1use crate::dimension::Dimension;
18use crate::numeric::Rational;
19
20#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct Unit {
24 pub symbol: &'static str,
25 pub dimension: Dimension,
26 pub scale: Rational,
28 pub offset: Rational,
30}
31
32impl Unit {
33 pub fn linear(symbol: &'static str, dimension: Dimension, scale: Rational) -> Unit {
35 Unit { symbol, dimension, scale, offset: Rational::zero() }
36 }
37
38 pub fn affine(symbol: &'static str, dimension: Dimension, scale: Rational, offset: Rational) -> Unit {
40 Unit { symbol, dimension, scale, offset }
41 }
42}
43
44#[derive(Clone, Debug, PartialEq, Eq)]
46pub struct Quantity {
47 magnitude: Rational,
49 dimension: Dimension,
50}
51
52impl Quantity {
53 pub fn of(value: Rational, unit: &Unit) -> Quantity {
55 Quantity {
56 magnitude: value.mul(&unit.scale).add(&unit.offset),
57 dimension: unit.dimension,
58 }
59 }
60
61 pub fn scalar(value: Rational) -> Quantity {
63 Quantity { magnitude: value, dimension: Dimension::DIMENSIONLESS }
64 }
65
66 pub fn si(magnitude: Rational, dimension: Dimension) -> Quantity {
69 Quantity { magnitude, dimension }
70 }
71
72 pub fn magnitude_si(&self) -> &Rational {
74 &self.magnitude
75 }
76
77 pub fn dimension(&self) -> Dimension {
78 self.dimension
79 }
80
81 pub fn in_unit(&self, unit: &Unit) -> Option<Rational> {
84 if self.dimension != unit.dimension {
85 return None;
86 }
87 self.magnitude.sub(&unit.offset).div(&unit.scale)
88 }
89
90 pub fn add(&self, other: &Quantity) -> Option<Quantity> {
92 if self.dimension != other.dimension {
93 return None;
94 }
95 Some(Quantity { magnitude: self.magnitude.add(&other.magnitude), dimension: self.dimension })
96 }
97
98 pub fn sub(&self, other: &Quantity) -> Option<Quantity> {
100 if self.dimension != other.dimension {
101 return None;
102 }
103 Some(Quantity { magnitude: self.magnitude.sub(&other.magnitude), dimension: self.dimension })
104 }
105
106 pub fn mul(&self, other: &Quantity) -> Quantity {
108 Quantity {
109 magnitude: self.magnitude.mul(&other.magnitude),
110 dimension: self.dimension.mul(other.dimension),
111 }
112 }
113
114 pub fn div(&self, other: &Quantity) -> Option<Quantity> {
116 Some(Quantity {
117 magnitude: self.magnitude.div(&other.magnitude)?,
118 dimension: self.dimension.div(other.dimension),
119 })
120 }
121
122 pub fn in_best_unit<'a>(&self, ladder: &'a [Unit]) -> Option<(Rational, &'a Unit)> {
130 let one = Rational::one();
131 let mut best: Option<(Rational, &Unit)> = None;
132 for u in ladder {
133 let Some(mag) = self.in_unit(u) else { continue };
134 best = Some(match best {
135 None => (mag, u),
136 Some((best_mag, best_u)) => {
137 let (abs, best_abs) = (mag.abs(), best_mag.abs());
138 let take = match (abs >= one, best_abs >= one) {
139 (true, true) => abs < best_abs, (true, false) => true, (false, true) => false, (false, false) => abs > best_abs, };
144 if take { (mag, u) } else { (best_mag, best_u) }
145 }
146 });
147 }
148 best
149 }
150}
151
152pub mod units {
157 use super::Unit;
158 use crate::dimension::Dimension;
159 use crate::numeric::Rational;
160
161 fn r(n: i64, d: i64) -> Rational {
162 Rational::from_ratio_i64(n, d).expect("nonzero denominator")
163 }
164 fn i(n: i64) -> Rational {
165 Rational::from_i64(n)
166 }
167 fn cube(x: Rational) -> Rational {
169 x.pow(3).expect("cube exists")
170 }
171 fn square(x: Rational) -> Rational {
173 x.pow(2).expect("square exists")
174 }
175
176 fn inch_m() -> Rational { r(127, 5000) } fn foot_m() -> Rational { r(381, 1250) } fn lb_kg() -> Rational { r(45_359_237, 100_000_000) } fn g0() -> Rational { r(980_665, 100_000) } fn us_gallon_m3() -> Rational { cube(inch_m()).mul(&i(231)) }
183 fn imp_gallon_m3() -> Rational { r(454_609, 100_000_000) }
185
186 pub fn metre() -> Unit { Unit::linear("m", Dimension::length(), Rational::one()) }
188 pub fn kilometre() -> Unit { Unit::linear("km", Dimension::length(), i(1000)) }
189 pub fn hectometre() -> Unit { Unit::linear("hm", Dimension::length(), i(100)) }
190 pub fn dekametre() -> Unit { Unit::linear("dam", Dimension::length(), i(10)) }
191 pub fn decimetre() -> Unit { Unit::linear("dm", Dimension::length(), r(1, 10)) }
192 pub fn centimetre() -> Unit { Unit::linear("cm", Dimension::length(), r(1, 100)) }
193 pub fn millimetre() -> Unit { Unit::linear("mm", Dimension::length(), r(1, 1000)) }
194 pub fn micrometre() -> Unit { Unit::linear("µm", Dimension::length(), r(1, 1_000_000)) }
195 pub fn nanometre() -> Unit { Unit::linear("nm", Dimension::length(), r(1, 1_000_000_000)) }
196 pub fn angstrom() -> Unit { Unit::linear("Å", Dimension::length(), r(1, 10_000_000_000)) }
197 pub fn inch() -> Unit { Unit::linear("in", Dimension::length(), inch_m()) }
198 pub fn foot() -> Unit { Unit::linear("ft", Dimension::length(), foot_m()) }
199 pub fn yard() -> Unit { Unit::linear("yd", Dimension::length(), r(1143, 1250)) } pub fn mile() -> Unit { Unit::linear("mi", Dimension::length(), r(201_168, 125)) } pub fn nautical_mile() -> Unit { Unit::linear("nmi", Dimension::length(), i(1852)) }
202 pub fn fathom() -> Unit { Unit::linear("ftm", Dimension::length(), r(1143, 625)) } pub fn furlong() -> Unit { Unit::linear("fur", Dimension::length(), r(25_146, 125)) } pub fn chain() -> Unit { Unit::linear("ch", Dimension::length(), r(12_573, 625)) } pub fn rod() -> Unit { Unit::linear("rod", Dimension::length(), r(12_573, 2500)) } pub fn hand() -> Unit { Unit::linear("hand", Dimension::length(), r(127, 1250)) } pub fn point() -> Unit { Unit::linear("pt", Dimension::length(), r(127, 360_000)) } pub fn pica() -> Unit { Unit::linear("pica", Dimension::length(), r(127, 30_000)) } pub fn thou() -> Unit { Unit::linear("thou", Dimension::length(), r(127, 5_000_000)) } pub fn astronomical_unit() -> Unit { Unit::linear("AU", Dimension::length(), i(149_597_870_700)) }
211 pub fn light_year() -> Unit { Unit::linear("ly", Dimension::length(), i(9_460_730_472_580_800)) } pub fn kilogram() -> Unit { Unit::linear("kg", Dimension::mass(), Rational::one()) }
215 pub fn gram() -> Unit { Unit::linear("g", Dimension::mass(), r(1, 1000)) }
216 pub fn milligram() -> Unit { Unit::linear("mg", Dimension::mass(), r(1, 1_000_000)) }
217 pub fn microgram() -> Unit { Unit::linear("µg", Dimension::mass(), r(1, 1_000_000_000)) }
218 pub fn tonne() -> Unit { Unit::linear("t", Dimension::mass(), i(1000)) }
219 pub fn pound() -> Unit { Unit::linear("lb", Dimension::mass(), lb_kg()) }
220 pub fn ounce() -> Unit { Unit::linear("oz", Dimension::mass(), lb_kg().div(&i(16)).unwrap()) }
221 pub fn stone() -> Unit { Unit::linear("st", Dimension::mass(), lb_kg().mul(&i(14))) }
222 pub fn short_ton() -> Unit { Unit::linear("ton", Dimension::mass(), lb_kg().mul(&i(2000))) }
223 pub fn long_ton() -> Unit { Unit::linear("LT", Dimension::mass(), lb_kg().mul(&i(2240))) }
224 pub fn hundredweight() -> Unit { Unit::linear("cwt", Dimension::mass(), lb_kg().mul(&i(100))) }
225 pub fn grain() -> Unit { Unit::linear("gr", Dimension::mass(), lb_kg().div(&i(7000)).unwrap()) }
226 pub fn dram() -> Unit { Unit::linear("dr", Dimension::mass(), lb_kg().div(&i(256)).unwrap()) } pub fn carat() -> Unit { Unit::linear("ct", Dimension::mass(), r(1, 5000)) } pub fn troy_ounce() -> Unit { Unit::linear("ozt", Dimension::mass(), r(311_034_768, 10_000_000_000)) } pub fn troy_pound() -> Unit { Unit::linear("lbt", Dimension::mass(), r(311_034_768, 10_000_000_000).mul(&i(12))) }
230
231 pub fn second() -> Unit { Unit::linear("s", Dimension::time(), Rational::one()) }
233 pub fn millisecond() -> Unit { Unit::linear("ms", Dimension::time(), r(1, 1000)) }
234 pub fn microsecond() -> Unit { Unit::linear("µs", Dimension::time(), r(1, 1_000_000)) }
235 pub fn nanosecond() -> Unit { Unit::linear("ns", Dimension::time(), r(1, 1_000_000_000)) }
236 pub fn minute() -> Unit { Unit::linear("min", Dimension::time(), i(60)) }
237 pub fn hour() -> Unit { Unit::linear("h", Dimension::time(), i(3600)) }
238 pub fn day() -> Unit { Unit::linear("day", Dimension::time(), i(86_400)) }
239 pub fn week() -> Unit { Unit::linear("wk", Dimension::time(), i(604_800)) }
240 pub fn fortnight() -> Unit { Unit::linear("fortnight", Dimension::time(), i(1_209_600)) }
241 pub fn julian_year() -> Unit { Unit::linear("yr", Dimension::time(), i(31_557_600)) } pub fn decade() -> Unit { Unit::linear("decade", Dimension::time(), i(315_576_000)) }
243 pub fn century() -> Unit { Unit::linear("century", Dimension::time(), i(3_155_760_000)) }
244
245 pub fn kelvin() -> Unit { Unit::affine("K", Dimension::temperature(), Rational::one(), Rational::zero()) }
247 pub fn celsius() -> Unit { Unit::affine("°C", Dimension::temperature(), Rational::one(), r(5463, 20)) } pub fn fahrenheit() -> Unit { Unit::affine("°F", Dimension::temperature(), r(5, 9), r(45967, 180)) }
249 pub fn rankine() -> Unit { Unit::affine("°R", Dimension::temperature(), r(5, 9), Rational::zero()) } pub fn reaumur() -> Unit { Unit::affine("°Ré", Dimension::temperature(), r(5, 4), r(5463, 20)) } pub fn cubic_metre() -> Unit { Unit::linear("m³", Dimension::volume(), Rational::one()) }
254 pub fn litre() -> Unit { Unit::linear("L", Dimension::volume(), r(1, 1000)) }
255 pub fn millilitre() -> Unit { Unit::linear("mL", Dimension::volume(), r(1, 1_000_000)) }
256 pub fn centilitre() -> Unit { Unit::linear("cL", Dimension::volume(), r(1, 100_000)) }
257 pub fn decilitre() -> Unit { Unit::linear("dL", Dimension::volume(), r(1, 10_000)) }
258 pub fn cubic_centimetre() -> Unit { Unit::linear("cc", Dimension::volume(), r(1, 1_000_000)) }
259 pub fn cubic_inch() -> Unit { Unit::linear("in³", Dimension::volume(), cube(inch_m())) }
260 pub fn cubic_foot() -> Unit { Unit::linear("ft³", Dimension::volume(), cube(foot_m())) }
261 pub fn us_gallon() -> Unit { Unit::linear("gal", Dimension::volume(), us_gallon_m3()) }
263 pub fn us_quart() -> Unit { Unit::linear("qt", Dimension::volume(), us_gallon_m3().div(&i(4)).unwrap()) }
264 pub fn us_pint() -> Unit { Unit::linear("pt", Dimension::volume(), us_gallon_m3().div(&i(8)).unwrap()) }
265 pub fn us_cup() -> Unit { Unit::linear("cup", Dimension::volume(), us_gallon_m3().div(&i(16)).unwrap()) }
266 pub fn us_gill() -> Unit { Unit::linear("gill", Dimension::volume(), us_gallon_m3().div(&i(32)).unwrap()) }
267 pub fn us_fluid_ounce() -> Unit { Unit::linear("fl oz", Dimension::volume(), us_gallon_m3().div(&i(128)).unwrap()) }
268 pub fn tablespoon() -> Unit { Unit::linear("tbsp", Dimension::volume(), us_gallon_m3().div(&i(256)).unwrap()) }
269 pub fn teaspoon() -> Unit { Unit::linear("tsp", Dimension::volume(), us_gallon_m3().div(&i(768)).unwrap()) }
270 pub fn oil_barrel() -> Unit { Unit::linear("bbl", Dimension::volume(), us_gallon_m3().mul(&i(42))) } pub fn imperial_gallon() -> Unit { Unit::linear("imp gal", Dimension::volume(), imp_gallon_m3()) }
273 pub fn imperial_pint() -> Unit { Unit::linear("imp pt", Dimension::volume(), imp_gallon_m3().div(&i(8)).unwrap()) }
274 pub fn imperial_fluid_ounce() -> Unit { Unit::linear("imp fl oz", Dimension::volume(), imp_gallon_m3().div(&i(160)).unwrap()) }
275
276 pub fn square_metre() -> Unit { Unit::linear("m²", Dimension::area(), Rational::one()) }
278 pub fn square_kilometre() -> Unit { Unit::linear("km²", Dimension::area(), i(1_000_000)) }
279 pub fn square_centimetre() -> Unit { Unit::linear("cm²", Dimension::area(), r(1, 10_000)) }
280 pub fn square_inch() -> Unit { Unit::linear("in²", Dimension::area(), square(inch_m())) }
281 pub fn square_foot() -> Unit { Unit::linear("ft²", Dimension::area(), square(foot_m())) }
282 pub fn square_yard() -> Unit { Unit::linear("yd²", Dimension::area(), square(r(1143, 1250))) }
283 pub fn square_mile() -> Unit { Unit::linear("mi²", Dimension::area(), square(r(201_168, 125))) }
284 pub fn hectare() -> Unit { Unit::linear("ha", Dimension::area(), i(10_000)) }
285 pub fn are() -> Unit { Unit::linear("a", Dimension::area(), i(100)) }
286 pub fn acre() -> Unit { Unit::linear("ac", Dimension::area(), square(foot_m()).mul(&i(43_560))) } pub fn metre_per_second() -> Unit { Unit::linear("m/s", Dimension::speed(), Rational::one()) }
290 pub fn kilometre_per_hour() -> Unit { Unit::linear("km/h", Dimension::speed(), r(5, 18)) } pub fn mile_per_hour() -> Unit { Unit::linear("mph", Dimension::speed(), r(201_168, 125).div(&i(3600)).unwrap()) }
292 pub fn foot_per_second() -> Unit { Unit::linear("ft/s", Dimension::speed(), foot_m()) }
293 pub fn knot() -> Unit { Unit::linear("kn", Dimension::speed(), r(1852, 3600)) } pub fn hertz() -> Unit { Unit::linear("Hz", Dimension::frequency(), Rational::one()) }
297 pub fn kilohertz() -> Unit { Unit::linear("kHz", Dimension::frequency(), i(1000)) }
298 pub fn megahertz() -> Unit { Unit::linear("MHz", Dimension::frequency(), i(1_000_000)) }
299 pub fn gigahertz() -> Unit { Unit::linear("GHz", Dimension::frequency(), i(1_000_000_000)) }
300 pub fn rpm() -> Unit { Unit::linear("rpm", Dimension::frequency(), r(1, 60)) }
301
302 pub fn newton() -> Unit { Unit::linear("N", Dimension::force(), Rational::one()) }
304 pub fn kilonewton() -> Unit { Unit::linear("kN", Dimension::force(), i(1000)) }
305 pub fn dyne() -> Unit { Unit::linear("dyn", Dimension::force(), r(1, 100_000)) }
306 pub fn kilogram_force() -> Unit { Unit::linear("kgf", Dimension::force(), g0()) }
307 pub fn pound_force() -> Unit { Unit::linear("lbf", Dimension::force(), lb_kg().mul(&g0())) }
308
309 pub fn joule() -> Unit { Unit::linear("J", Dimension::energy(), Rational::one()) }
311 pub fn kilojoule() -> Unit { Unit::linear("kJ", Dimension::energy(), i(1000)) }
312 pub fn megajoule() -> Unit { Unit::linear("MJ", Dimension::energy(), i(1_000_000)) }
313 pub fn calorie() -> Unit { Unit::linear("cal", Dimension::energy(), r(523, 125)) } pub fn kilocalorie() -> Unit { Unit::linear("kcal", Dimension::energy(), r(4184, 1)) }
315 pub fn watt_hour() -> Unit { Unit::linear("Wh", Dimension::energy(), i(3600)) }
316 pub fn kilowatt_hour() -> Unit { Unit::linear("kWh", Dimension::energy(), i(3_600_000)) }
317 pub fn erg() -> Unit { Unit::linear("erg", Dimension::energy(), r(1, 10_000_000)) }
318 pub fn electronvolt() -> Unit {
319 use crate::numeric::BigInt;
322 let scale = Rational::new(BigInt::from_i64(1_602_176_634), BigInt::from_i64(10).pow(28))
323 .expect("nonzero denominator");
324 Unit::linear("eV", Dimension::energy(), scale)
325 }
326
327 pub fn watt() -> Unit { Unit::linear("W", Dimension::power(), Rational::one()) }
329 pub fn kilowatt() -> Unit { Unit::linear("kW", Dimension::power(), i(1000)) }
330 pub fn megawatt() -> Unit { Unit::linear("MW", Dimension::power(), i(1_000_000)) }
331 pub fn horsepower() -> Unit { Unit::linear("hp", Dimension::power(), lb_kg().mul(&g0()).mul(&foot_m()).mul(&i(550))) } pub fn pascal() -> Unit { Unit::linear("Pa", Dimension::pressure(), Rational::one()) }
335 pub fn kilopascal() -> Unit { Unit::linear("kPa", Dimension::pressure(), i(1000)) }
336 pub fn bar() -> Unit { Unit::linear("bar", Dimension::pressure(), i(100_000)) }
337 pub fn millibar() -> Unit { Unit::linear("mbar", Dimension::pressure(), i(100)) }
338 pub fn atmosphere() -> Unit { Unit::linear("atm", Dimension::pressure(), i(101_325)) }
339 pub fn psi() -> Unit { Unit::linear("psi", Dimension::pressure(), lb_kg().mul(&g0()).div(&square(inch_m())).unwrap()) } pub fn bit() -> Unit { Unit::linear("bit", Dimension::information(), Rational::one()) }
343 pub fn byte() -> Unit { Unit::linear("B", Dimension::information(), i(8)) }
344 pub fn kilobit() -> Unit { Unit::linear("kbit", Dimension::information(), i(1000)) }
345 pub fn kilobyte() -> Unit { Unit::linear("kB", Dimension::information(), i(8000)) }
346 pub fn megabyte() -> Unit { Unit::linear("MB", Dimension::information(), i(8_000_000)) }
347 pub fn gigabyte() -> Unit { Unit::linear("GB", Dimension::information(), i(8_000_000_000)) }
348 pub fn kibibyte() -> Unit { Unit::linear("KiB", Dimension::information(), i(8 * 1024)) }
349 pub fn mebibyte() -> Unit { Unit::linear("MiB", Dimension::information(), i(8 * 1024 * 1024)) }
350 pub fn gibibyte() -> Unit { Unit::linear("GiB", Dimension::information(), i(8 * 1024 * 1024 * 1024)) }
351
352 pub fn turn() -> Unit { Unit::linear("turn", Dimension::angle(), Rational::one()) }
356 pub fn degree() -> Unit { Unit::linear("°", Dimension::angle(), r(1, 360)) }
357 pub fn gradian() -> Unit { Unit::linear("grad", Dimension::angle(), r(1, 400)) }
358 pub fn arcminute() -> Unit { Unit::linear("′", Dimension::angle(), r(1, 21_600)) } pub fn arcsecond() -> Unit { Unit::linear("″", Dimension::angle(), r(1, 1_296_000)) } pub fn ampere() -> Unit { Unit::linear("A", Dimension::current(), Rational::one()) }
363 pub fn milliampere() -> Unit { Unit::linear("mA", Dimension::current(), r(1, 1000)) }
364 pub fn coulomb() -> Unit { Unit::linear("C", Dimension::charge(), Rational::one()) }
365 pub fn ampere_hour() -> Unit { Unit::linear("Ah", Dimension::charge(), i(3600)) }
366 pub fn milliampere_hour() -> Unit { Unit::linear("mAh", Dimension::charge(), r(18, 5)) } pub fn volt() -> Unit { Unit::linear("V", Dimension::voltage(), Rational::one()) }
368 pub fn millivolt() -> Unit { Unit::linear("mV", Dimension::voltage(), r(1, 1000)) }
369 pub fn kilovolt() -> Unit { Unit::linear("kV", Dimension::voltage(), i(1000)) }
370 pub fn ohm() -> Unit { Unit::linear("Ω", Dimension::resistance(), Rational::one()) }
371
372 pub fn mole() -> Unit { Unit::linear("mol", Dimension::amount(), Rational::one()) }
374 pub fn candela() -> Unit { Unit::linear("cd", Dimension::luminous(), Rational::one()) }
375
376 fn e(mantissa: i64, pow10: u32) -> Rational {
378 use crate::numeric::BigInt;
379 Rational::from_bigint(BigInt::from_i64(mantissa).mul(&BigInt::from_i64(10).pow(pow10)))
380 }
381
382 pub fn stick_of_butter() -> Unit { Unit::linear("stick", Dimension::volume(), us_gallon_m3().div(&i(32)).unwrap()) } pub fn dash() -> Unit { Unit::linear("dash", Dimension::volume(), us_gallon_m3().div(&i(6144)).unwrap()) } pub fn pinch() -> Unit { Unit::linear("pinch", Dimension::volume(), us_gallon_m3().div(&i(12288)).unwrap()) } pub fn smidgen() -> Unit { Unit::linear("smidgen", Dimension::volume(), us_gallon_m3().div(&i(24576)).unwrap()) } fn us_bushel_m3() -> Rational { cube(inch_m()).mul(&r(215_042, 100)) } pub fn bushel() -> Unit { Unit::linear("bu", Dimension::volume(), us_bushel_m3()) }
390 pub fn peck() -> Unit { Unit::linear("pk", Dimension::volume(), us_bushel_m3().div(&i(4)).unwrap()) }
391 pub fn dry_gallon() -> Unit { Unit::linear("dry gal", Dimension::volume(), us_bushel_m3().div(&i(8)).unwrap()) }
392 pub fn dry_quart() -> Unit { Unit::linear("dry qt", Dimension::volume(), us_bushel_m3().div(&i(32)).unwrap()) }
393 pub fn dry_pint() -> Unit { Unit::linear("dry pt", Dimension::volume(), us_bushel_m3().div(&i(64)).unwrap()) }
394
395 pub fn cable() -> Unit { Unit::linear("cable", Dimension::length(), r(926, 5)) } pub fn league() -> Unit { Unit::linear("lea", Dimension::length(), r(603_504, 125)) } pub fn nautical_league() -> Unit { Unit::linear("nl", Dimension::length(), i(5556)) } pub fn light_second() -> Unit { Unit::linear("ls", Dimension::length(), i(299_792_458)) } pub fn light_minute() -> Unit { Unit::linear("lmin", Dimension::length(), i(299_792_458 * 60)) }
403 pub fn light_hour() -> Unit { Unit::linear("lh", Dimension::length(), i(299_792_458 * 3600)) }
404 pub fn light_day() -> Unit { Unit::linear("ld", Dimension::length(), i(299_792_458 * 86_400)) }
405 pub fn lunar_distance() -> Unit { Unit::linear("LD", Dimension::length(), i(384_399_000)) } pub fn solar_radius() -> Unit { Unit::linear("R☉", Dimension::length(), i(696_340_000)) }
407 pub fn earth_radius() -> Unit { Unit::linear("R⊕", Dimension::length(), i(6_371_000)) } pub fn solar_mass() -> Unit { Unit::linear("M☉", Dimension::mass(), e(198_892, 25)) } pub fn earth_mass() -> Unit { Unit::linear("M⊕", Dimension::mass(), e(59_722, 20)) } pub fn jupiter_mass() -> Unit { Unit::linear("M♃", Dimension::mass(), e(18_982, 23)) } pub fn gray() -> Unit { Unit::linear("Gy", Dimension::absorbed_dose(), Rational::one()) } pub fn sievert() -> Unit { Unit::linear("Sv", Dimension::absorbed_dose(), Rational::one()) } pub fn rad_unit() -> Unit { Unit::linear("rad", Dimension::absorbed_dose(), r(1, 100)) } pub fn rem() -> Unit { Unit::linear("rem", Dimension::absorbed_dose(), r(1, 100)) } pub fn becquerel() -> Unit { Unit::linear("Bq", Dimension::radioactivity(), Rational::one()) } pub fn curie() -> Unit { Unit::linear("Ci", Dimension::radioactivity(), i(37_000_000_000)) } pub fn roentgen() -> Unit { Unit::linear("R", Dimension::exposure(), r(129, 500_000)) } pub fn katal() -> Unit { Unit::linear("kat", Dimension::catalytic_activity(), Rational::one()) } pub fn lumen() -> Unit { Unit::linear("lm", Dimension::luminous_flux(), Rational::one()) } pub fn lux() -> Unit { Unit::linear("lx", Dimension::illuminance(), Rational::one()) } pub fn phot() -> Unit { Unit::linear("ph", Dimension::illuminance(), i(10_000)) } pub fn foot_candle() -> Unit { Unit::linear("fc", Dimension::illuminance(), square(r(1250, 381))) } pub fn nit() -> Unit { Unit::linear("nit", Dimension::luminance(), Rational::one()) } pub fn bit_per_second() -> Unit { Unit::linear("bit/s", Dimension::data_rate(), Rational::one()) }
432 pub fn kilobit_per_second() -> Unit { Unit::linear("kbps", Dimension::data_rate(), i(1000)) }
433 pub fn megabit_per_second() -> Unit { Unit::linear("Mbps", Dimension::data_rate(), i(1_000_000)) }
434 pub fn gigabit_per_second() -> Unit { Unit::linear("Gbps", Dimension::data_rate(), i(1_000_000_000)) }
435 pub fn byte_per_second() -> Unit { Unit::linear("B/s", Dimension::data_rate(), i(8)) }
436 pub fn megabyte_per_second() -> Unit { Unit::linear("MB/s", Dimension::data_rate(), i(8_000_000)) }
437
438 pub fn cubic_metre_per_second() -> Unit { Unit::linear("m³/s", Dimension::volumetric_flow(), Rational::one()) }
440 pub fn litre_per_second() -> Unit { Unit::linear("L/s", Dimension::volumetric_flow(), r(1, 1000)) }
441 pub fn litre_per_minute() -> Unit { Unit::linear("L/min", Dimension::volumetric_flow(), r(1, 60_000)) }
442 pub fn gallon_per_minute() -> Unit { Unit::linear("gpm", Dimension::volumetric_flow(), us_gallon_m3().div(&i(60)).unwrap()) }
443 pub fn cubic_foot_per_minute() -> Unit { Unit::linear("cfm", Dimension::volumetric_flow(), cube(foot_m()).div(&i(60)).unwrap()) }
444
445 pub fn molar() -> Unit { Unit::linear("M", Dimension::molar_concentration(), i(1000)) } pub fn millimolar() -> Unit { Unit::linear("mM", Dimension::molar_concentration(), Rational::one()) }
448 pub fn micromolar() -> Unit { Unit::linear("µM", Dimension::molar_concentration(), r(1, 1000)) }
449 pub fn molal() -> Unit { Unit::linear("m", Dimension::molality(), Rational::one()) } pub fn pascal_second() -> Unit { Unit::linear("Pa·s", Dimension::dynamic_viscosity(), Rational::one()) }
453 pub fn poise() -> Unit { Unit::linear("P", Dimension::dynamic_viscosity(), r(1, 10)) }
454 pub fn centipoise() -> Unit { Unit::linear("cP", Dimension::dynamic_viscosity(), r(1, 1000)) }
455 pub fn square_metre_per_second() -> Unit { Unit::linear("m²/s", Dimension::kinematic_viscosity(), Rational::one()) }
456 pub fn stokes() -> Unit { Unit::linear("St", Dimension::kinematic_viscosity(), r(1, 10_000)) }
457 pub fn centistokes() -> Unit { Unit::linear("cSt", Dimension::kinematic_viscosity(), r(1, 1_000_000)) }
458
459 pub fn mile_per_gallon() -> Unit { Unit::linear("mpg", Dimension::fuel_economy(), r(201_168, 125).div(&us_gallon_m3()).unwrap()) }
461 pub fn km_per_litre() -> Unit { Unit::linear("km/L", Dimension::fuel_economy(), i(1000).div(&r(1, 1000)).unwrap()) } pub fn newton_metre() -> Unit { Unit::linear("N·m", Dimension::energy(), Rational::one()) }
465 pub fn pound_foot() -> Unit { Unit::linear("lb·ft", Dimension::energy(), lb_kg().mul(&g0()).mul(&foot_m())) }
466
467 pub fn megametre() -> Unit { Unit::linear("Mm", Dimension::length(), i(1_000_000)) }
469 pub fn gigametre() -> Unit { Unit::linear("Gm", Dimension::length(), i(1_000_000_000)) }
470 pub fn picometre() -> Unit { Unit::linear("pm", Dimension::length(), r(1, 1_000_000_000_000)) }
471 pub fn femtometre() -> Unit { Unit::linear("fm", Dimension::length(), r(1, 1_000_000_000_000_000)) }
472 pub fn nanogram() -> Unit { Unit::linear("ng", Dimension::mass(), r(1, 1_000_000_000_000)) }
473 pub fn picosecond() -> Unit { Unit::linear("ps", Dimension::time(), r(1, 1_000_000_000_000)) }
474 pub fn gigawatt() -> Unit { Unit::linear("GW", Dimension::power(), i(1_000_000_000)) }
475 pub fn terawatt() -> Unit { Unit::linear("TW", Dimension::power(), i(1_000_000_000_000)) }
476 pub fn gigajoule() -> Unit { Unit::linear("GJ", Dimension::energy(), i(1_000_000_000)) }
477 pub fn terajoule() -> Unit { Unit::linear("TJ", Dimension::energy(), i(1_000_000_000_000)) }
478 pub fn terabit() -> Unit { Unit::linear("Tbit", Dimension::information(), i(1_000_000_000_000)) }
479 pub fn terabyte() -> Unit { Unit::linear("TB", Dimension::information(), i(8_000_000_000_000)) }
480 pub fn petabyte() -> Unit { Unit::linear("PB", Dimension::information(), i(8_000_000_000_000_000)) }
481
482 pub fn siemens() -> Unit { Unit::linear("S", Dimension::conductance(), Rational::one()) }
484 pub fn millisiemens() -> Unit { Unit::linear("mS", Dimension::conductance(), r(1, 1000)) }
485 pub fn farad() -> Unit { Unit::linear("F", Dimension::capacitance(), Rational::one()) }
486 pub fn microfarad() -> Unit { Unit::linear("µF", Dimension::capacitance(), r(1, 1_000_000)) }
487 pub fn nanofarad() -> Unit { Unit::linear("nF", Dimension::capacitance(), r(1, 1_000_000_000)) }
488 pub fn picofarad() -> Unit { Unit::linear("pF", Dimension::capacitance(), r(1, 1_000_000_000_000)) }
489 pub fn weber() -> Unit { Unit::linear("Wb", Dimension::magnetic_flux(), Rational::one()) }
490 pub fn maxwell() -> Unit { Unit::linear("Mx", Dimension::magnetic_flux(), r(1, 100_000_000)) } pub fn henry() -> Unit { Unit::linear("H", Dimension::inductance(), Rational::one()) }
492 pub fn millihenry() -> Unit { Unit::linear("mH", Dimension::inductance(), r(1, 1000)) }
493 pub fn microhenry() -> Unit { Unit::linear("µH", Dimension::inductance(), r(1, 1_000_000)) }
494 pub fn tesla() -> Unit { Unit::linear("T", Dimension::magnetic_flux_density(), Rational::one()) }
495 pub fn millitesla() -> Unit { Unit::linear("mT", Dimension::magnetic_flux_density(), r(1, 1000)) }
496 pub fn gauss() -> Unit { Unit::linear("G", Dimension::magnetic_flux_density(), r(1, 10_000)) } pub fn newton_per_metre() -> Unit { Unit::linear("N/m", Dimension::surface_tension(), Rational::one()) }
500 pub fn dyne_per_centimetre() -> Unit { Unit::linear("dyn/cm", Dimension::surface_tension(), r(1, 1000)) } pub fn each() -> Unit { Unit::linear("ea", Dimension::DIMENSIONLESS, Rational::one()) }
505 pub fn pair() -> Unit { Unit::linear("pair", Dimension::DIMENSIONLESS, i(2)) }
506 pub fn dozen() -> Unit { Unit::linear("dz", Dimension::DIMENSIONLESS, i(12)) }
507 pub fn baker_dozen() -> Unit { Unit::linear("baker's dz", Dimension::DIMENSIONLESS, i(13)) }
508 pub fn score() -> Unit { Unit::linear("score", Dimension::DIMENSIONLESS, i(20)) }
509 pub fn gross() -> Unit { Unit::linear("gross", Dimension::DIMENSIONLESS, i(144)) }
510 pub fn great_gross() -> Unit { Unit::linear("great gross", Dimension::DIMENSIONLESS, i(1728)) }
511 pub fn ream() -> Unit { Unit::linear("ream", Dimension::DIMENSIONLESS, i(500)) }
512 pub fn percent() -> Unit { Unit::linear("%", Dimension::DIMENSIONLESS, r(1, 100)) }
513 pub fn permille() -> Unit { Unit::linear("‰", Dimension::DIMENSIONLESS, r(1, 1000)) }
514 pub fn ppm() -> Unit { Unit::linear("ppm", Dimension::DIMENSIONLESS, r(1, 1_000_000)) }
515 pub fn ppb() -> Unit { Unit::linear("ppb", Dimension::DIMENSIONLESS, r(1, 1_000_000_000)) }
516 pub fn basis_point() -> Unit { Unit::linear("bp", Dimension::DIMENSIONLESS, r(1, 10_000)) }
517
518 pub fn by_name(name: &str) -> Option<Unit> {
523 let n = name.trim().to_ascii_lowercase();
524 let u = match n.as_str() {
525 "meter" | "metre" | "meters" | "metres" | "m" => metre(),
527 "kilometer" | "kilometre" | "kilometers" | "kilometres" | "km" => kilometre(),
528 "centimeter" | "centimetre" | "centimeters" | "centimetres" | "cm" => centimetre(),
529 "millimeter" | "millimetre" | "millimeters" | "millimetres" | "mm" => millimetre(),
530 "micrometer" | "micrometre" | "micron" | "microns" | "µm" | "um" => micrometre(),
531 "nanometer" | "nanometre" | "nanometers" | "nanometres" | "nm" => nanometre(),
532 "inch" | "inches" | "in" => inch(),
533 "foot" | "feet" | "ft" => foot(),
534 "yard" | "yards" | "yd" => yard(),
535 "mile" | "miles" | "mi" => mile(),
536 "nautical mile" | "nautical miles" | "nmi" => nautical_mile(),
537 "angstrom" | "angstroms" | "ångström" | "å" => angstrom(),
538 "light year" | "light years" | "lightyear" | "ly" => light_year(),
539 "astronomical unit" | "astronomical units" | "au" => astronomical_unit(),
540 "kilogram" | "kilograms" | "kg" => kilogram(),
542 "gram" | "grams" | "g" => gram(),
543 "milligram" | "milligrams" | "mg" => milligram(),
544 "microgram" | "micrograms" | "µg" | "ug" => microgram(),
545 "tonne" | "tonnes" | "metric ton" | "metric tons" | "t" => tonne(),
546 "pound" | "pounds" | "lb" | "lbs" => pound(),
547 "ounce" | "ounces" | "oz" => ounce(),
548 "stone" | "stones" | "st" => stone(),
549 "carat" | "carats" | "ct" => carat(),
550 "second" | "seconds" | "sec" | "secs" | "s" => second(),
552 "millisecond" | "milliseconds" | "ms" => millisecond(),
553 "microsecond" | "microseconds" | "µs" | "us" => microsecond(),
554 "nanosecond" | "nanoseconds" | "ns" => nanosecond(),
555 "minute" | "minutes" | "min" | "mins" => minute(),
556 "hour" | "hours" | "hr" | "hrs" | "h" => hour(),
557 "day" | "days" => day(),
558 "week" | "weeks" | "wk" => week(),
559 "year" | "years" | "yr" | "yrs" => julian_year(),
560 "kelvin" | "k" => kelvin(),
562 "celsius" | "centigrade" | "°c" | "c" => celsius(),
563 "fahrenheit" | "°f" | "f" => fahrenheit(),
564 "rankine" | "°r" => rankine(),
565 "reaumur" | "réaumur" | "°ré" => reaumur(),
566 "liter" | "litre" | "liters" | "litres" | "l" => litre(),
568 "milliliter" | "millilitre" | "milliliters" | "millilitres" | "ml" => millilitre(),
569 "cubic meter" | "cubic metre" | "m3" | "m³" => cubic_metre(),
570 "gallon" | "gallons" | "gal" => us_gallon(),
571 "quart" | "quarts" | "qt" => us_quart(),
572 "pint" | "pints" | "pt" => us_pint(),
573 "cup" | "cups" => us_cup(),
574 "tablespoon" | "tablespoons" | "tbsp" => tablespoon(),
575 "teaspoon" | "teaspoons" | "tsp" => teaspoon(),
576 "fluid ounce" | "fluid ounces" | "fl oz" => us_fluid_ounce(),
577 "square meter" | "square metre" | "square meters" | "square metres" | "m2" => square_metre(),
579 "square foot" | "square feet" | "sq ft" => square_foot(),
580 "square inch" | "square inches" | "sq in" => square_inch(),
581 "hectare" | "hectares" | "ha" => hectare(),
582 "acre" | "acres" => acre(),
583 "meter per second" | "metre per second" | "meters per second" | "m/s" => metre_per_second(),
585 "kilometer per hour" | "kilometre per hour" | "km/h" | "kph" => kilometre_per_hour(),
586 "mile per hour" | "miles per hour" | "mph" => mile_per_hour(),
587 "knot" | "knots" | "kn" => knot(),
588 "hertz" | "hz" => hertz(),
590 "kilohertz" | "khz" => kilohertz(),
591 "megahertz" | "mhz" => megahertz(),
592 "gigahertz" | "ghz" => gigahertz(),
593 "joule" | "joules" | "j" => joule(),
595 "kilojoule" | "kilojoules" | "kj" => kilojoule(),
596 "calorie" | "calories" | "cal" => calorie(),
597 "kilocalorie" | "kilocalories" | "kcal" => kilocalorie(),
598 "watt hour" | "watt hours" | "wh" => watt_hour(),
599 "kilowatt hour" | "kilowatt hours" | "kwh" => kilowatt_hour(),
600 "electronvolt" | "electronvolts" | "ev" => electronvolt(),
601 "watt" | "watts" | "w" => watt(),
603 "kilowatt" | "kilowatts" | "kw" => kilowatt(),
604 "megawatt" | "megawatts" | "mw" => megawatt(),
605 "horsepower" | "hp" => horsepower(),
606 "pascal" | "pascals" | "pa" => pascal(),
608 "kilopascal" | "kilopascals" | "kpa" => kilopascal(),
609 "bar" | "bars" => bar(),
610 "atmosphere" | "atmospheres" | "atm" => atmosphere(),
611 "psi" => psi(),
612 "bit" | "bits" => bit(),
614 "byte" | "bytes" => byte(),
615 "kilobyte" | "kilobytes" | "kb" => kilobyte(),
616 "megabyte" | "megabytes" | "mb" => megabyte(),
617 "gigabyte" | "gigabytes" | "gb" => gigabyte(),
618 "turn" | "turns" => turn(),
620 "degree" | "degrees" | "deg" | "°" => degree(),
621 "gradian" | "gradians" | "grad" => gradian(),
622 "arcminute" | "arcminutes" => arcminute(),
623 "arcsecond" | "arcseconds" => arcsecond(),
624 "gray" | "grays" | "gy" => gray(),
626 "sievert" | "sieverts" | "sv" => sievert(),
627 "rad" | "rads" => rad_unit(),
628 "rem" | "rems" => rem(),
629 "becquerel" | "becquerels" | "bq" => becquerel(),
630 "curie" | "curies" | "ci" => curie(),
631 "roentgen" | "roentgens" => roentgen(),
632 "katal" | "katals" | "kat" => katal(),
633 "lumen" | "lumens" | "lm" => lumen(),
635 "lux" | "lx" => lux(),
636 "phot" | "phots" => phot(),
637 "foot candle" | "foot-candle" | "footcandle" | "fc" => foot_candle(),
638 "nit" | "nits" => nit(),
639 "siemens" => siemens(),
641 "farad" | "farads" => farad(),
642 "microfarad" | "microfarads" | "µf" | "uf" => microfarad(),
643 "nanofarad" | "nanofarads" | "nf" => nanofarad(),
644 "picofarad" | "picofarads" | "pf" => picofarad(),
645 "weber" | "webers" | "wb" => weber(),
646 "maxwell" | "maxwells" | "mx" => maxwell(),
647 "henry" | "henries" => henry(),
648 "tesla" | "teslas" => tesla(),
649 "gauss" => gauss(),
650 "pascal second" | "pascal-second" | "pa·s" | "pas" => pascal_second(),
652 "poise" => poise(),
653 "centipoise" | "cp" => centipoise(),
654 "stokes" | "stoke" => stokes(),
655 "centistokes" | "cst" => centistokes(),
656 "gallon per minute" | "gpm" => gallon_per_minute(),
658 "cubic foot per minute" | "cfm" => cubic_foot_per_minute(),
659 "litre per second" | "liter per second" | "l/s" => litre_per_second(),
660 "molar" => molar(),
661 "millimolar" | "mm/l" => millimolar(),
662 "bit per second" | "bits per second" | "bps" => bit_per_second(),
664 "kilobit per second" | "kbps" => kilobit_per_second(),
665 "megabit per second" | "mbps" => megabit_per_second(),
666 "gigabit per second" | "gbps" => gigabit_per_second(),
667 "byte per second" | "bytes per second" => byte_per_second(),
668 "newton per meter" | "newton per metre" | "n/m" => newton_per_metre(),
670 "stick" | "sticks" | "stick of butter" => stick_of_butter(),
672 "dash" | "dashes" => dash(),
673 "pinch" | "pinches" => pinch(),
674 "smidgen" | "smidgens" => smidgen(),
675 "fathom" | "fathoms" => fathom(),
677 "cable" | "cables" => cable(),
678 "league" | "leagues" => league(),
679 "light second" | "light-second" | "light seconds" => light_second(),
681 "light minute" | "light-minute" | "light minutes" => light_minute(),
682 "each" | "ea" | "count" => each(),
685 "pair" | "pairs" => pair(),
686 "dozen" | "dozens" | "dz" => dozen(),
687 "score" | "scores" => score(),
688 "gross" => gross(),
689 "ream" | "reams" => ream(),
690 "percent" | "percents" | "%" => percent(),
691 "permille" | "per mille" | "‰" => permille(),
692 "ppm" => ppm(),
693 "ppb" => ppb(),
694 "basis point" | "basis points" => basis_point(),
695 "megameter" | "megametre" => megametre(),
697 "gigameter" | "gigametre" => gigametre(),
698 "picometer" | "picometre" | "pm" => picometre(),
699 "nanogram" | "nanograms" => nanogram(),
700 "gigawatt" | "gigawatts" | "gw" => gigawatt(),
701 "terawatt" | "terawatts" | "tw" => terawatt(),
702 "terabyte" | "terabytes" | "tb" => terabyte(),
703 "petabyte" | "petabytes" | "pb" => petabyte(),
704 _ => return None,
705 };
706 Some(u)
707 }
708}
709
710pub mod constants {
715 use super::Quantity;
716 use crate::dimension::Dimension;
717 use crate::numeric::{BigInt, Rational};
718
719 fn ep(mantissa: i64, pow10: u32) -> Rational {
721 Rational::from_bigint(BigInt::from_i64(mantissa).mul(&BigInt::from_i64(10).pow(pow10)))
722 }
723 fn en(mantissa: i64, pow10: u32) -> Rational {
725 Rational::new(BigInt::from_i64(mantissa), BigInt::from_i64(10).pow(pow10)).unwrap()
726 }
727 fn r(n: i64, d: i64) -> Rational {
728 Rational::new(BigInt::from_i64(n), BigInt::from_i64(d)).unwrap()
729 }
730
731 fn action() -> Dimension { Dimension::energy().mul(Dimension::time()) }
733
734 pub fn speed_of_light() -> Quantity { Quantity::si(ep(299_792_458, 0), Dimension::speed()) }
736 pub fn planck_constant() -> Quantity { Quantity::si(en(662_607_015, 42), action()) }
738 pub fn elementary_charge() -> Quantity { Quantity::si(en(1_602_176_634, 28), Dimension::charge()) }
740 pub fn boltzmann_constant() -> Quantity {
742 Quantity::si(en(1_380_649, 29), Dimension::energy().div(Dimension::temperature()))
743 }
744 pub fn avogadro_constant() -> Quantity {
746 Quantity::si(ep(602_214_076, 15), Dimension::amount().recip())
747 }
748 pub fn molar_gas_constant() -> Quantity {
750 avogadro_constant().mul(&boltzmann_constant())
751 }
752 pub fn gravitational_constant() -> Quantity {
754 let dim = Dimension::volume().div(Dimension::mass()).div(Dimension::time().powi(2));
755 Quantity::si(en(667_430, 16), dim)
756 }
757 pub fn standard_gravity() -> Quantity { Quantity::si(r(980_665, 100_000), Dimension::acceleration()) }
759 pub fn standard_atmosphere() -> Quantity { Quantity::si(ep(101_325, 0), Dimension::pressure()) }
761}
762
763pub fn light_travel_time(distance: &Quantity) -> Option<Quantity> {
768 if distance.dimension() != Dimension::length() {
769 return None;
770 }
771 distance.div(&constants::speed_of_light())
772}
773
774pub fn time_quantity_to_nanos(q: &Quantity) -> Option<i64> {
778 if q.dimension() != Dimension::time() {
779 return None;
780 }
781 q.magnitude_si().mul(&Rational::from_i64(1_000_000_000)).round().to_i64()
783}
784
785pub fn observed_arrival_nanos(event_nanos: i64, distance: &Quantity) -> Option<i64> {
789 let delay = time_quantity_to_nanos(&light_travel_time(distance)?)?;
790 event_nanos.checked_add(delay)
791}
792
793#[derive(Clone, Copy, Debug, PartialEq)]
797pub struct Position {
798 pub x: f64,
799 pub y: f64,
800 pub z: f64,
801}
802
803impl Position {
804 pub fn distance_to(self, other: Position) -> f64 {
806 let (dx, dy, dz) = (self.x - other.x, self.y - other.y, self.z - other.z);
807 (dx * dx + dy * dy + dz * dz).sqrt()
808 }
809}
810
811#[derive(Clone, Copy, Debug, PartialEq)]
817pub struct SpacetimeStamp {
818 pub instant_nanos: i64,
819 pub position: Position,
820}
821
822#[derive(Clone, Copy, Debug, PartialEq, Eq)]
826pub enum CausalRelation {
827 Before,
828 After,
829 Concurrent,
830}
831
832impl SpacetimeStamp {
833 pub fn light_separation_nanos(self, other: SpacetimeStamp) -> i64 {
836 let metres = self.position.distance_to(other.position);
837 (metres / 299_792_458.0 * 1e9).round() as i64
838 }
839
840 pub fn causal_relation(self, other: SpacetimeStamp) -> CausalRelation {
843 let dt = other.instant_nanos as i128 - self.instant_nanos as i128;
845 let light = self.light_separation_nanos(other) as i128;
846 if dt >= light {
847 CausalRelation::Before } else if -dt >= light {
849 CausalRelation::After } else {
851 CausalRelation::Concurrent }
853 }
854
855 pub fn observed_at_nanos(self, observer: Position) -> i64 {
858 let other = SpacetimeStamp { instant_nanos: self.instant_nanos, position: observer };
859 self.instant_nanos + self.light_separation_nanos(other)
860 }
861}
862
863pub fn lorentz_factor(beta: f64) -> Option<f64> {
867 if !(beta.abs() < 1.0) {
869 return None;
870 }
871 Some(1.0 / (1.0 - beta * beta).sqrt())
872}
873
874pub fn proper_time_seconds(coordinate_seconds: f64, beta: f64) -> Option<f64> {
878 Some(coordinate_seconds / lorentz_factor(beta)?)
879}
880
881pub fn time_dilation_factor(velocity: &Quantity) -> Option<f64> {
884 if velocity.dimension() != Dimension::speed() {
885 return None;
886 }
887 let c = constants::speed_of_light();
888 let beta = velocity.magnitude_si().to_f64() / c.magnitude_si().to_f64();
889 lorentz_factor(beta)
890}
891
892#[cfg(test)]
893mod tests {
894 use super::units::*;
895 use super::*;
896
897 fn r(n: i64, d: i64) -> Rational {
898 Rational::from_ratio_i64(n, d).unwrap()
899 }
900 fn i(n: i64) -> Rational {
901 Rational::from_i64(n)
902 }
903
904 #[test]
907 fn golden_two_inches_plus_five_cm_in_feet_is_exactly_42_over_127() {
908 let a = Quantity::of(i(2), &inch());
909 let b = Quantity::of(i(5), ¢imetre());
910 let sum = a.add(&b).expect("both are Length");
911 assert_eq!(sum.dimension(), Dimension::length());
912 assert_eq!(sum.in_unit(&metre()).unwrap(), r(63, 625), "= 63/625 m exactly");
913 assert_eq!(sum.in_unit(&foot()).unwrap(), r(42, 127), "= 42/127 ft exactly");
914 assert_ne!((42.0_f64 / 127.0) as f32 as f64, 42.0 / 127.0);
916 }
917
918 #[test]
919 fn exact_unit_conversions_within_a_dimension() {
920 assert_eq!(Quantity::of(i(1), &mile()).in_unit(&foot()).unwrap(), i(5280));
921 assert_eq!(Quantity::of(i(1), &foot()).in_unit(&inch()).unwrap(), i(12));
922 assert_eq!(Quantity::of(i(1), &yard()).in_unit(&foot()).unwrap(), i(3));
923 assert_eq!(Quantity::of(i(1), &kilometre()).in_unit(&metre()).unwrap(), i(1000));
924 assert_eq!(Quantity::of(i(1), &kilogram()).in_unit(&gram()).unwrap(), i(1000));
925 assert_eq!(Quantity::of(i(1), £()).in_unit(&ounce()).unwrap(), i(16));
926 assert_eq!(Quantity::of(i(1), &hour()).in_unit(&minute()).unwrap(), i(60));
927 assert_eq!(Quantity::of(i(1), £()).in_unit(&gram()).unwrap(), r(45_359_237, 100_000));
929 }
930
931 #[test]
932 fn affine_temperature_conversion_is_offset_aware_and_exact() {
933 assert_eq!(Quantity::of(i(0), &celsius()).in_unit(&kelvin()).unwrap(), r(5463, 20));
935 assert_eq!(Quantity::of(i(20), &celsius()).in_unit(&fahrenheit()).unwrap(), i(68));
936 assert_eq!(Quantity::of(i(100), &celsius()).in_unit(&fahrenheit()).unwrap(), i(212));
937 assert_eq!(Quantity::of(i(-40), &celsius()).in_unit(&fahrenheit()).unwrap(), i(-40));
938 assert_eq!(Quantity::of(i(212), &fahrenheit()).in_unit(&celsius()).unwrap(), i(100));
939 }
940
941 #[test]
942 fn cross_dimension_operations_are_forbidden() {
943 let length = Quantity::of(i(1), &metre());
944 let mass = Quantity::of(i(1), &kilogram());
945 assert!(length.add(&mass).is_none(), "Length + Mass is forbidden");
947 assert!(length.sub(&mass).is_none(), "Length − Mass is forbidden");
948 assert!(length.in_unit(&kilogram()).is_none(), "Length → Mass cast is forbidden");
949 }
950
951 #[test]
952 fn multiplication_and_division_combine_dimensions() {
953 let area = Quantity::of(i(3), &metre()).mul(&Quantity::of(i(4), &metre()));
955 assert_eq!(area.dimension(), Dimension::area());
956 assert_eq!(area.magnitude_si(), &i(12)); let speed = Quantity::of(i(100), &metre()).div(&Quantity::of(i(10), &second())).unwrap();
959 assert_eq!(speed.dimension(), Dimension::speed());
960 assert_eq!(speed.magnitude_si(), &i(10)); let back = area.div(&Quantity::of(i(3), &metre())).unwrap();
963 assert_eq!(back.dimension(), Dimension::length());
964 let scaled = Quantity::of(i(2), &metre()).mul(&Quantity::scalar(i(3)));
966 assert_eq!(scaled.dimension(), Dimension::length());
967 assert_eq!(scaled.in_unit(&metre()).unwrap(), i(6));
968 }
969
970 #[test]
971 fn dimensional_algebra_gauntlet_produces_correct_derived_dimensions_and_magnitudes() {
972 let accel = Quantity::of(i(3), &metre_per_second()).div(&Quantity::of(i(1), &second())).unwrap();
976 let force = Quantity::of(i(2), &kilogram()).mul(&accel);
977 assert_eq!(force.dimension(), Dimension::force());
978 assert_eq!(force.magnitude_si(), &i(6)); let energy = force.mul(&Quantity::of(i(4), &metre()));
981 assert_eq!(energy.dimension(), Dimension::energy());
982 assert_eq!(energy.magnitude_si(), &i(24)); let power = energy.div(&Quantity::of(i(2), &second())).unwrap();
985 assert_eq!(power.dimension(), Dimension::power());
986 assert_eq!(power.magnitude_si(), &i(12)); let pressure = force.div(&Quantity::of(i(2), &square_metre())).unwrap();
989 assert_eq!(pressure.dimension(), Dimension::pressure());
990 assert_eq!(pressure.magnitude_si(), &i(3)); let charge = Quantity::of(i(5), &ere()).mul(&Quantity::of(i(2), &second()));
993 assert_eq!(charge.dimension(), Dimension::charge());
994 assert_eq!(charge.magnitude_si(), &i(10)); let rate = Quantity::of(i(16), &bit()).div(&Quantity::of(i(2), &second())).unwrap();
997 assert_eq!(rate.dimension(), Dimension::data_rate());
998 assert_eq!(rate.in_unit(&bit_per_second()).unwrap(), i(8)); let flow = Quantity::of(i(6), &cubic_metre()).div(&Quantity::of(i(2), &second())).unwrap();
1001 assert_eq!(flow.dimension(), Dimension::volumetric_flow());
1002 assert_eq!(flow.in_unit(&cubic_metre_per_second()).unwrap(), i(3));
1003 let conc = Quantity::of(i(2), &mole()).div(&Quantity::of(i(1), &cubic_metre())).unwrap();
1005 assert_eq!(conc.dimension(), Dimension::molar_concentration());
1006 assert_eq!(conc.in_unit(&millimolar()).unwrap(), i(2));
1007 let kv = Quantity::of(i(4), &square_metre()).div(&Quantity::of(i(2), &second())).unwrap();
1009 assert_eq!(kv.dimension(), Dimension::kinematic_viscosity());
1010 assert_eq!(kv.in_unit(&square_metre_per_second()).unwrap(), i(2));
1011 let dv = Quantity::of(i(4), &pascal()).mul(&Quantity::of(i(2), &second()));
1013 assert_eq!(dv.dimension(), Dimension::dynamic_viscosity());
1014 assert_eq!(dv.in_unit(&pascal_second()).unwrap(), i(8));
1015 let fe = Quantity::of(i(100), &kilometre()).div(&Quantity::of(i(10), &litre())).unwrap();
1017 assert_eq!(fe.dimension(), Dimension::fuel_economy());
1018 assert_eq!(fe.in_unit(&km_per_litre()).unwrap(), i(10));
1019 let mu = Quantity::of(i(6), &pascal_second());
1021 let rho = Quantity::of(i(2), &kilogram()).div(&Quantity::of(i(1), &cubic_metre())).unwrap();
1022 let nu = mu.div(&rho).unwrap();
1023 assert_eq!(nu.dimension(), Dimension::kinematic_viscosity());
1024 assert_eq!(nu.in_unit(&square_metre_per_second()).unwrap(), i(3)); }
1026
1027 struct Rng(u64);
1029 impl Rng {
1030 fn next(&mut self) -> u64 {
1031 self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
1032 let mut z = self.0;
1033 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
1034 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
1035 z ^ (z >> 31)
1036 }
1037 }
1038
1039 #[test]
1040 fn conversion_round_trips_and_add_commutes_under_fuzz() {
1041 let length_units = [metre(), kilometre(), centimetre(), inch(), foot(), yard(), mile()];
1044 let mass_units = [kilogram(), gram(), pound(), ounce()];
1045 let temp_units = [kelvin(), celsius(), fahrenheit()];
1046 let mut rng = Rng(0x_FACE_C0DE_1234_5678);
1047 for _ in 0..2000 {
1048 let n = (rng.next() % 4001) as i64 - 2000;
1049 let d = ((rng.next() % 99) as i64) + 1;
1050 let value = r(n, d);
1051 for set in [&length_units[..], &mass_units[..], &temp_units[..]] {
1052 let u1 = &set[(rng.next() as usize) % set.len()];
1053 let u2 = &set[(rng.next() as usize) % set.len()];
1054 let q = Quantity::of(value.clone(), u1);
1055 assert_eq!(q.in_unit(u1).unwrap(), value, "round-trip in {}", u1.symbol);
1057 let via = Quantity::of(q.in_unit(u2).unwrap(), u2);
1059 assert_eq!(via.in_unit(u1).unwrap(), value, "{} → {} → {}", u1.symbol, u2.symbol, u1.symbol);
1060 let q2 = Quantity::of(value.clone(), u2);
1063 assert_eq!(q.add(&q2), q2.add(&q), "add commutes");
1064 }
1065 }
1066 }
1067
1068 #[test]
1069 fn whole_catalog_round_trips_through_every_sibling_under_fuzz() {
1070 let groups = catalog();
1074 let mut rng = Rng(0x_C0FF_EE_D0_0D_F0_0D);
1075 for _ in 0..400 {
1076 let n = (rng.next() % 20_001) as i64 - 10_000;
1077 let d = ((rng.next() % 199) as i64) + 1;
1078 let value = r(n, d);
1079 for group in &groups {
1080 let u1 = &group[(rng.next() as usize) % group.len()];
1081 let u2 = &group[(rng.next() as usize) % group.len()];
1082 let q = Quantity::of(value.clone(), u1);
1083 let through = q.in_unit(u2).expect("same-dimension conversion is total");
1084 let back = Quantity::of(through, u2).in_unit(u1).unwrap();
1085 assert_eq!(back, value, "{} → {} → {}", u1.symbol, u2.symbol, u1.symbol);
1086 }
1087 }
1088 }
1089
1090 #[test]
1091 fn affine_temperature_fixed_points_are_exact() {
1092 assert_eq!(Quantity::of(i(0), &celsius()).in_unit(&kelvin()).unwrap(), r(27315, 100));
1095 assert_eq!(Quantity::of(i(0), &celsius()).in_unit(&fahrenheit()).unwrap(), i(32));
1096 assert_eq!(Quantity::of(i(0), &celsius()).in_unit(&rankine()).unwrap(), r(49167, 100));
1097 assert_eq!(Quantity::of(i(0), &celsius()).in_unit(&reaumur()).unwrap(), i(0));
1098 assert_eq!(Quantity::of(i(100), &celsius()).in_unit(&fahrenheit()).unwrap(), i(212));
1100 assert_eq!(Quantity::of(i(100), &celsius()).in_unit(&kelvin()).unwrap(), r(37315, 100));
1101 assert_eq!(Quantity::of(i(100), &celsius()).in_unit(&reaumur()).unwrap(), i(80));
1102 assert_eq!(Quantity::of(i(-40), &celsius()).in_unit(&fahrenheit()).unwrap(), i(-40));
1104 assert_eq!(Quantity::of(i(0), &kelvin()).in_unit(&celsius()).unwrap(), r(-27315, 100));
1106 assert_eq!(Quantity::of(i(0), &kelvin()).in_unit(&fahrenheit()).unwrap(), r(-45967, 100));
1107 assert_eq!(Quantity::of(i(0), &kelvin()).in_unit(&rankine()).unwrap(), i(0));
1108 assert_eq!(Quantity::of(i(37), &celsius()).in_unit(&fahrenheit()).unwrap(), r(986, 10));
1110 let v = r(37, 7);
1112 for u in [kelvin(), celsius(), fahrenheit(), rankine(), reaumur()] {
1113 assert_eq!(Quantity::of(v.clone(), &u).in_unit(&u).unwrap(), v, "round-trip {}", u.symbol);
1114 }
1115 }
1116
1117 #[test]
1118 fn cooking_measurements_convert_exactly() {
1119 assert_eq!(Quantity::of(i(1), &us_cup()).in_unit(&tablespoon()).unwrap(), i(16), "1 cup = 16 tbsp");
1121 assert_eq!(Quantity::of(i(1), &us_cup()).in_unit(&teaspoon()).unwrap(), i(48), "1 cup = 48 tsp");
1122 assert_eq!(Quantity::of(i(1), &us_cup()).in_unit(&us_fluid_ounce()).unwrap(), i(8), "1 cup = 8 fl oz");
1123 assert_eq!(Quantity::of(i(1), &tablespoon()).in_unit(&teaspoon()).unwrap(), i(3), "1 tbsp = 3 tsp");
1124 assert_eq!(Quantity::of(i(1), &us_fluid_ounce()).in_unit(&tablespoon()).unwrap(), i(2), "1 fl oz = 2 tbsp");
1125 assert_eq!(Quantity::of(i(1), &us_pint()).in_unit(&us_cup()).unwrap(), i(2), "1 pint = 2 cups");
1126 assert_eq!(Quantity::of(i(1), &us_quart()).in_unit(&us_cup()).unwrap(), i(4), "1 quart = 4 cups");
1127 assert_eq!(Quantity::of(i(1), &us_gallon()).in_unit(&us_cup()).unwrap(), i(16), "1 gallon = 16 cups");
1128 assert_eq!(Quantity::of(i(1), &us_gallon()).in_unit(&teaspoon()).unwrap(), i(768), "1 gallon = 768 tsp");
1129 assert_eq!(Quantity::of(i(1), &oil_barrel()).in_unit(&us_gallon()).unwrap(), i(42), "1 oil barrel = 42 gal");
1130 assert_eq!(Quantity::of(i(1), &us_gallon()).in_unit(&litre()).unwrap(), r(473_176_473, 125_000_000));
1132 let mixed = Quantity::of(i(3), &teaspoon()).add(&Quantity::of(i(1), &tablespoon())).unwrap();
1134 assert_eq!(mixed.in_unit(&tablespoon()).unwrap(), i(2));
1135 }
1136
1137 #[test]
1138 fn general_cross_unit_conversions_are_exact() {
1139 assert_eq!(Quantity::of(i(1), &cubic_metre()).in_unit(&litre()).unwrap(), i(1000));
1141 assert_eq!(Quantity::of(i(1), &cubic_foot()).in_unit(&cubic_inch()).unwrap(), i(1728));
1142 assert_eq!(Quantity::of(i(1), &litre()).in_unit(&millilitre()).unwrap(), i(1000));
1143 assert_eq!(Quantity::of(i(1), &acre()).in_unit(&square_foot()).unwrap(), i(43_560));
1145 assert_eq!(Quantity::of(i(1), &hectare()).in_unit(&square_metre()).unwrap(), i(10_000));
1146 assert_eq!(Quantity::of(i(1), &square_foot()).in_unit(&square_inch()).unwrap(), i(144));
1147 assert_eq!(Quantity::of(i(1), &kilometre_per_hour()).in_unit(&metre_per_second()).unwrap(), r(5, 18));
1149 assert_eq!(Quantity::of(i(1), &day()).in_unit(&hour()).unwrap(), i(24));
1150 assert_eq!(Quantity::of(i(1), &fortnight()).in_unit(&day()).unwrap(), i(14));
1151 assert_eq!(Quantity::of(i(1), &gigahertz()).in_unit(&hertz()).unwrap(), i(1_000_000_000));
1152 assert_eq!(Quantity::of(i(1), &kilocalorie()).in_unit(&calorie()).unwrap(), i(1000));
1154 assert_eq!(Quantity::of(i(1), &kilowatt_hour()).in_unit(&joule()).unwrap(), i(3_600_000));
1155 assert_eq!(Quantity::of(i(1), &calorie()).in_unit(&joule()).unwrap(), r(523, 125)); assert_eq!(Quantity::of(i(1), &atmosphere()).in_unit(&pascal()).unwrap(), i(101_325));
1157 assert_eq!(Quantity::of(i(1), &byte()).in_unit(&bit()).unwrap(), i(8));
1159 assert_eq!(Quantity::of(i(1), &kibibyte()).in_unit(&byte()).unwrap(), i(1024));
1160 assert_eq!(Quantity::of(i(1), &mebibyte()).in_unit(&kibibyte()).unwrap(), i(1024));
1161 assert_eq!(Quantity::of(i(1), &megabyte()).in_unit(&byte()).unwrap(), i(1_000_000));
1162 assert_eq!(Quantity::of(i(1), &turn()).in_unit(°ree()).unwrap(), i(360));
1164 assert_eq!(Quantity::of(i(1), °ree()).in_unit(&arcminute()).unwrap(), i(60));
1165 assert_eq!(Quantity::of(i(1), &arcminute()).in_unit(&arcsecond()).unwrap(), i(60));
1166 assert_eq!(Quantity::of(i(1), &ere_hour()).in_unit(&coulomb()).unwrap(), i(3600));
1168 }
1169
1170 #[test]
1171 fn extended_units_cooking_nautical_astronomical_radiation_illumination_convert_exactly() {
1172 assert_eq!(Quantity::of(i(1), &teaspoon()).in_unit(&dash()).unwrap(), i(8), "1 tsp = 8 dashes");
1174 assert_eq!(Quantity::of(i(1), &teaspoon()).in_unit(&pinch()).unwrap(), i(16), "1 tsp = 16 pinches");
1175 assert_eq!(Quantity::of(i(1), &teaspoon()).in_unit(&smidgen()).unwrap(), i(32), "1 tsp = 32 smidgens");
1176 assert_eq!(Quantity::of(i(1), &dash()).in_unit(&pinch()).unwrap(), i(2), "1 dash = 2 pinches");
1177 assert_eq!(Quantity::of(i(1), &stick_of_butter()).in_unit(&tablespoon()).unwrap(), i(8), "1 stick = 8 tbsp");
1178 assert_eq!(Quantity::of(i(1), &stick_of_butter()).in_unit(&us_cup()).unwrap(), r(1, 2), "1 stick = ½ cup");
1179 assert_eq!(Quantity::of(i(1), &bushel()).in_unit(&peck()).unwrap(), i(4), "1 bushel = 4 pecks");
1181 assert_eq!(Quantity::of(i(1), &bushel()).in_unit(&dry_gallon()).unwrap(), i(8));
1182 assert_eq!(Quantity::of(i(1), &dry_gallon()).in_unit(&dry_quart()).unwrap(), i(4));
1183 assert_eq!(Quantity::of(i(1), &nautical_mile()).in_unit(&cable()).unwrap(), i(10), "1 nmi = 10 cables");
1185 assert_eq!(Quantity::of(i(1), &nautical_league()).in_unit(&nautical_mile()).unwrap(), i(3));
1186 assert_eq!(Quantity::of(i(1), &league()).in_unit(&mile()).unwrap(), i(3), "1 league = 3 miles");
1187 assert_eq!(Quantity::of(i(1), &light_minute()).in_unit(&light_second()).unwrap(), i(60));
1189 assert_eq!(Quantity::of(i(1), &light_hour()).in_unit(&light_minute()).unwrap(), i(60));
1190 assert_eq!(Quantity::of(i(1), &light_day()).in_unit(&light_hour()).unwrap(), i(24));
1191 assert_eq!(Quantity::of(i(1), &light_second()).in_unit(&metre()).unwrap(), i(299_792_458));
1192 assert_eq!(Quantity::of(i(1), &gray()).in_unit(&rad_unit()).unwrap(), i(100), "1 Gy = 100 rad");
1194 assert_eq!(Quantity::of(i(1), &sievert()).in_unit(&rem()).unwrap(), i(100), "1 Sv = 100 rem");
1195 assert_eq!(Quantity::of(i(1), &curie()).in_unit(&becquerel()).unwrap(), i(37_000_000_000), "1 Ci = 3.7e10 Bq");
1196 assert_eq!(gray().dimension, Dimension::absorbed_dose());
1197 assert_eq!(sievert().dimension, Dimension::absorbed_dose()); assert_eq!(becquerel().dimension, Dimension::radioactivity());
1199 assert_eq!(roentgen().dimension, Dimension::exposure());
1200 assert_eq!(katal().dimension, Dimension::catalytic_activity());
1201 assert_eq!(Quantity::of(i(1), &phot()).in_unit(&lux()).unwrap(), i(10_000), "1 phot = 10000 lux");
1203 assert_eq!(Quantity::of(i(1), &foot_candle()).in_unit(&lux()).unwrap(), r(1_562_500, 145_161));
1204 assert_eq!(lumen().dimension, Dimension::luminous_flux());
1205 assert_eq!(lux().dimension, Dimension::illuminance());
1206 assert_eq!(nit().dimension, Dimension::luminance());
1207 }
1208
1209 #[test]
1210 fn rate_flow_concentration_viscosity_torque_and_prefixes_convert_exactly() {
1211 assert_eq!(Quantity::of(i(1), &byte_per_second()).in_unit(&bit_per_second()).unwrap(), i(8));
1213 assert_eq!(Quantity::of(i(1), &gigabit_per_second()).in_unit(&megabit_per_second()).unwrap(), i(1000));
1214 assert_eq!(Quantity::of(i(1), &megabyte_per_second()).in_unit(&bit_per_second()).unwrap(), i(8_000_000));
1215 assert_eq!(Quantity::of(i(1), &cubic_metre_per_second()).in_unit(&litre_per_second()).unwrap(), i(1000));
1217 assert_eq!(Quantity::of(i(1), &litre_per_second()).in_unit(&litre_per_minute()).unwrap(), i(60));
1218 assert_eq!(Quantity::of(i(1), &molar()).in_unit(&millimolar()).unwrap(), i(1000));
1220 assert_eq!(Quantity::of(i(1), &millimolar()).in_unit(µmolar()).unwrap(), i(1000));
1221 assert_eq!(Quantity::of(i(1), &pascal_second()).in_unit(&poise()).unwrap(), i(10));
1223 assert_eq!(Quantity::of(i(1), &poise()).in_unit(¢ipoise()).unwrap(), i(100));
1224 assert_eq!(Quantity::of(i(1), &square_metre_per_second()).in_unit(&stokes()).unwrap(), i(10_000));
1225 assert_eq!(Quantity::of(i(1), &stokes()).in_unit(¢istokes()).unwrap(), i(100));
1226 assert_eq!(newton_metre().dimension, Dimension::energy());
1228 assert_eq!(Quantity::of(i(1), &newton_metre()).in_unit(&joule()).unwrap(), i(1));
1229 assert_eq!(km_per_litre().dimension, Dimension::fuel_economy());
1231 assert_eq!(mile_per_gallon().dimension, Dimension::fuel_economy());
1232 assert_eq!(Quantity::of(i(1), &gigametre()).in_unit(&megametre()).unwrap(), i(1000));
1234 assert_eq!(Quantity::of(i(1), &megametre()).in_unit(&metre()).unwrap(), i(1_000_000));
1235 assert_eq!(Quantity::of(i(1), &terawatt()).in_unit(&gigawatt()).unwrap(), i(1000));
1236 assert_eq!(Quantity::of(i(1), &petabyte()).in_unit(&terabyte()).unwrap(), i(1000));
1237 assert_eq!(Quantity::of(i(1), &picometre()).in_unit(&femtometre()).unwrap(), i(1000));
1238 assert_eq!(Quantity::of(i(1), &terajoule()).in_unit(&gigajoule()).unwrap(), i(1000));
1239 }
1240
1241 #[test]
1242 fn electromagnetic_and_surface_tension_units_convert_exactly() {
1243 assert_eq!(siemens().dimension, Dimension::resistance().recip());
1245 assert_eq!(Quantity::of(i(1), &siemens()).in_unit(&millisiemens()).unwrap(), i(1000));
1246 assert_eq!(Quantity::of(i(1), µfarad()).in_unit(&nanofarad()).unwrap(), i(1000));
1248 assert_eq!(Quantity::of(i(1), &nanofarad()).in_unit(&picofarad()).unwrap(), i(1000));
1249 assert_eq!(Quantity::of(i(1), &farad()).in_unit(&picofarad()).unwrap(), i(1_000_000_000_000));
1250 assert_eq!(Quantity::of(i(1), &weber()).in_unit(&maxwell()).unwrap(), i(100_000_000));
1252 assert_eq!(Quantity::of(i(1), &henry()).in_unit(&millihenry()).unwrap(), i(1000));
1254 assert_eq!(Quantity::of(i(1), &millihenry()).in_unit(µhenry()).unwrap(), i(1000));
1255 assert_eq!(Quantity::of(i(1), &tesla()).in_unit(&gauss()).unwrap(), i(10_000));
1257 assert_eq!(Quantity::of(i(1), &tesla()).in_unit(&millitesla()).unwrap(), i(1000));
1258 assert_eq!(Quantity::of(i(1), &newton_per_metre()).in_unit(&dyne_per_centimetre()).unwrap(), i(1000));
1260 assert_eq!(farad().dimension, Dimension::charge().div(Dimension::voltage()));
1262 assert_eq!(henry().dimension, Dimension::magnetic_flux().div(Dimension::current()));
1263 assert_eq!(tesla().dimension, Dimension::magnetic_flux().div(Dimension::area()));
1264 assert!(Quantity::of(i(1), &farad()).in_unit(&henry()).is_none());
1266 }
1267
1268 #[test]
1269 fn dimensionless_counting_and_ratio_units_convert_exactly() {
1270 assert_eq!(Quantity::of(i(1), &dozen()).in_unit(&each()).unwrap(), i(12));
1272 assert_eq!(Quantity::of(i(1), &gross()).in_unit(&dozen()).unwrap(), i(12)); assert_eq!(Quantity::of(i(1), &great_gross()).in_unit(&gross()).unwrap(), i(12));
1274 assert_eq!(Quantity::of(i(1), &score()).in_unit(&each()).unwrap(), i(20));
1275 assert_eq!(Quantity::of(i(1), &baker_dozen()).in_unit(&each()).unwrap(), i(13));
1276 assert_eq!(Quantity::of(i(1), &ream()).in_unit(&each()).unwrap(), i(500));
1277 assert_eq!(Quantity::of(i(1), &each()).in_unit(&percent()).unwrap(), i(100));
1279 assert_eq!(Quantity::of(i(1), &each()).in_unit(&permille()).unwrap(), i(1000));
1280 assert_eq!(Quantity::of(i(1), &each()).in_unit(&basis_point()).unwrap(), i(10_000));
1281 assert_eq!(Quantity::of(i(1), &percent()).in_unit(&basis_point()).unwrap(), i(100)); assert_eq!(Quantity::of(i(1), &percent()).in_unit(&permille()).unwrap(), i(10));
1283 assert_eq!(Quantity::of(i(1), &each()).in_unit(&ppm()).unwrap(), i(1_000_000));
1284 assert_eq!(Quantity::of(i(1), &ppm()).in_unit(&ppb()).unwrap(), i(1000));
1285 assert_eq!(Quantity::of(i(1), &dozen()).in_unit(&percent()).unwrap(), i(1200));
1287 for u in [each(), pair(), dozen(), gross(), percent(), ppm(), basis_point()] {
1289 assert_eq!(u.dimension, Dimension::DIMENSIONLESS, "{} is dimensionless", u.symbol);
1290 assert!(Quantity::of(i(1), &u).in_unit(&metre()).is_none(), "{} → m forbidden", u.symbol);
1291 }
1292 }
1293
1294 #[test]
1295 fn light_travel_time_is_exact_and_dimension_safe() {
1296 let secs = |q: Quantity| q.in_unit(&second()).unwrap();
1297 let ltt = |u: Unit| super::light_travel_time(&Quantity::of(i(1), &u)).unwrap();
1298 assert_eq!(secs(ltt(light_second())), i(1));
1300 assert_eq!(secs(ltt(light_minute())), i(60));
1301 assert_eq!(secs(ltt(light_year())), i(31_557_600)); let sun = ltt(astronomical_unit());
1304 assert_eq!(sun.dimension(), Dimension::time());
1305 let sun_secs = secs(sun);
1306 assert_eq!(sun_secs, r(149_597_870_700, 299_792_458));
1307 assert!(sun_secs > i(498) && sun_secs < i(500));
1308 assert!(super::light_travel_time(&Quantity::of(i(5), &kilogram())).is_none());
1310 }
1311
1312 #[test]
1313 fn observed_arrival_adds_light_delay() {
1314 const NS: i64 = 1_000_000_000;
1315 assert_eq!(super::time_quantity_to_nanos(&Quantity::of(i(1), &second())), Some(NS));
1317 assert_eq!(super::time_quantity_to_nanos(&Quantity::of(i(1), &millisecond())), Some(1_000_000));
1318 assert_eq!(super::time_quantity_to_nanos(&Quantity::of(i(1), &metre())), None);
1320 assert_eq!(
1322 super::observed_arrival_nanos(0, &Quantity::of(i(1), &light_minute())),
1323 Some(60 * NS)
1324 );
1325 assert_eq!(
1327 super::observed_arrival_nanos(5 * NS, &Quantity::of(i(1), &light_second())),
1328 Some(6 * NS)
1329 );
1330 let sun = super::observed_arrival_nanos(0, &Quantity::of(i(1), &astronomical_unit())).unwrap();
1332 assert!(sun > 498 * NS && sun < 500 * NS);
1333 let expected = super::time_quantity_to_nanos(&super::light_travel_time(&Quantity::of(i(1), &astronomical_unit())).unwrap());
1335 assert_eq!(Some(sun), expected);
1336 assert_eq!(super::observed_arrival_nanos(0, &Quantity::of(i(1), &kilogram())), None);
1338 }
1339
1340 #[test]
1341 fn spacetime_stamp_is_space_aware_like_a_timezone_for_position() {
1342 use super::{CausalRelation, Position, SpacetimeStamp};
1343 const C: f64 = 299_792_458.0; let pos = |x: f64| Position { x, y: 0.0, z: 0.0 };
1345 let at = |ns: i64, x: f64| SpacetimeStamp { instant_nanos: ns, position: pos(x) };
1346 let here = pos(0.0);
1347 let one_ls = C; assert_eq!(at(0, 0.0).causal_relation(at(1_000_000_000, 0.0)), CausalRelation::Before);
1351 assert_eq!(at(1_000_000_000, 0.0).causal_relation(at(0, 0.0)), CausalRelation::After);
1352 assert_eq!(at(0, 0.0).light_separation_nanos(at(0, one_ls)), 1_000_000_000);
1354 assert_eq!(at(0, 0.0).causal_relation(at(2_000_000_000, one_ls)), CausalRelation::Before);
1356 assert_eq!(at(0, 0.0).causal_relation(at(1_000_000_000, one_ls)), CausalRelation::Before);
1358 assert_eq!(at(0, 0.0).causal_relation(at(500_000_000, one_ls)), CausalRelation::Concurrent);
1360 assert_eq!(at(0, 0.0).observed_at_nanos(pos(one_ls)), 1_000_000_000);
1362 assert!((Position { x: 3.0, y: 4.0, z: 0.0 }.distance_to(here) - 5.0).abs() < 1e-9);
1364 }
1365
1366 #[test]
1367 fn relativistic_time_dilation() {
1368 let approx = |a: f64, b: f64| (a - b).abs() < 1e-9;
1369 assert_eq!(super::lorentz_factor(0.0), Some(1.0));
1371 assert!(approx(super::lorentz_factor(0.6).unwrap(), 1.25));
1373 assert!(approx(super::lorentz_factor(0.8).unwrap(), 5.0 / 3.0));
1374 assert!(approx(super::proper_time_seconds(10.0, 0.6).unwrap(), 8.0));
1376 assert_eq!(super::lorentz_factor(1.0), None);
1378 assert_eq!(super::lorentz_factor(1.5), None);
1379 assert_eq!(super::lorentz_factor(f64::NAN), None);
1380 let c = super::constants::speed_of_light();
1382 let v = Quantity::si(c.magnitude_si().mul(&r(3, 5)), Dimension::speed());
1383 assert!(approx(super::time_dilation_factor(&v).unwrap(), 1.25));
1384 assert_eq!(super::time_dilation_factor(&Quantity::of(i(1), &metre())), None);
1386 }
1387
1388 #[test]
1389 fn physical_constants_carry_correct_dimensions_and_compose() {
1390 use super::constants::*;
1391 let c = speed_of_light();
1393 assert_eq!(c.dimension(), Dimension::speed());
1394 assert_eq!(c.in_unit(&metre_per_second()).unwrap(), i(299_792_458));
1395 assert_eq!(c.in_unit(&kilometre_per_hour()).unwrap(), r(5_396_264_244, 5));
1396 let energy = Quantity::of(i(2), &kilogram()).mul(&c).mul(&c);
1398 assert_eq!(energy.dimension(), Dimension::energy());
1399 let c_sq = i(299_792_458).mul(&i(299_792_458));
1400 assert_eq!(energy.magnitude_si(), &i(2).mul(&c_sq));
1401 let r_gas = molar_gas_constant();
1403 assert_eq!(r_gas.dimension(), Dimension::energy().div(Dimension::amount()).div(Dimension::temperature()));
1404 assert_eq!(r_gas.magnitude_si(), avogadro_constant().mul(&boltzmann_constant()).magnitude_si());
1405 assert_eq!(planck_constant().dimension(), Dimension::energy().mul(Dimension::time())); assert_eq!(elementary_charge().dimension(), Dimension::charge());
1408 assert_eq!(boltzmann_constant().dimension(), Dimension::energy().div(Dimension::temperature()));
1409 assert_eq!(avogadro_constant().dimension(), Dimension::amount().recip());
1410 assert_eq!(gravitational_constant().dimension(),
1411 Dimension::volume().div(Dimension::mass()).div(Dimension::time().powi(2)));
1412 assert_eq!(standard_gravity().dimension(), Dimension::acceleration());
1413 assert_eq!(standard_atmosphere().in_unit(&pascal()).unwrap(), i(101_325));
1415 assert_eq!(standard_gravity().in_unit(&metre_per_second()).is_none(), true); let n = Quantity::of(i(1), &mole());
1418 let t = Quantity::of(i(300), &kelvin());
1419 let v = Quantity::of(i(1), &cubic_metre());
1420 let p = n.mul(&r_gas).mul(&t).div(&v).unwrap();
1421 assert_eq!(p.dimension(), Dimension::pressure());
1422 }
1423
1424 #[test]
1425 fn in_best_unit_auto_scales_to_the_most_human_unit() {
1426 let ladder = [millimetre(), centimetre(), metre(), kilometre()];
1427 let (mag, u) = Quantity::of(i(1500), &metre()).in_best_unit(&ladder).unwrap();
1429 assert_eq!((u.symbol, mag), ("km", r(3, 2)));
1430 let (mag, u) = Quantity::of(r(1, 2), &kilometre()).in_best_unit(&ladder).unwrap();
1432 assert_eq!((u.symbol, mag), ("m", i(500)));
1433 let (mag, u) = Quantity::of(r(3, 1000), &metre()).in_best_unit(&ladder).unwrap();
1435 assert_eq!((u.symbol, mag), ("mm", i(3)));
1436 let (mag, u) = Quantity::of(i(1), &kilometre()).in_best_unit(&ladder).unwrap();
1438 assert_eq!((u.symbol, mag), ("km", i(1)));
1439 let (mag, u) = Quantity::of(r(1, 10), &millimetre()).in_best_unit(&ladder).unwrap();
1441 assert_eq!((u.symbol, mag), ("mm", r(1, 10)));
1442 let (mag, u) = Quantity::of(i(-2000), &metre()).in_best_unit(&ladder).unwrap();
1444 assert_eq!((u.symbol, mag), ("km", i(-2)));
1445 assert!(Quantity::of(i(1), &metre()).in_best_unit(&[kilogram(), second()]).is_none());
1447 let q = Quantity::of(i(1500), &metre());
1449 let (mag, u) = q.in_best_unit(&ladder).unwrap();
1450 assert_eq!(Quantity::of(mag, u).magnitude_si(), q.magnitude_si());
1451 let mass_ladder = [gram(), kilogram(), tonne()];
1453 let (mag, u) = Quantity::of(i(2_500_000), &gram()).in_best_unit(&mass_ladder).unwrap();
1454 assert_eq!((u.symbol, mag), ("t", r(5, 2)));
1455 }
1456
1457 #[test]
1458 fn by_name_resolves_units_from_english_names_plurals_and_symbols() {
1459 for alias in ["inch", "inches", "in", " Inch "] {
1461 assert_eq!(by_name(alias), Some(inch()), "{alias}");
1462 }
1463 for alias in ["centimeter", "centimetre", "cm"] {
1464 assert_eq!(by_name(alias), Some(centimetre()), "{alias}");
1465 }
1466 for alias in ["foot", "feet", "ft"] {
1467 assert_eq!(by_name(alias), Some(foot()), "{alias}");
1468 }
1469 assert_eq!(by_name("KM"), Some(kilometre()));
1471 assert_eq!(by_name("Kilogram"), Some(kilogram()));
1472 assert_eq!(by_name("celsius"), Some(celsius()));
1473 assert_eq!(by_name("tablespoon"), Some(tablespoon()));
1474 assert_eq!(by_name("MPH"), Some(mile_per_hour()));
1475 assert_eq!(by_name("zorgles"), None);
1477 assert_eq!(by_name(""), None);
1478 let a = Quantity::of(i(2), &by_name("inches").unwrap());
1480 let b = Quantity::of(i(5), &by_name("cm").unwrap());
1481 let sum = a.add(&b).unwrap();
1482 assert_eq!(sum.in_unit(&by_name("feet").unwrap()).unwrap(), r(42, 127));
1483 }
1484
1485 #[test]
1486 fn by_name_covers_radiation_photometry_em_dimensionless_and_prefixes() {
1487 assert_eq!(by_name("gray"), Some(gray()));
1489 assert_eq!(by_name("sievert"), Some(sievert()));
1490 assert_eq!(by_name("becquerel"), Some(becquerel()));
1491 assert_eq!(by_name("lux"), Some(lux()));
1492 assert_eq!(by_name("lumen"), Some(lumen()));
1493 assert_eq!(by_name("tesla"), Some(tesla()));
1494 assert_eq!(by_name("farad"), Some(farad()));
1495 assert_eq!(by_name("henry"), Some(henry()));
1496 assert_eq!(by_name("poise"), Some(poise()));
1497 assert_eq!(by_name("molar"), Some(molar()));
1498 assert_eq!(by_name("Mbps"), Some(megabit_per_second()));
1499 assert_eq!(by_name("dozen"), Some(dozen()));
1500 assert_eq!(by_name("percent"), Some(percent()));
1501 assert_eq!(by_name("ppm"), Some(ppm()));
1502 assert_eq!(by_name("terabyte"), Some(terabyte()));
1503 assert_eq!(by_name("pinch"), Some(pinch()));
1504 assert_eq!(by_name("fathom"), Some(fathom()));
1505 assert_eq!(by_name("light second"), Some(light_second()));
1506 for name in ["gray", "tesla", "poise", "dozen", "percent", "terabyte", "pinch", "light second", "lux", "molar"] {
1508 let u = by_name(name).unwrap();
1509 let v = r(3, 1);
1510 assert_eq!(Quantity::of(v.clone(), &u).in_unit(&u).unwrap(), v, "{name}");
1511 }
1512 }
1513
1514 fn catalog() -> Vec<Vec<Unit>> {
1516 vec![
1517 vec![metre(), kilometre(), hectometre(), dekametre(), decimetre(), centimetre(), millimetre(),
1518 micrometre(), nanometre(), picometre(), femtometre(), megametre(), gigametre(), angstrom(),
1519 inch(), foot(), yard(), mile(), nautical_mile(),
1520 fathom(), furlong(), chain(), rod(), hand(), point(), pica(), thou(), astronomical_unit(),
1521 light_year(), cable(), league(), nautical_league(), light_second(), light_minute(),
1522 light_hour(), light_day(), lunar_distance(), solar_radius(), earth_radius()],
1523 vec![kilogram(), gram(), milligram(), microgram(), nanogram(), tonne(), pound(), ounce(), stone(), short_ton(),
1524 long_ton(), hundredweight(), grain(), dram(), carat(), troy_ounce(), troy_pound(),
1525 solar_mass(), earth_mass(), jupiter_mass()],
1526 vec![second(), millisecond(), microsecond(), nanosecond(), picosecond(), minute(), hour(), day(), week(),
1527 fortnight(), julian_year(), decade(), century()],
1528 vec![kelvin(), celsius(), fahrenheit(), rankine(), reaumur()],
1529 vec![cubic_metre(), litre(), millilitre(), centilitre(), decilitre(), cubic_centimetre(),
1530 cubic_inch(), cubic_foot(), us_gallon(), us_quart(), us_pint(), us_cup(), us_gill(),
1531 us_fluid_ounce(), tablespoon(), teaspoon(), oil_barrel(), imperial_gallon(), imperial_pint(),
1532 imperial_fluid_ounce(), stick_of_butter(), dash(), pinch(), smidgen(), bushel(), peck(),
1533 dry_gallon(), dry_quart(), dry_pint()],
1534 vec![square_metre(), square_kilometre(), square_centimetre(), square_inch(), square_foot(),
1535 square_yard(), square_mile(), hectare(), are(), acre()],
1536 vec![metre_per_second(), kilometre_per_hour(), mile_per_hour(), foot_per_second(), knot()],
1537 vec![hertz(), kilohertz(), megahertz(), gigahertz(), rpm()],
1538 vec![newton(), kilonewton(), dyne(), kilogram_force(), pound_force()],
1539 vec![joule(), kilojoule(), megajoule(), gigajoule(), terajoule(), calorie(), kilocalorie(),
1540 watt_hour(), kilowatt_hour(), erg(), electronvolt(), newton_metre(), pound_foot()],
1541 vec![watt(), kilowatt(), megawatt(), gigawatt(), terawatt(), horsepower()],
1542 vec![pascal(), kilopascal(), bar(), millibar(), atmosphere(), psi()],
1543 vec![bit(), byte(), kilobit(), kilobyte(), megabyte(), gigabyte(), terabit(), terabyte(), petabyte(),
1544 kibibyte(), mebibyte(), gibibyte()],
1545 vec![turn(), degree(), gradian(), arcminute(), arcsecond()],
1546 vec![ampere(), milliampere()],
1547 vec![coulomb(), ampere_hour(), milliampere_hour()],
1548 vec![volt(), millivolt(), kilovolt()],
1549 vec![ohm()],
1550 vec![mole()],
1551 vec![candela()],
1552 vec![gray(), sievert(), rad_unit(), rem()], vec![becquerel(), curie()], vec![roentgen()], vec![katal()], vec![lumen()], vec![lux(), phot(), foot_candle()], vec![nit()], vec![bit_per_second(), kilobit_per_second(), megabit_per_second(), gigabit_per_second(),
1563 byte_per_second(), megabyte_per_second()], vec![cubic_metre_per_second(), litre_per_second(), litre_per_minute(),
1565 gallon_per_minute(), cubic_foot_per_minute()], vec![molar(), millimolar(), micromolar()], vec![molal()], vec![pascal_second(), poise(), centipoise()], vec![square_metre_per_second(), stokes(), centistokes()], vec![mile_per_gallon(), km_per_litre()], vec![siemens(), millisiemens()], vec![farad(), microfarad(), nanofarad(), picofarad()], vec![weber(), maxwell()], vec![henry(), millihenry(), microhenry()], vec![tesla(), millitesla(), gauss()], vec![newton_per_metre(), dyne_per_centimetre()], vec![each(), pair(), dozen(), baker_dozen(), score(), gross(), great_gross(), ream(),
1580 percent(), permille(), ppm(), ppb(), basis_point()],
1581 ]
1582 }
1583
1584 #[test]
1588 fn every_unit_in_the_catalog_is_well_formed_and_dimension_safe() {
1589 let groups = catalog();
1590 let v = r(7, 3); for group in &groups {
1592 for u in group {
1593 assert_eq!(Quantity::of(v.clone(), u).in_unit(u).unwrap(), v, "round-trip {}", u.symbol);
1595 assert!(!u.scale.is_zero() && !u.scale.is_negative(), "scale > 0 for {}", u.symbol);
1597 for other in group {
1599 assert!(
1600 Quantity::of(v.clone(), u).in_unit(other).is_some(),
1601 "{} convertible to {}",
1602 u.symbol,
1603 other.symbol
1604 );
1605 assert_eq!(u.dimension, other.dimension, "{} and {} share a dimension", u.symbol, other.symbol);
1606 }
1607 }
1608 }
1609 for (gi, ga) in groups.iter().enumerate() {
1611 for (gj, gb) in groups.iter().enumerate() {
1612 if gi == gj || ga[0].dimension == gb[0].dimension {
1613 continue;
1614 }
1615 assert!(
1616 Quantity::of(v.clone(), &ga[0]).in_unit(&gb[0]).is_none(),
1617 "{} → {} must be a forbidden cross-dimension cast",
1618 ga[0].symbol,
1619 gb[0].symbol
1620 );
1621 }
1622 }
1623 }
1624}