From 554bd5edbd686e02785b2a317914197e23474edf Mon Sep 17 00:00:00 2001 From: Richard Stacpoole Date: Sun, 21 Jun 2026 09:53:12 +1000 Subject: [PATCH 1/3] Make Quantity extensible via RawRepresentable struct Replaces the closed `Quantity` enum with a `RawRepresentable` struct that exposes the same built-in dimensions as static constants, resolving the long-standing TODO about extensibility. Callers can now define their own dimensions (e.g. a `Money` dimension for cost calculations) with a static extension, without forking the package or repurposing an unrelated base quantity like `Amount`: extension Quantity { static let money = Quantity(rawValue: "Money") } All existing call sites are source-compatible: `.Length`-style literals, `[Quantity: Int]` dictionary keys, and the `\\Quantity.rawValue` keypath all continue to work. The one behavioral change is that the synthesized failable `init?(rawValue:)` becomes a non-failable `init(rawValue:)`, since any string is now a valid dimension. Adds QuantityTests covering custom-dimension identity, arithmetic cancellation, and dimension description. Documents the feature under a new 'Custom Dimensions' README section. Co-Authored-By: Claude Opus 4.8 --- README.md | 32 ++++++++++++++ Sources/Units/Quantity.swift | 39 +++++++++++------ Tests/UnitsTests/QuantityTests.swift | 63 ++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 Tests/UnitsTests/QuantityTests.swift diff --git a/README.md b/README.md index 42ce9cd..0f8e8e8 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,38 @@ let weeklyCartons = try (workforce * personPickRate).convert(to: carton / .week) print(weeklyCartons) // Prints '350.0 carton/week' ``` +### Custom Dimensions + +The built-in `Quantity` dimensions (`Length`, `Mass`, `Time`, etc.) cover the ISQ base +quantities, but you can define your own dimension when none of them fit — for example, a +`Money` dimension for cost calculations. Because `Quantity` is `RawRepresentable`, you can +add one with a static extension: + +```swift +extension Quantity { + static let money = Quantity(rawValue: "Money") +} +``` + +Your dimension then behaves like any built-in one. Here a concrete rate of `$/m^3` is +multiplied by a volume in `m^3`, and the volume cancels to leave a pure cost: + +```swift +let registry = try RegistryBuilder().addUnit( + name: "dollar", + symbol: "$", + dimension: [.money: 1] +).registry() +let dollar = try Unit(fromSymbol: "$", registry: registry) + +let rate = Measurement(value: 180, unit: dollar / .meter.pow(3)) // $180 per m^3 +let volume = Measurement(value: 24, unit: .meter.pow(3)) // 24 m^3 +print(rate * volume) // Prints '4320.0 $' +``` + +Unlike repurposing an unrelated base quantity such as `Amount`, a dedicated dimension can't +silently cancel against unrelated units. Use a unique `rawValue` for each distinct dimension. + ## CLI The easiest way to install the CLI is with brew: diff --git a/Sources/Units/Quantity.swift b/Sources/Units/Quantity.swift index 0674a91..75a1135 100644 --- a/Sources/Units/Quantity.swift +++ b/Sources/Units/Quantity.swift @@ -1,17 +1,32 @@ -/// A dimension of measurement. These may be combined to form composite dimensions and measurements -public enum Quantity: String, Sendable { - // TODO: Consider changing away from enum for extensibility +/// A dimension of measurement. These may be combined to form composite dimensions and measurements. +/// +/// `Quantity` is `RawRepresentable` rather than an enum so that callers can define their own +/// dimensions without modifying this package. Add one with a static extension: +/// +/// ```swift +/// extension Quantity { +/// static let money = Quantity(rawValue: "Money") +/// } +/// ``` +/// +/// Use a unique `rawValue` for each distinct dimension — equality and hashing are based on it. +public struct Quantity: RawRepresentable, Hashable, Sendable { + public let rawValue: String + + public init(rawValue: String) { + self.rawValue = rawValue + } // Base ISQ quantities: https://en.wikipedia.org/wiki/International_System_of_Quantities#Base_quantities - case Amount - case Current - case Length - case Mass - case Temperature - case Time - case LuminousIntensity + public static let Amount = Quantity(rawValue: "Amount") + public static let Current = Quantity(rawValue: "Current") + public static let Length = Quantity(rawValue: "Length") + public static let Mass = Quantity(rawValue: "Mass") + public static let Temperature = Quantity(rawValue: "Temperature") + public static let Time = Quantity(rawValue: "Time") + public static let LuminousIntensity = Quantity(rawValue: "LuminousIntensity") // Extended SI Units: https://en.wikipedia.org/wiki/Non-SI_units_mentioned_in_the_SI - case Angle - case Data + public static let Angle = Quantity(rawValue: "Angle") + public static let Data = Quantity(rawValue: "Data") } diff --git a/Tests/UnitsTests/QuantityTests.swift b/Tests/UnitsTests/QuantityTests.swift new file mode 100644 index 0000000..217da22 --- /dev/null +++ b/Tests/UnitsTests/QuantityTests.swift @@ -0,0 +1,63 @@ +import Units +import XCTest + +/// A user-defined dimension, declared outside the package. This is the capability +/// that `Quantity` being `RawRepresentable` (rather than a closed enum) enables. +private extension Quantity { + static let money = Quantity(rawValue: "Money") +} + +class QuantityTests: XCTestCase { + /// Built-in quantities keep their stable string raw values. + func testBuiltInRawValues() { + XCTAssertEqual(Quantity.Length.rawValue, "Length") + XCTAssertEqual(Quantity.Time.rawValue, "Time") + XCTAssertEqual(Quantity(rawValue: "Mass"), .Mass) + } + + /// A custom dimension is distinct from every built-in one and equal to itself. + func testCustomQuantityIdentity() { + XCTAssertEqual(Quantity.money, Quantity(rawValue: "Money")) + XCTAssertNotEqual(Quantity.money, .Length) + XCTAssertNotEqual(Quantity.money, .Amount) + + // Usable as a dictionary key alongside built-ins. + let dimension: [Quantity: Int] = [.money: 1, .Length: -3] + XCTAssertEqual(dimension[.money], 1) + XCTAssertEqual(dimension[.Length], -3) + } + + /// A unit on a custom dimension participates in dimensional arithmetic: + /// a rate of `$/m^3` multiplied by a volume in `m^3` cancels to pure `$`. + func testCustomDimensionCancelsInArithmetic() throws { + let registry = try RegistryBuilder().addUnit( + name: "dollar", + symbol: "$", + dimension: [.money: 1] + ).registry() + + let dollar = try Unit(fromSymbol: "$", registry: registry) + let cubicMeter = Unit.meter.pow(3) + + let rate = Measurement(value: 180, unit: dollar / cubicMeter) // $180 per m^3 + let volume = Measurement(value: 24, unit: cubicMeter) // 24 m^3 + let cost = rate * volume + + XCTAssertEqual(cost.value, 4320, accuracy: 1e-9) + // The volume cancels, leaving a pure money dimension. + XCTAssertEqual(cost.unit.dimension, [.money: 1]) + XCTAssertTrue(cost.isDimensionallyEquivalent(to: Measurement(value: 1, unit: dollar))) + } + + /// A custom dimension surfaces in the human-readable dimension description. + func testCustomDimensionDescription() throws { + let registry = try RegistryBuilder().addUnit( + name: "dollar", + symbol: "$", + dimension: [.money: 1] + ).registry() + + let dollar = try Unit(fromSymbol: "$", registry: registry) + XCTAssertEqual(dollar.dimensionDescription(), "Money") + } +} From e3db0ab01ac222d16dfe9b6dd385b80c478635e8 Mon Sep 17 00:00:00 2001 From: Richard Stacpoole Date: Sat, 27 Jun 2026 15:37:35 +1000 Subject: [PATCH 2/3] Rename Quantity properties to lowerCamelCase; restore description Addresses review feedback on the enum -> struct change: - Rename the static dimension properties to lowerCamelCase (.length, .mass, ...) per the Swift API Design Guidelines. The former PascalCase names are kept as @available(*, deprecated, renamed:) aliases so existing call sites keep compiling; they can be removed in a future major release. Internal call sites and tests now use the new names. - Add CustomStringConvertible returning rawValue, restoring the enum's behavior where "\(Quantity.length)" prints "Length" rather than the synthesized struct description. Co-Authored-By: Claude Opus 4.8 --- Sources/Units/Quantity.swift | 58 +++- Sources/Units/Unit/DefaultUnits.swift | 380 ++++++++++++------------ Tests/UnitsTests/MeasurementTests.swift | 24 +- Tests/UnitsTests/QuantityTests.swift | 20 +- Tests/UnitsTests/UnitTests.swift | 14 +- 5 files changed, 270 insertions(+), 226 deletions(-) diff --git a/Sources/Units/Quantity.swift b/Sources/Units/Quantity.swift index 75a1135..a11b31c 100644 --- a/Sources/Units/Quantity.swift +++ b/Sources/Units/Quantity.swift @@ -9,7 +9,11 @@ /// } /// ``` /// -/// Use a unique `rawValue` for each distinct dimension — equality and hashing are based on it. +/// Equality and hashing are based on `rawValue`, so each distinct dimension needs a unique +/// raw string. Because the namespace is global across every module that links this package, +/// two modules that independently pick the same raw value are treated as the *same* dimension +/// and silently cancel against one another. Prefix custom raw values to avoid collisions — +/// e.g. `"Acme.Money"` rather than `"Money"`. public struct Quantity: RawRepresentable, Hashable, Sendable { public let rawValue: String @@ -18,15 +22,49 @@ public struct Quantity: RawRepresentable, Hashable, Sendable { } // Base ISQ quantities: https://en.wikipedia.org/wiki/International_System_of_Quantities#Base_quantities - public static let Amount = Quantity(rawValue: "Amount") - public static let Current = Quantity(rawValue: "Current") - public static let Length = Quantity(rawValue: "Length") - public static let Mass = Quantity(rawValue: "Mass") - public static let Temperature = Quantity(rawValue: "Temperature") - public static let Time = Quantity(rawValue: "Time") - public static let LuminousIntensity = Quantity(rawValue: "LuminousIntensity") + public static let amount = Quantity(rawValue: "Amount") + public static let current = Quantity(rawValue: "Current") + public static let length = Quantity(rawValue: "Length") + public static let mass = Quantity(rawValue: "Mass") + public static let temperature = Quantity(rawValue: "Temperature") + public static let time = Quantity(rawValue: "Time") + public static let luminousIntensity = Quantity(rawValue: "LuminousIntensity") // Extended SI Units: https://en.wikipedia.org/wiki/Non-SI_units_mentioned_in_the_SI - public static let Angle = Quantity(rawValue: "Angle") - public static let Data = Quantity(rawValue: "Data") + public static let angle = Quantity(rawValue: "Angle") + public static let data = Quantity(rawValue: "Data") +} + +// MARK: - Deprecated PascalCase aliases + +// The previous `enum Quantity` spelled its cases in PascalCase. These aliases keep existing +// call sites compiling against the renamed lowerCamelCase properties (Swift API Design +// Guidelines) and can be removed in a future major release. +public extension Quantity { + @available(*, deprecated, renamed: "amount") + static let Amount = Quantity.amount + @available(*, deprecated, renamed: "current") + static let Current = Quantity.current + @available(*, deprecated, renamed: "length") + static let Length = Quantity.length + @available(*, deprecated, renamed: "mass") + static let Mass = Quantity.mass + @available(*, deprecated, renamed: "temperature") + static let Temperature = Quantity.temperature + @available(*, deprecated, renamed: "time") + static let Time = Quantity.time + @available(*, deprecated, renamed: "luminousIntensity") + static let LuminousIntensity = Quantity.luminousIntensity + @available(*, deprecated, renamed: "angle") + static let Angle = Quantity.angle + @available(*, deprecated, renamed: "data") + static let Data = Quantity.data +} + +// MARK: - CustomStringConvertible + +extension Quantity: CustomStringConvertible { + // Preserve the enum's behavior: interpolating a Quantity yields its raw value + // (e.g. "Length"), not the synthesized struct description. + public var description: String { rawValue } } diff --git a/Sources/Units/Unit/DefaultUnits.swift b/Sources/Units/Unit/DefaultUnits.swift index c856543..fb6eeed 100644 --- a/Sources/Units/Unit/DefaultUnits.swift +++ b/Sources/Units/Unit/DefaultUnits.swift @@ -14,7 +14,7 @@ enum DefaultUnits { static let standardGravity = try! DefinedUnit( name: "standardGravity", symbol: "ɡ", - dimension: [.Length: 1, .Time: -2], + dimension: [.length: 1, .time: -2], coefficient: 9.80665 ) @@ -25,18 +25,18 @@ enum DefaultUnits { static let mole = try! DefinedUnit( name: "mole", symbol: "mol", - dimension: [.Amount: 1] + dimension: [.amount: 1] ) static let millimole = try! DefinedUnit( name: "millimole", symbol: "mmol", - dimension: [.Amount: 1], + dimension: [.amount: 1], coefficient: 0.001 ) static let particle = try! DefinedUnit( name: "particle", symbol: "particle", - dimension: [.Amount: 1], + dimension: [.amount: 1], coefficient: 6.02214076e-23 ) @@ -47,18 +47,18 @@ enum DefaultUnits { static let radian = try! DefinedUnit( name: "radian", symbol: "rad", - dimension: [.Angle: 1] + dimension: [.angle: 1] ) static let degree = try! DefinedUnit( name: "degree", symbol: "°", - dimension: [.Angle: 1], + dimension: [.angle: 1], coefficient: 180 / Double.pi ) static let revolution = try! DefinedUnit( name: "revolution", symbol: "rev", - dimension: [.Angle: 1], + dimension: [.angle: 1], coefficient: 2 * Double.pi ) @@ -69,19 +69,19 @@ enum DefaultUnits { static let acre = try! DefinedUnit( name: "acre", symbol: "ac", - dimension: [.Length: 2], + dimension: [.length: 2], coefficient: 4046.8564224 ) static let are = try! DefinedUnit( name: "are", symbol: "a", - dimension: [.Length: 2], + dimension: [.length: 2], coefficient: 100 ) static let hectare = try! DefinedUnit( name: "hectare", symbol: "ha", - dimension: [.Length: 2], + dimension: [.length: 2], coefficient: 10000 ) @@ -92,7 +92,7 @@ enum DefaultUnits { static let farad = try! DefinedUnit( name: "farad", symbol: "F", - dimension: [.Current: 2, .Time: 4, .Length: -2, .Mass: -1] + dimension: [.current: 2, .time: 4, .length: -2, .mass: -1] ) // MARK: Charge @@ -102,7 +102,7 @@ enum DefaultUnits { static let coulomb = try! DefinedUnit( name: "coulomb", symbol: "C", - dimension: [.Current: 1, .Time: 1] + dimension: [.current: 1, .time: 1] ) // MARK: Current @@ -112,30 +112,30 @@ enum DefaultUnits { static let ampere = try! DefinedUnit( name: "ampere", symbol: "A", - dimension: [.Current: 1] + dimension: [.current: 1] ) static let microampere = try! DefinedUnit( name: "microampere", symbol: "μA", - dimension: [.Current: 1], + dimension: [.current: 1], coefficient: 1e-6 ) static let milliampere = try! DefinedUnit( name: "milliampere", symbol: "mA", - dimension: [.Current: 1], + dimension: [.current: 1], coefficient: 0.001 ) static let kiloampere = try! DefinedUnit( name: "kiloampere", symbol: "kA", - dimension: [.Current: 1], + dimension: [.current: 1], coefficient: 1000 ) static let megaampere = try! DefinedUnit( name: "megaampere", symbol: "MA", - dimension: [.Current: 1], + dimension: [.current: 1], coefficient: 1e6 ) @@ -146,204 +146,204 @@ enum DefaultUnits { static let bit = try! DefinedUnit( name: "bit", symbol: "bit", - dimension: [.Data: 1] + dimension: [.data: 1] ) static let kilobit = try! DefinedUnit( name: "kilobit", symbol: "kbit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 1000 ) static let megabit = try! DefinedUnit( name: "megabit", symbol: "Mbit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 1e6 ) static let gigabit = try! DefinedUnit( name: "gigabit", symbol: "Gbit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 1e9 ) static let terabit = try! DefinedUnit( name: "terabit", symbol: "Tbit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 1e12 ) static let petabit = try! DefinedUnit( name: "petabit", symbol: "Pbit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 1e15 ) static let exabit = try! DefinedUnit( name: "exabit", symbol: "Ebit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 1e18 ) static let zetabit = try! DefinedUnit( name: "zetabit", symbol: "Zbit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 1e21 ) static let yottabit = try! DefinedUnit( name: "yottabit", symbol: "Ybit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 1e24 ) static let kibibit = try! DefinedUnit( name: "kibibit", symbol: "Kibit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 1024 ) static let mebibit = try! DefinedUnit( name: "mebibit", symbol: "Mibit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: pow(1024, 2) ) static let gibibit = try! DefinedUnit( name: "gibibit", symbol: "Gibit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: pow(1024, 3) ) static let tebibit = try! DefinedUnit( name: "tebibit", symbol: "Tibit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: pow(1024, 4) ) static let pebibit = try! DefinedUnit( name: "pebibit", symbol: "Pibit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: pow(1024, 5) ) static let exbibit = try! DefinedUnit( name: "exbibit", symbol: "Eibit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: pow(1024, 6) ) static let zebibit = try! DefinedUnit( name: "zebibit", symbol: "Zibit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: pow(1024, 7) ) static let yobibit = try! DefinedUnit( name: "yobibit", symbol: "Yibit", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: pow(1024, 8) ) static let byte = try! DefinedUnit( name: "byte", symbol: "byte", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8 ) static let kilobyte = try! DefinedUnit( name: "kilobyte", symbol: "kB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8000 ) static let megabyte = try! DefinedUnit( name: "megabyte", symbol: "MB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8e6 ) static let gigabyte = try! DefinedUnit( name: "gigabyte", symbol: "GB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8e9 ) static let terabyte = try! DefinedUnit( name: "terabyte", symbol: "TB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8e12 ) static let petabyte = try! DefinedUnit( name: "petabyte", symbol: "PB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8e15 ) static let exabyte = try! DefinedUnit( name: "exabyte", symbol: "EB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8e18 ) static let zetabyte = try! DefinedUnit( name: "zetabyte", symbol: "ZB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8e21 ) static let yottabyte = try! DefinedUnit( name: "yottabyte", symbol: "YB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8e24 ) static let kibibyte = try! DefinedUnit( name: "kibibyte", symbol: "KiB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8 * 1024 ) static let mebibyte = try! DefinedUnit( name: "mebibyte", symbol: "MiB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8 * pow(1024, 2) ) static let gibibyte = try! DefinedUnit( name: "gibibyte", symbol: "GiB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8 * pow(1024, 3) ) static let tebibyte = try! DefinedUnit( name: "tebibyte", symbol: "TiB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8 * pow(1024, 4) ) static let pebibyte = try! DefinedUnit( name: "pebibyte", symbol: "PiB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8 * pow(1024, 5) ) static let exbibyte = try! DefinedUnit( name: "exbibyte", symbol: "EiB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8 * pow(1024, 6) ) static let zebibyte = try! DefinedUnit( name: "zebibyte", symbol: "ZiB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8 * pow(1024, 7) ) static let yobibyte = try! DefinedUnit( name: "yobibyte", symbol: "YiB", - dimension: [.Data: 1], + dimension: [.data: 1], coefficient: 8 * pow(1024, 8) ) @@ -354,30 +354,30 @@ enum DefaultUnits { static let volt = try! DefinedUnit( name: "volt", symbol: "V", - dimension: [.Mass: 1, .Length: 2, .Time: -3, .Current: -1] + dimension: [.mass: 1, .length: 2, .time: -3, .current: -1] ) static let microvolt = try! DefinedUnit( name: "microvolt", symbol: "μV", - dimension: [.Mass: 1, .Length: 2, .Time: -3, .Current: -1], + dimension: [.mass: 1, .length: 2, .time: -3, .current: -1], coefficient: 1e-6 ) static let millivolt = try! DefinedUnit( name: "millivolt", symbol: "mV", - dimension: [.Mass: 1, .Length: 2, .Time: -3, .Current: -1], + dimension: [.mass: 1, .length: 2, .time: -3, .current: -1], coefficient: 0.001 ) static let kilovolt = try! DefinedUnit( name: "kilovolt", symbol: "kV", - dimension: [.Mass: 1, .Length: 2, .Time: -3, .Current: -1], + dimension: [.mass: 1, .length: 2, .time: -3, .current: -1], coefficient: 1000 ) static let megavolt = try! DefinedUnit( name: "megavolt", symbol: "MV", - dimension: [.Mass: 1, .Length: 2, .Time: -3, .Current: -1], + dimension: [.mass: 1, .length: 2, .time: -3, .current: -1], coefficient: 1e6 ) @@ -388,61 +388,61 @@ enum DefaultUnits { static let joule = try! DefinedUnit( name: "joule", symbol: "J", - dimension: [.Mass: 1, .Length: 2, .Time: -2] + dimension: [.mass: 1, .length: 2, .time: -2] ) static let kilojoule = try! DefinedUnit( name: "kilojoule", symbol: "kJ", - dimension: [.Mass: 1, .Length: 2, .Time: -2], + dimension: [.mass: 1, .length: 2, .time: -2], coefficient: 1000 ) static let megajoule = try! DefinedUnit( name: "megajoule", symbol: "MJ", - dimension: [.Mass: 1, .Length: 2, .Time: -2], + dimension: [.mass: 1, .length: 2, .time: -2], coefficient: 1_000_000 ) static let calorie = try! DefinedUnit( name: "calorie", symbol: "cal", - dimension: [.Mass: 1, .Length: 2, .Time: -2], + dimension: [.mass: 1, .length: 2, .time: -2], coefficient: 4.184 ) static let kilocalorie = try! DefinedUnit( name: "kilocalorie", symbol: "kCal", - dimension: [.Mass: 1, .Length: 2, .Time: -2], + dimension: [.mass: 1, .length: 2, .time: -2], coefficient: 4184 ) // Thermochemical BTU: https://en.wikipedia.org/wiki/British_thermal_unit#Definitions static let btu = try! DefinedUnit( name: "btu", symbol: "BTU", - dimension: [.Mass: 1, .Length: 2, .Time: -2], + dimension: [.mass: 1, .length: 2, .time: -2], coefficient: 1054.35 ) static let kilobtu = try! DefinedUnit( name: "kilobtu", symbol: "kBTU", - dimension: [.Mass: 1, .Length: 2, .Time: -2], + dimension: [.mass: 1, .length: 2, .time: -2], coefficient: 1.05435e6 ) static let megabtu = try! DefinedUnit( name: "megabtu", symbol: "MBTU", - dimension: [.Mass: 1, .Length: 2, .Time: -2], + dimension: [.mass: 1, .length: 2, .time: -2], coefficient: 1.05435e9 ) static let therm = try! DefinedUnit( name: "therm", symbol: "therm", - dimension: [.Mass: 1, .Length: 2, .Time: -2], + dimension: [.mass: 1, .length: 2, .time: -2], coefficient: 1.05435e8 ) static let electronVolt = try! DefinedUnit( name: "electronVolt", symbol: "eV", - dimension: [.Mass: 1, .Length: 2, .Time: -2], + dimension: [.mass: 1, .length: 2, .time: -2], coefficient: 1.602176634e-19 ) @@ -453,12 +453,12 @@ enum DefaultUnits { static let newton = try! DefinedUnit( name: "newton", symbol: "N", - dimension: [.Mass: 1, .Length: 1, .Time: -2] + dimension: [.mass: 1, .length: 1, .time: -2] ) static let poundForce = try! DefinedUnit( name: "poundForce", symbol: "lbf", - dimension: [.Mass: 1, .Length: 1, .Time: -2], + dimension: [.mass: 1, .length: 1, .time: -2], coefficient: 4.448222 ) @@ -469,48 +469,48 @@ enum DefaultUnits { static let hertz = try! DefinedUnit( name: "hertz", symbol: "Hz", - dimension: [.Time: -1] + dimension: [.time: -1] ) static let nanohertz = try! DefinedUnit( name: "nanohertz", symbol: "nHz", - dimension: [.Time: -1], + dimension: [.time: -1], coefficient: 1e-9 ) static let microhertz = try! DefinedUnit( name: "microhertz", symbol: "μHz", - dimension: [.Time: -1], + dimension: [.time: -1], coefficient: 1e-6 ) static let millihertz = try! DefinedUnit( name: "millihertz", symbol: "mHz", - dimension: [.Time: -1], + dimension: [.time: -1], coefficient: 0.001 ) static let kilohertz = try! DefinedUnit( name: "kilohertz", symbol: "kHz", - dimension: [.Time: -1], + dimension: [.time: -1], coefficient: 1000 ) static let megahertz = try! DefinedUnit( name: "megahertz", symbol: "MHz", - dimension: [.Time: -1], + dimension: [.time: -1], coefficient: 1e6 ) static let gigahertz = try! DefinedUnit( name: "gigahertz", symbol: "GHz", - dimension: [.Time: -1], + dimension: [.time: -1], coefficient: 1e9 ) static let terahertz = try! DefinedUnit( name: "terahertz", symbol: "THz", - dimension: [.Time: -1], + dimension: [.time: -1], coefficient: 1e12 ) @@ -521,18 +521,18 @@ enum DefaultUnits { static let lux = try! DefinedUnit( name: "lux", symbol: "lx", - dimension: [.LuminousIntensity: 1, .Length: -2] + dimension: [.luminousIntensity: 1, .length: -2] ) static let footCandle = try! DefinedUnit( name: "footCandle", symbol: "fc", - dimension: [.LuminousIntensity: 1, .Length: -2], + dimension: [.luminousIntensity: 1, .length: -2], coefficient: 10.76 ) static let phot = try! DefinedUnit( name: "phot", symbol: "phot", - dimension: [.LuminousIntensity: 1, .Length: -2], + dimension: [.luminousIntensity: 1, .length: -2], coefficient: 10000 ) @@ -543,7 +543,7 @@ enum DefaultUnits { static let henry = try! DefinedUnit( name: "henry", symbol: "H", - dimension: [.Length: 2, .Mass: 1, .Current: -2] + dimension: [.length: 2, .mass: 1, .current: -2] ) // MARK: Length @@ -553,127 +553,127 @@ enum DefaultUnits { static let meter = try! DefinedUnit( name: "meter", symbol: "m", - dimension: [.Length: 1] + dimension: [.length: 1] ) static let picometer = try! DefinedUnit( name: "picometer", symbol: "pm", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 1e-12 ) static let nanoometer = try! DefinedUnit( name: "nanoometer", symbol: "nm", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 1e-9 ) static let micrometer = try! DefinedUnit( name: "micrometer", symbol: "μm", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 1e-6 ) static let millimeter = try! DefinedUnit( name: "millimeter", symbol: "mm", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 0.001 ) static let centimeter = try! DefinedUnit( name: "centimeter", symbol: "cm", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 0.01 ) static let decameter = try! DefinedUnit( name: "decameter", symbol: "dm", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 10 ) static let hectometer = try! DefinedUnit( name: "hectometer", symbol: "hm", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 100 ) static let kilometer = try! DefinedUnit( name: "kilometer", symbol: "km", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 1000 ) static let megameter = try! DefinedUnit( name: "megameter", symbol: "Mm", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 1e6 ) static let inch = try! DefinedUnit( name: "inch", symbol: "in", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 0.0254 ) static let foot = try! DefinedUnit( name: "foot", symbol: "ft", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 0.3048 ) // International yard: https://en.wikipedia.org/wiki/Yard static let yard = try! DefinedUnit( name: "yard", symbol: "yd", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 0.9144 ) static let mile = try! DefinedUnit( name: "mile", symbol: "mi", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 1609.344 ) static let scandanavianMile = try! DefinedUnit( name: "scandanavianMile", symbol: "smi", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 10000 ) static let nauticalMile = try! DefinedUnit( name: "nauticalMile", symbol: "NM", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 1852 ) static let fathom = try! DefinedUnit( name: "fathom", symbol: "fathom", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 1.8288 ) static let furlong = try! DefinedUnit( name: "furlong", symbol: "furlong", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 201.168 ) static let astronomicalUnit = try! DefinedUnit( name: "astronomicalUnit", symbol: "au", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 1.495978707e11 ) static let lightyear = try! DefinedUnit( name: "lightyear", symbol: "ly", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 9.4607304725808e15 ) static let parsec = try! DefinedUnit( name: "parsec", symbol: "pc", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 3.0856775814913673e16 ) @@ -684,7 +684,7 @@ enum DefaultUnits { static let candela = try! DefinedUnit( name: "candela", symbol: "cd", - dimension: [.LuminousIntensity: 1] + dimension: [.luminousIntensity: 1] ) // MARK: Luminous Flux @@ -694,7 +694,7 @@ enum DefaultUnits { static let lumen = try! DefinedUnit( name: "lumen", symbol: "lm", - dimension: [.Angle: 2, .LuminousIntensity: 1] + dimension: [.angle: 2, .luminousIntensity: 1] ) // MARK: Magnetic Flux @@ -704,7 +704,7 @@ enum DefaultUnits { static let weber = try! DefinedUnit( name: "weber", symbol: "Wb", - dimension: [.Mass: 1, .Length: 2, .Time: -2, .Current: -1] + dimension: [.mass: 1, .length: 2, .time: -2, .current: -1] ) // MARK: Magnetic Flux Density @@ -714,7 +714,7 @@ enum DefaultUnits { static let tesla = try! DefinedUnit( name: "tesla", symbol: "T", - dimension: [.Mass: 1, .Time: -2, .Current: -1] + dimension: [.mass: 1, .time: -2, .current: -1] ) // MARK: Mass @@ -724,97 +724,97 @@ enum DefaultUnits { static let kilogram = try! DefinedUnit( name: "kilogram", symbol: "kg", - dimension: [.Mass: 1] + dimension: [.mass: 1] ) static let picogram = try! DefinedUnit( name: "picogram", symbol: "pg", - dimension: [.Mass: 1], + dimension: [.mass: 1], coefficient: 1e-15 ) static let nanogram = try! DefinedUnit( name: "nanogram", symbol: "ng", - dimension: [.Mass: 1], + dimension: [.mass: 1], coefficient: 1e-12 ) static let microgram = try! DefinedUnit( name: "microgram", symbol: "μg", - dimension: [.Mass: 1], + dimension: [.mass: 1], coefficient: 1e-9 ) static let milligram = try! DefinedUnit( name: "milligram", symbol: "mg", - dimension: [.Mass: 1], + dimension: [.mass: 1], coefficient: 1e-6 ) static let centigram = try! DefinedUnit( name: "centigram", symbol: "cg", - dimension: [.Mass: 1], + dimension: [.mass: 1], coefficient: 0.00001 ) static let decigram = try! DefinedUnit( name: "decigram", symbol: "dg", - dimension: [.Mass: 1], + dimension: [.mass: 1], coefficient: 0.0001 ) static let gram = try! DefinedUnit( name: "gram", symbol: "g", - dimension: [.Mass: 1], + dimension: [.mass: 1], coefficient: 0.001 ) static let metricTon = try! DefinedUnit( name: "metricTon", symbol: "t", - dimension: [.Mass: 1], + dimension: [.mass: 1], coefficient: 1000 ) static let carat = try! DefinedUnit( name: "carat", symbol: "ct", - dimension: [.Mass: 1], + dimension: [.mass: 1], coefficient: 0.0002 ) static let ounce = try! DefinedUnit( name: "ounce", symbol: "oz", - dimension: [.Mass: 1], + dimension: [.mass: 1], coefficient: 0.028349523125 ) // pound-mass: https://en.wikipedia.org/wiki/Pound_(mass) static let pound = try! DefinedUnit( name: "pound", symbol: "lb", - dimension: [.Mass: 1], + dimension: [.mass: 1], coefficient: 0.45359237 ) static let stone = try! DefinedUnit( name: "stone", symbol: "st", - dimension: [.Mass: 1], + dimension: [.mass: 1], coefficient: 6.35029318 ) static let shortTon = try! DefinedUnit( name: "shortTon", symbol: "ton", - dimension: [.Mass: 1], + dimension: [.mass: 1], coefficient: 907.18474 ) static let troyOunces = try! DefinedUnit( name: "troyOunces", symbol: "troyOunces", - dimension: [.Mass: 1], + dimension: [.mass: 1], coefficient: 0.0311034768 ) static let slug = try! DefinedUnit( name: "slug", symbol: "slug", - dimension: [.Mass: 1], + dimension: [.mass: 1], coefficient: 14.5939 ) @@ -834,72 +834,72 @@ enum DefaultUnits { static let watt = try! DefinedUnit( name: "watt", symbol: "W", - dimension: [.Mass: 1, .Length: 2, .Time: -3] + dimension: [.mass: 1, .length: 2, .time: -3] ) static let femptowatt = try! DefinedUnit( name: "femptowatt", symbol: "fW", - dimension: [.Mass: 1, .Length: 2, .Time: -3], + dimension: [.mass: 1, .length: 2, .time: -3], coefficient: 1e-15 ) static let picowatt = try! DefinedUnit( name: "picowatt", symbol: "pW", - dimension: [.Mass: 1, .Length: 2, .Time: -3], + dimension: [.mass: 1, .length: 2, .time: -3], coefficient: 1e-12 ) static let nanowatt = try! DefinedUnit( name: "nanowatt", symbol: "nW", - dimension: [.Mass: 1, .Length: 2, .Time: -3], + dimension: [.mass: 1, .length: 2, .time: -3], coefficient: 1e-9 ) static let microwatt = try! DefinedUnit( name: "microwatt", symbol: "μW", - dimension: [.Mass: 1, .Length: 2, .Time: -3], + dimension: [.mass: 1, .length: 2, .time: -3], coefficient: 1e-6 ) static let milliwatt = try! DefinedUnit( name: "milliwatt", symbol: "mW", - dimension: [.Mass: 1, .Length: 2, .Time: -3], + dimension: [.mass: 1, .length: 2, .time: -3], coefficient: 0.001 ) static let kilowatt = try! DefinedUnit( name: "kilowatt", symbol: "kW", - dimension: [.Mass: 1, .Length: 2, .Time: -3], + dimension: [.mass: 1, .length: 2, .time: -3], coefficient: 1000 ) static let megawatt = try! DefinedUnit( name: "megawatt", symbol: "MW", - dimension: [.Mass: 1, .Length: 2, .Time: -3], + dimension: [.mass: 1, .length: 2, .time: -3], coefficient: 1e6 ) static let gigawatt = try! DefinedUnit( name: "gigawatt", symbol: "GW", - dimension: [.Mass: 1, .Length: 2, .Time: -3], + dimension: [.mass: 1, .length: 2, .time: -3], coefficient: 1e9 ) static let terawatt = try! DefinedUnit( name: "terawatt", symbol: "TW", - dimension: [.Mass: 1, .Length: 2, .Time: -3], + dimension: [.mass: 1, .length: 2, .time: -3], coefficient: 1e12 ) static let horsepower = try! DefinedUnit( name: "horsepower", symbol: "hp", - dimension: [.Mass: 1, .Length: 2, .Time: -3], + dimension: [.mass: 1, .length: 2, .time: -3], coefficient: 745.6998715822702 ) static let tonRefrigeration = try! DefinedUnit( name: "tonRefrigeration", symbol: "TR", - dimension: [.Mass: 1, .Length: 2, .Time: -3], + dimension: [.mass: 1, .length: 2, .time: -3], coefficient: 3500 ) @@ -910,78 +910,78 @@ enum DefaultUnits { static let pascal = try! DefinedUnit( name: "pascal", symbol: "Pa", - dimension: [.Mass: 1, .Length: -1, .Time: -2] + dimension: [.mass: 1, .length: -1, .time: -2] ) static let hectopascal = try! DefinedUnit( name: "hectopascal", symbol: "hPa", - dimension: [.Mass: 1, .Length: -1, .Time: -2], + dimension: [.mass: 1, .length: -1, .time: -2], coefficient: 100 ) static let kilopascal = try! DefinedUnit( name: "kilopascal", symbol: "kPa", - dimension: [.Mass: 1, .Length: -1, .Time: -2], + dimension: [.mass: 1, .length: -1, .time: -2], coefficient: 1000 ) static let megapascal = try! DefinedUnit( name: "megapascal", symbol: "MPa", - dimension: [.Mass: 1, .Length: -1, .Time: -2], + dimension: [.mass: 1, .length: -1, .time: -2], coefficient: 1e6 ) static let gigapascal = try! DefinedUnit( name: "gigapascal", symbol: "GPa", - dimension: [.Mass: 1, .Length: -1, .Time: -2], + dimension: [.mass: 1, .length: -1, .time: -2], coefficient: 1e9 ) static let bar = try! DefinedUnit( name: "bar", symbol: "bar", - dimension: [.Mass: 1, .Length: -1, .Time: -2], + dimension: [.mass: 1, .length: -1, .time: -2], coefficient: 100_000 ) static let millibar = try! DefinedUnit( name: "millibar", symbol: "mbar", - dimension: [.Mass: 1, .Length: -1, .Time: -2], + dimension: [.mass: 1, .length: -1, .time: -2], coefficient: 100 ) static let atmosphere = try! DefinedUnit( name: "atmosphere", symbol: "atm", - dimension: [.Mass: 1, .Length: -1, .Time: -2], + dimension: [.mass: 1, .length: -1, .time: -2], coefficient: 101_317.1 ) static let millimeterOfMercury = try! DefinedUnit( name: "millimeterOfMercury", symbol: "mmhg", - dimension: [.Mass: 1, .Length: -1, .Time: -2], + dimension: [.mass: 1, .length: -1, .time: -2], coefficient: 133.322387415 ) static let centimeterOfMercury = try! DefinedUnit( name: "centimeterOfMercury", symbol: "cmhg", - dimension: [.Mass: 1, .Length: -1, .Time: -2], + dimension: [.mass: 1, .length: -1, .time: -2], coefficient: 1333.22387415 ) static let inchOfMercury = try! DefinedUnit( name: "inchOfMercury", symbol: "inhg", - dimension: [.Mass: 1, .Length: -1, .Time: -2], + dimension: [.mass: 1, .length: -1, .time: -2], coefficient: 3386.389 ) static let centimeterOfWater = try! DefinedUnit( name: "centimeterOfWater", symbol: "cmH₂0", - dimension: [.Mass: 1, .Length: -1, .Time: -2], + dimension: [.mass: 1, .length: -1, .time: -2], coefficient: 98.0665 ) static let inchOfWater = try! DefinedUnit( name: "inchOfWater", symbol: "inH₂0", - dimension: [.Mass: 1, .Length: -1, .Time: -2], + dimension: [.mass: 1, .length: -1, .time: -2], coefficient: 249.082 ) @@ -992,30 +992,30 @@ enum DefaultUnits { static let ohm = try! DefinedUnit( name: "ohm", symbol: "Ω", - dimension: [.Mass: 1, .Length: 2, .Time: -3, .Current: -2] + dimension: [.mass: 1, .length: 2, .time: -3, .current: -2] ) static let microohm = try! DefinedUnit( name: "microohm", symbol: "μΩ", - dimension: [.Mass: 1, .Length: 2, .Time: -3, .Current: -2], + dimension: [.mass: 1, .length: 2, .time: -3, .current: -2], coefficient: 1e-6 ) static let milliohm = try! DefinedUnit( name: "milliohm", symbol: "mΩ", - dimension: [.Mass: 1, .Length: 2, .Time: -3, .Current: -2], + dimension: [.mass: 1, .length: 2, .time: -3, .current: -2], coefficient: 0.001 ) static let kiloohm = try! DefinedUnit( name: "kiloohm", symbol: "kΩ", - dimension: [.Mass: 1, .Length: 2, .Time: -3, .Current: -2], + dimension: [.mass: 1, .length: 2, .time: -3, .current: -2], coefficient: 1000 ) static let megaohm = try! DefinedUnit( name: "megaohm", symbol: "MΩ", - dimension: [.Mass: 1, .Length: 2, .Time: -3, .Current: -2], + dimension: [.mass: 1, .length: 2, .time: -3, .current: -2], coefficient: 1e6 ) @@ -1026,7 +1026,7 @@ enum DefaultUnits { static let steradian = try! DefinedUnit( name: "steradian", symbol: "sr", - dimension: [.Angle: 2] + dimension: [.angle: 2] ) // MARK: Temperature @@ -1036,25 +1036,25 @@ enum DefaultUnits { static let kelvin = try! DefinedUnit( name: "kelvin", symbol: "K", - dimension: [.Temperature: 1] + dimension: [.temperature: 1] ) static let celsius = try! DefinedUnit( name: "celsius", symbol: "°C", - dimension: [.Temperature: 1], + dimension: [.temperature: 1], constant: 273.15 ) static let fahrenheit = try! DefinedUnit( name: "fahrenheit", symbol: "°F", - dimension: [.Temperature: 1], + dimension: [.temperature: 1], coefficient: 5.0 / 9.0, constant: 273.15 - (32 * 5.0 / 9.0) ) static let rankine = try! DefinedUnit( name: "rankine", symbol: "°R", - dimension: [.Temperature: 1], + dimension: [.temperature: 1], coefficient: 5.0 / 9.0 ) @@ -1065,55 +1065,55 @@ enum DefaultUnits { static let second = try! DefinedUnit( name: "second", symbol: "s", - dimension: [.Time: 1] + dimension: [.time: 1] ) static let nanosecond = try! DefinedUnit( name: "nanosecond", symbol: "ns", - dimension: [.Time: 1], + dimension: [.time: 1], coefficient: 1e-9 ) static let microsecond = try! DefinedUnit( name: "microsecond", symbol: "μs", - dimension: [.Time: 1], + dimension: [.time: 1], coefficient: 1e-6 ) static let millisecond = try! DefinedUnit( name: "millisecond", symbol: "ms", - dimension: [.Time: 1], + dimension: [.time: 1], coefficient: 0.001 ) static let minute = try! DefinedUnit( name: "minute", symbol: "min", - dimension: [.Time: 1], + dimension: [.time: 1], coefficient: 60 ) static let hour = try! DefinedUnit( name: "hour", symbol: "hr", - dimension: [.Time: 1], + dimension: [.time: 1], coefficient: 3600 ) static let day = try! DefinedUnit( name: "day", symbol: "d", - dimension: [.Time: 1], + dimension: [.time: 1], coefficient: 86400 ) static let week = try! DefinedUnit( name: "week", symbol: "week", - dimension: [.Time: 1], + dimension: [.time: 1], coefficient: 604_800 ) // Julian year: https://en.wikipedia.org/wiki/Julian_year_%28astronomy%29 static let year = try! DefinedUnit( name: "year", symbol: "yr", - dimension: [.Time: 1], + dimension: [.time: 1], coefficient: 31_557_600 ) @@ -1124,7 +1124,7 @@ enum DefaultUnits { static let knots = try! DefinedUnit( name: "knots", symbol: "knot", - dimension: [.Length: 1, .Time: -1], + dimension: [.length: 1, .time: -1], coefficient: 0.514444 ) @@ -1135,148 +1135,148 @@ enum DefaultUnits { static let liter = try! DefinedUnit( name: "liter", symbol: "L", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 0.001 ) static let milliliter = try! DefinedUnit( name: "milliliter", symbol: "mL", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 1e-6 ) static let centiliter = try! DefinedUnit( name: "centiliter", symbol: "cL", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 1e-5 ) static let deciliter = try! DefinedUnit( name: "deciliter", symbol: "dL", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 1e-4 ) static let kiloliter = try! DefinedUnit( name: "kiloliter", symbol: "kL", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 1 ) static let megaliter = try! DefinedUnit( name: "megaliter", symbol: "ML", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 1000 ) // Liquid measures static let teaspoon = try! DefinedUnit( name: "teaspoon", symbol: "tsp", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 4.92892159375e-6 ) static let tablespoon = try! DefinedUnit( name: "tablespoon", symbol: "tbsp", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 14.7867647812e-6 ) static let fluidOunce = try! DefinedUnit( name: "fluidOunce", symbol: "fl_oz", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 29.5735295625e-6 ) static let cup = try! DefinedUnit( name: "cup", symbol: "cup", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 236.5882365e-6 ) static let pint = try! DefinedUnit( name: "pint", symbol: "pt", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 473.176473e-6 ) static let quart = try! DefinedUnit( name: "quart", symbol: "qt", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 9.46352946e-4 ) static let gallon = try! DefinedUnit( name: "gallon", symbol: "gal", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 0.003785411784 ) // Dry measures: https://en.wikipedia.org/wiki/Dry_measure static let dryPint: DefinedUnit = try! DefinedUnit( name: "dryPint", symbol: "drypt", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 5.506104713575e-4 ) static let dryQuart: DefinedUnit = try! DefinedUnit( name: "dryQuart", symbol: "dryqt", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 1.101220942715e-3 ) static let peck: DefinedUnit = try! DefinedUnit( name: "peck", symbol: "pk", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 8.80976754172e-3 ) static let bushel = try! DefinedUnit( name: "bushel", symbol: "bu", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 0.035239070167 ) // Imperial measures static let imperialFluidOunce = try! DefinedUnit( name: "imperialFluidOunce", symbol: "ifl_oz", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 28.4130625e-6 ) static let imperialCup = try! DefinedUnit( name: "imperialCup", symbol: "icup", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 197.15686375e-6 ) static let imperialPint = try! DefinedUnit( name: "imperialPint", symbol: "ipt", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 568.26125e-6 ) static let imperialQuart = try! DefinedUnit( name: "imperialQuart", symbol: "iqt", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 1.1365225e-3 ) static let imperialGallon = try! DefinedUnit( name: "imperialGallon", symbol: "igal", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 0.00454609 ) static let imperialPeck: DefinedUnit = try! DefinedUnit( name: "imperialPeck", symbol: "ipk", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 9.09218e-3 ) static let metricCup = try! DefinedUnit( name: "metricCup", symbol: "mcup", - dimension: [.Length: 3], + dimension: [.length: 3], coefficient: 0.00025 ) } diff --git a/Tests/UnitsTests/MeasurementTests.swift b/Tests/UnitsTests/MeasurementTests.swift index cbb59d8..266d828 100644 --- a/Tests/UnitsTests/MeasurementTests.swift +++ b/Tests/UnitsTests/MeasurementTests.swift @@ -117,7 +117,7 @@ final class MeasurementTests: XCTestCase { let workUnit = try XCTUnwrap(work.unit) XCTAssertEqual( workUnit.dimension, - [.Mass: 1, .Length: 2, .Time: -2] + [.mass: 1, .length: 2, .time: -2] ) // Test scalar multiplication @@ -367,7 +367,7 @@ final class MeasurementTests: XCTestCase { var registry = try registryBuilder.addUnit( name: "centifoot", symbol: "cft", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 0.003048 ).registry() let centifoot = try Unit(fromSymbol: "cft", registry: registry) @@ -389,7 +389,7 @@ final class MeasurementTests: XCTestCase { registry = try registryBuilder.addUnit( name: "centiinch", symbol: "cin", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 0.000254 ).registry() let centiinch = try Unit(fromSymbol: "cin", registry: registry) @@ -406,7 +406,7 @@ final class MeasurementTests: XCTestCase { try registryBuilder.addUnit( name: "no name", symbol: "", - dimension: [.Amount: 1], + dimension: [.amount: 1], coefficient: 1 ) ) @@ -414,7 +414,7 @@ final class MeasurementTests: XCTestCase { try registryBuilder.addUnit( name: "unit with space", symbol: "unit with space", - dimension: [.Amount: 1], + dimension: [.amount: 1], coefficient: 1 ) ) @@ -422,7 +422,7 @@ final class MeasurementTests: XCTestCase { try registryBuilder.addUnit( name: "slash", symbol: "/", - dimension: [.Amount: 1], + dimension: [.amount: 1], coefficient: 1 ) ) @@ -430,7 +430,7 @@ final class MeasurementTests: XCTestCase { try registryBuilder.addUnit( name: "star", symbol: "*", - dimension: [.Amount: 1], + dimension: [.amount: 1], coefficient: 1 ) ) @@ -438,7 +438,7 @@ final class MeasurementTests: XCTestCase { try registryBuilder.addUnit( name: "carrot", symbol: "^", - dimension: [.Amount: 1], + dimension: [.amount: 1], coefficient: 1 ) ) @@ -485,7 +485,7 @@ final class MeasurementTests: XCTestCase { try registryBuilder.addUnit( name: "centiinch", symbol: "cin", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 0.000254 ) let registry = registryBuilder.registry() @@ -561,13 +561,13 @@ final class MeasurementTests: XCTestCase { try registryBuilder.addUnit( name: "apple", symbol: "apple", - dimension: [.Amount: 1], + dimension: [.amount: 1], coefficient: 1 ) try registryBuilder.addUnit( name: "carton", symbol: "carton", - dimension: [.Amount: 1], + dimension: [.amount: 1], coefficient: 48 ) var registry = registryBuilder.registry() @@ -585,7 +585,7 @@ final class MeasurementTests: XCTestCase { try registryBuilder.addUnit( name: "person", symbol: "person", - dimension: [.Amount: 1], + dimension: [.amount: 1], coefficient: 1 ) registry = registryBuilder.registry() diff --git a/Tests/UnitsTests/QuantityTests.swift b/Tests/UnitsTests/QuantityTests.swift index 217da22..f831299 100644 --- a/Tests/UnitsTests/QuantityTests.swift +++ b/Tests/UnitsTests/QuantityTests.swift @@ -10,21 +10,27 @@ private extension Quantity { class QuantityTests: XCTestCase { /// Built-in quantities keep their stable string raw values. func testBuiltInRawValues() { - XCTAssertEqual(Quantity.Length.rawValue, "Length") - XCTAssertEqual(Quantity.Time.rawValue, "Time") - XCTAssertEqual(Quantity(rawValue: "Mass"), .Mass) + XCTAssertEqual(Quantity.length.rawValue, "Length") + XCTAssertEqual(Quantity.time.rawValue, "Time") + XCTAssertEqual(Quantity(rawValue: "Mass"), .mass) + } + + /// Interpolating a Quantity yields its raw value, matching the former enum behavior. + func testDescription() { + XCTAssertEqual(Quantity.length.description, "Length") + XCTAssertEqual("\(Quantity.money)", "Money") } /// A custom dimension is distinct from every built-in one and equal to itself. func testCustomQuantityIdentity() { XCTAssertEqual(Quantity.money, Quantity(rawValue: "Money")) - XCTAssertNotEqual(Quantity.money, .Length) - XCTAssertNotEqual(Quantity.money, .Amount) + XCTAssertNotEqual(Quantity.money, .length) + XCTAssertNotEqual(Quantity.money, .amount) // Usable as a dictionary key alongside built-ins. - let dimension: [Quantity: Int] = [.money: 1, .Length: -3] + let dimension: [Quantity: Int] = [.money: 1, .length: -3] XCTAssertEqual(dimension[.money], 1) - XCTAssertEqual(dimension[.Length], -3) + XCTAssertEqual(dimension[.length], -3) } /// A unit on a custom dimension participates in dimensional arithmetic: diff --git a/Tests/UnitsTests/UnitTests.swift b/Tests/UnitsTests/UnitTests.swift index b935252..2fd9162 100644 --- a/Tests/UnitsTests/UnitTests.swift +++ b/Tests/UnitsTests/UnitTests.swift @@ -176,37 +176,37 @@ final class UnitTests: XCTestCase { func testDimension() throws { XCTAssertEqual( Unit.meter.dimension, - [.Length: 1] + [.length: 1] ) XCTAssertEqual( (Unit.meter / Unit.second).dimension, - [.Length: 1, .Time: -1] + [.length: 1, .time: -1] ) XCTAssertEqual( (Unit.meter * Unit.foot / Unit.second).dimension, - [.Length: 2, .Time: -1] + [.length: 2, .time: -1] ) XCTAssertEqual( (Unit.meter * Unit.meter / Unit.second).dimension, - [.Length: 2, .Time: -1] + [.length: 2, .time: -1] ) XCTAssertEqual( (Unit.meter / Unit.second / Unit.foot).dimension, - [.Time: -1] + [.time: -1] ) XCTAssertEqual( (Unit.meter / (Unit.second * Unit.foot)).dimension, - [.Time: -1] + [.time: -1] ) XCTAssertEqual( (Unit.meter / Unit.second.pow(2)).dimension, - [.Length: 1, .Time: -2] + [.length: 1, .time: -2] ) } From fe4ba2b318341ccf3be9caf2268d168d08ebfa69 Mon Sep 17 00:00:00 2001 From: Richard Stacpoole Date: Sat, 27 Jun 2026 15:37:35 +1000 Subject: [PATCH 3/3] Update README for new naming and document collision risk - Update the Custom Units / Custom Dimensions examples to the new lowerCamelCase dimension properties (.length, .amount) so the docs don't demonstrate the now-deprecated PascalCase names. - Spell out in the Custom Dimensions section that a Quantity is identified solely by its raw string across all linked modules, so two modules using the same raw value silently collapse into one dimension. Recommend prefixing custom raw values (e.g. "Acme.Money") to avoid collisions. Co-Authored-By: Claude Opus 4.8 --- README.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0f8e8e8..5f6aab9 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ let registryBuilder = RegistryBuilder() registryBuilder.addUnit( name: "centifoot", symbol: "cft", - dimension: [.Length: 1], + dimension: [.length: 1], coefficient: 0.003048 // This is the conversion to meters ) let registry = registryBuilder.registry() @@ -159,13 +159,13 @@ let registryBuilder = RegistryBuilder() try registryBuilder.addUnit( name: "apple", symbol: "apple", - dimension: [.Amount: 1], + dimension: [.amount: 1], coefficient: 1 ) try registryBuilder.addUnit( name: "carton", symbol: "carton", - dimension: [.Amount: 1], + dimension: [.amount: 1], coefficient: 48 ) let registry = registryBuilder.registry() @@ -183,7 +183,7 @@ We can extend this example to determine how many cartons a group of people can p try registryBuilder.addUnit( name: "person", symbol: "person", - dimension: [.Amount: 1], + dimension: [.amount: 1], coefficient: 1 ) let person = try Unit(fromSymbol: "person", registry: registryBuilder.registry()) @@ -196,14 +196,14 @@ print(weeklyCartons) // Prints '350.0 carton/week' ### Custom Dimensions -The built-in `Quantity` dimensions (`Length`, `Mass`, `Time`, etc.) cover the ISQ base +The built-in `Quantity` dimensions (`.length`, `.mass`, `.time`, etc.) cover the ISQ base quantities, but you can define your own dimension when none of them fit — for example, a `Money` dimension for cost calculations. Because `Quantity` is `RawRepresentable`, you can add one with a static extension: ```swift extension Quantity { - static let money = Quantity(rawValue: "Money") + static let money = Quantity(rawValue: "Acme.Money") } ``` @@ -223,8 +223,16 @@ let volume = Measurement(value: 24, unit: .meter.pow(3)) // 24 m^3 print(rate * volume) // Prints '4320.0 $' ``` -Unlike repurposing an unrelated base quantity such as `Amount`, a dedicated dimension can't -silently cancel against unrelated units. Use a unique `rawValue` for each distinct dimension. +A dedicated dimension is safer than repurposing an unrelated base quantity such as `.amount`, +which would silently cancel against unrelated units that share it. But the `rawValue` namespace +is **global**: a `Quantity` is identified solely by its raw string, across every module linked +into the program. If two modules each define a dimension with the same raw value — say both use +`"Money"` — they are treated as *the same dimension* and will silently cancel against each other, +producing wrong results with no error. There is no central registry to detect the clash. + +To avoid this, namespace every custom `rawValue` with a prefix unique to your module or +organization (e.g. `"Acme.Money"`, not `"Money"`), exactly as the example above does. Reserve +bare names like `"Money"` for nothing, since you cannot know what another dependency has chosen. ## CLI