diff --git a/README.md b/README.md index 42ce9cd..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()) @@ -194,6 +194,46 @@ 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: "Acme.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 $' +``` + +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 The easiest way to install the CLI is with brew: diff --git a/Sources/Units/Quantity.swift b/Sources/Units/Quantity.swift index 0674a91..a11b31c 100644 --- a/Sources/Units/Quantity.swift +++ b/Sources/Units/Quantity.swift @@ -1,17 +1,70 @@ -/// 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") +/// } +/// ``` +/// +/// 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 + + 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") +} + +// 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 new file mode 100644 index 0000000..f831299 --- /dev/null +++ b/Tests/UnitsTests/QuantityTests.swift @@ -0,0 +1,69 @@ +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) + } + + /// 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) + + // 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") + } +} 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] ) }