use crate::dimension::Dimension;
use crate::numeric::Rational;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Unit {
pub symbol: &'static str,
pub dimension: Dimension,
pub scale: Rational,
pub offset: Rational,
}
impl Unit {
pub fn linear(symbol: &'static str, dimension: Dimension, scale: Rational) -> Unit {
Unit { symbol, dimension, scale, offset: Rational::zero() }
}
pub fn affine(symbol: &'static str, dimension: Dimension, scale: Rational, offset: Rational) -> Unit {
Unit { symbol, dimension, scale, offset }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Quantity {
magnitude: Rational,
dimension: Dimension,
}
impl Quantity {
pub fn of(value: Rational, unit: &Unit) -> Quantity {
Quantity {
magnitude: value.mul(&unit.scale).add(&unit.offset),
dimension: unit.dimension,
}
}
pub fn scalar(value: Rational) -> Quantity {
Quantity { magnitude: value, dimension: Dimension::DIMENSIONLESS }
}
pub fn si(magnitude: Rational, dimension: Dimension) -> Quantity {
Quantity { magnitude, dimension }
}
pub fn magnitude_si(&self) -> &Rational {
&self.magnitude
}
pub fn dimension(&self) -> Dimension {
self.dimension
}
pub fn in_unit(&self, unit: &Unit) -> Option<Rational> {
if self.dimension != unit.dimension {
return None;
}
self.magnitude.sub(&unit.offset).div(&unit.scale)
}
pub fn add(&self, other: &Quantity) -> Option<Quantity> {
if self.dimension != other.dimension {
return None;
}
Some(Quantity { magnitude: self.magnitude.add(&other.magnitude), dimension: self.dimension })
}
pub fn sub(&self, other: &Quantity) -> Option<Quantity> {
if self.dimension != other.dimension {
return None;
}
Some(Quantity { magnitude: self.magnitude.sub(&other.magnitude), dimension: self.dimension })
}
pub fn mul(&self, other: &Quantity) -> Quantity {
Quantity {
magnitude: self.magnitude.mul(&other.magnitude),
dimension: self.dimension.mul(other.dimension),
}
}
pub fn div(&self, other: &Quantity) -> Option<Quantity> {
Some(Quantity {
magnitude: self.magnitude.div(&other.magnitude)?,
dimension: self.dimension.div(other.dimension),
})
}
pub fn in_best_unit<'a>(&self, ladder: &'a [Unit]) -> Option<(Rational, &'a Unit)> {
let one = Rational::one();
let mut best: Option<(Rational, &Unit)> = None;
for u in ladder {
let Some(mag) = self.in_unit(u) else { continue };
best = Some(match best {
None => (mag, u),
Some((best_mag, best_u)) => {
let (abs, best_abs) = (mag.abs(), best_mag.abs());
let take = match (abs >= one, best_abs >= one) {
(true, true) => abs < best_abs, (true, false) => true, (false, true) => false, (false, false) => abs > best_abs, };
if take { (mag, u) } else { (best_mag, best_u) }
}
});
}
best
}
}
pub mod units {
use super::Unit;
use crate::dimension::Dimension;
use crate::numeric::Rational;
fn r(n: i64, d: i64) -> Rational {
Rational::from_ratio_i64(n, d).expect("nonzero denominator")
}
fn i(n: i64) -> Rational {
Rational::from_i64(n)
}
fn cube(x: Rational) -> Rational {
x.pow(3).expect("cube exists")
}
fn square(x: Rational) -> Rational {
x.pow(2).expect("square exists")
}
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)) }
fn imp_gallon_m3() -> Rational { r(454_609, 100_000_000) }
pub fn metre() -> Unit { Unit::linear("m", Dimension::length(), Rational::one()) }
pub fn kilometre() -> Unit { Unit::linear("km", Dimension::length(), i(1000)) }
pub fn hectometre() -> Unit { Unit::linear("hm", Dimension::length(), i(100)) }
pub fn dekametre() -> Unit { Unit::linear("dam", Dimension::length(), i(10)) }
pub fn decimetre() -> Unit { Unit::linear("dm", Dimension::length(), r(1, 10)) }
pub fn centimetre() -> Unit { Unit::linear("cm", Dimension::length(), r(1, 100)) }
pub fn millimetre() -> Unit { Unit::linear("mm", Dimension::length(), r(1, 1000)) }
pub fn micrometre() -> Unit { Unit::linear("µm", Dimension::length(), r(1, 1_000_000)) }
pub fn nanometre() -> Unit { Unit::linear("nm", Dimension::length(), r(1, 1_000_000_000)) }
pub fn angstrom() -> Unit { Unit::linear("Å", Dimension::length(), r(1, 10_000_000_000)) }
pub fn inch() -> Unit { Unit::linear("in", Dimension::length(), inch_m()) }
pub fn foot() -> Unit { Unit::linear("ft", Dimension::length(), foot_m()) }
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)) }
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)) }
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()) }
pub fn gram() -> Unit { Unit::linear("g", Dimension::mass(), r(1, 1000)) }
pub fn milligram() -> Unit { Unit::linear("mg", Dimension::mass(), r(1, 1_000_000)) }
pub fn microgram() -> Unit { Unit::linear("µg", Dimension::mass(), r(1, 1_000_000_000)) }
pub fn tonne() -> Unit { Unit::linear("t", Dimension::mass(), i(1000)) }
pub fn pound() -> Unit { Unit::linear("lb", Dimension::mass(), lb_kg()) }
pub fn ounce() -> Unit { Unit::linear("oz", Dimension::mass(), lb_kg().div(&i(16)).unwrap()) }
pub fn stone() -> Unit { Unit::linear("st", Dimension::mass(), lb_kg().mul(&i(14))) }
pub fn short_ton() -> Unit { Unit::linear("ton", Dimension::mass(), lb_kg().mul(&i(2000))) }
pub fn long_ton() -> Unit { Unit::linear("LT", Dimension::mass(), lb_kg().mul(&i(2240))) }
pub fn hundredweight() -> Unit { Unit::linear("cwt", Dimension::mass(), lb_kg().mul(&i(100))) }
pub fn grain() -> Unit { Unit::linear("gr", Dimension::mass(), lb_kg().div(&i(7000)).unwrap()) }
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))) }
pub fn second() -> Unit { Unit::linear("s", Dimension::time(), Rational::one()) }
pub fn millisecond() -> Unit { Unit::linear("ms", Dimension::time(), r(1, 1000)) }
pub fn microsecond() -> Unit { Unit::linear("µs", Dimension::time(), r(1, 1_000_000)) }
pub fn nanosecond() -> Unit { Unit::linear("ns", Dimension::time(), r(1, 1_000_000_000)) }
pub fn minute() -> Unit { Unit::linear("min", Dimension::time(), i(60)) }
pub fn hour() -> Unit { Unit::linear("h", Dimension::time(), i(3600)) }
pub fn day() -> Unit { Unit::linear("day", Dimension::time(), i(86_400)) }
pub fn week() -> Unit { Unit::linear("wk", Dimension::time(), i(604_800)) }
pub fn fortnight() -> Unit { Unit::linear("fortnight", Dimension::time(), i(1_209_600)) }
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)) }
pub fn century() -> Unit { Unit::linear("century", Dimension::time(), i(3_155_760_000)) }
pub fn kelvin() -> Unit { Unit::affine("K", Dimension::temperature(), Rational::one(), Rational::zero()) }
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)) }
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()) }
pub fn litre() -> Unit { Unit::linear("L", Dimension::volume(), r(1, 1000)) }
pub fn millilitre() -> Unit { Unit::linear("mL", Dimension::volume(), r(1, 1_000_000)) }
pub fn centilitre() -> Unit { Unit::linear("cL", Dimension::volume(), r(1, 100_000)) }
pub fn decilitre() -> Unit { Unit::linear("dL", Dimension::volume(), r(1, 10_000)) }
pub fn cubic_centimetre() -> Unit { Unit::linear("cc", Dimension::volume(), r(1, 1_000_000)) }
pub fn cubic_inch() -> Unit { Unit::linear("in³", Dimension::volume(), cube(inch_m())) }
pub fn cubic_foot() -> Unit { Unit::linear("ft³", Dimension::volume(), cube(foot_m())) }
pub fn us_gallon() -> Unit { Unit::linear("gal", Dimension::volume(), us_gallon_m3()) }
pub fn us_quart() -> Unit { Unit::linear("qt", Dimension::volume(), us_gallon_m3().div(&i(4)).unwrap()) }
pub fn us_pint() -> Unit { Unit::linear("pt", Dimension::volume(), us_gallon_m3().div(&i(8)).unwrap()) }
pub fn us_cup() -> Unit { Unit::linear("cup", Dimension::volume(), us_gallon_m3().div(&i(16)).unwrap()) }
pub fn us_gill() -> Unit { Unit::linear("gill", Dimension::volume(), us_gallon_m3().div(&i(32)).unwrap()) }
pub fn us_fluid_ounce() -> Unit { Unit::linear("fl oz", Dimension::volume(), us_gallon_m3().div(&i(128)).unwrap()) }
pub fn tablespoon() -> Unit { Unit::linear("tbsp", Dimension::volume(), us_gallon_m3().div(&i(256)).unwrap()) }
pub fn teaspoon() -> Unit { Unit::linear("tsp", Dimension::volume(), us_gallon_m3().div(&i(768)).unwrap()) }
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()) }
pub fn imperial_pint() -> Unit { Unit::linear("imp pt", Dimension::volume(), imp_gallon_m3().div(&i(8)).unwrap()) }
pub fn imperial_fluid_ounce() -> Unit { Unit::linear("imp fl oz", Dimension::volume(), imp_gallon_m3().div(&i(160)).unwrap()) }
pub fn square_metre() -> Unit { Unit::linear("m²", Dimension::area(), Rational::one()) }
pub fn square_kilometre() -> Unit { Unit::linear("km²", Dimension::area(), i(1_000_000)) }
pub fn square_centimetre() -> Unit { Unit::linear("cm²", Dimension::area(), r(1, 10_000)) }
pub fn square_inch() -> Unit { Unit::linear("in²", Dimension::area(), square(inch_m())) }
pub fn square_foot() -> Unit { Unit::linear("ft²", Dimension::area(), square(foot_m())) }
pub fn square_yard() -> Unit { Unit::linear("yd²", Dimension::area(), square(r(1143, 1250))) }
pub fn square_mile() -> Unit { Unit::linear("mi²", Dimension::area(), square(r(201_168, 125))) }
pub fn hectare() -> Unit { Unit::linear("ha", Dimension::area(), i(10_000)) }
pub fn are() -> Unit { Unit::linear("a", Dimension::area(), i(100)) }
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()) }
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()) }
pub fn foot_per_second() -> Unit { Unit::linear("ft/s", Dimension::speed(), foot_m()) }
pub fn knot() -> Unit { Unit::linear("kn", Dimension::speed(), r(1852, 3600)) }
pub fn hertz() -> Unit { Unit::linear("Hz", Dimension::frequency(), Rational::one()) }
pub fn kilohertz() -> Unit { Unit::linear("kHz", Dimension::frequency(), i(1000)) }
pub fn megahertz() -> Unit { Unit::linear("MHz", Dimension::frequency(), i(1_000_000)) }
pub fn gigahertz() -> Unit { Unit::linear("GHz", Dimension::frequency(), i(1_000_000_000)) }
pub fn rpm() -> Unit { Unit::linear("rpm", Dimension::frequency(), r(1, 60)) }
pub fn newton() -> Unit { Unit::linear("N", Dimension::force(), Rational::one()) }
pub fn kilonewton() -> Unit { Unit::linear("kN", Dimension::force(), i(1000)) }
pub fn dyne() -> Unit { Unit::linear("dyn", Dimension::force(), r(1, 100_000)) }
pub fn kilogram_force() -> Unit { Unit::linear("kgf", Dimension::force(), g0()) }
pub fn pound_force() -> Unit { Unit::linear("lbf", Dimension::force(), lb_kg().mul(&g0())) }
pub fn joule() -> Unit { Unit::linear("J", Dimension::energy(), Rational::one()) }
pub fn kilojoule() -> Unit { Unit::linear("kJ", Dimension::energy(), i(1000)) }
pub fn megajoule() -> Unit { Unit::linear("MJ", Dimension::energy(), i(1_000_000)) }
pub fn calorie() -> Unit { Unit::linear("cal", Dimension::energy(), r(523, 125)) } pub fn kilocalorie() -> Unit { Unit::linear("kcal", Dimension::energy(), r(4184, 1)) }
pub fn watt_hour() -> Unit { Unit::linear("Wh", Dimension::energy(), i(3600)) }
pub fn kilowatt_hour() -> Unit { Unit::linear("kWh", Dimension::energy(), i(3_600_000)) }
pub fn erg() -> Unit { Unit::linear("erg", Dimension::energy(), r(1, 10_000_000)) }
pub fn electronvolt() -> Unit {
use crate::numeric::BigInt;
let scale = Rational::new(BigInt::from_i64(1_602_176_634), BigInt::from_i64(10).pow(28))
.expect("nonzero denominator");
Unit::linear("eV", Dimension::energy(), scale)
}
pub fn watt() -> Unit { Unit::linear("W", Dimension::power(), Rational::one()) }
pub fn kilowatt() -> Unit { Unit::linear("kW", Dimension::power(), i(1000)) }
pub fn megawatt() -> Unit { Unit::linear("MW", Dimension::power(), i(1_000_000)) }
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()) }
pub fn kilopascal() -> Unit { Unit::linear("kPa", Dimension::pressure(), i(1000)) }
pub fn bar() -> Unit { Unit::linear("bar", Dimension::pressure(), i(100_000)) }
pub fn millibar() -> Unit { Unit::linear("mbar", Dimension::pressure(), i(100)) }
pub fn atmosphere() -> Unit { Unit::linear("atm", Dimension::pressure(), i(101_325)) }
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()) }
pub fn byte() -> Unit { Unit::linear("B", Dimension::information(), i(8)) }
pub fn kilobit() -> Unit { Unit::linear("kbit", Dimension::information(), i(1000)) }
pub fn kilobyte() -> Unit { Unit::linear("kB", Dimension::information(), i(8000)) }
pub fn megabyte() -> Unit { Unit::linear("MB", Dimension::information(), i(8_000_000)) }
pub fn gigabyte() -> Unit { Unit::linear("GB", Dimension::information(), i(8_000_000_000)) }
pub fn kibibyte() -> Unit { Unit::linear("KiB", Dimension::information(), i(8 * 1024)) }
pub fn mebibyte() -> Unit { Unit::linear("MiB", Dimension::information(), i(8 * 1024 * 1024)) }
pub fn gibibyte() -> Unit { Unit::linear("GiB", Dimension::information(), i(8 * 1024 * 1024 * 1024)) }
pub fn turn() -> Unit { Unit::linear("turn", Dimension::angle(), Rational::one()) }
pub fn degree() -> Unit { Unit::linear("°", Dimension::angle(), r(1, 360)) }
pub fn gradian() -> Unit { Unit::linear("grad", Dimension::angle(), r(1, 400)) }
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()) }
pub fn milliampere() -> Unit { Unit::linear("mA", Dimension::current(), r(1, 1000)) }
pub fn coulomb() -> Unit { Unit::linear("C", Dimension::charge(), Rational::one()) }
pub fn ampere_hour() -> Unit { Unit::linear("Ah", Dimension::charge(), i(3600)) }
pub fn milliampere_hour() -> Unit { Unit::linear("mAh", Dimension::charge(), r(18, 5)) } pub fn volt() -> Unit { Unit::linear("V", Dimension::voltage(), Rational::one()) }
pub fn millivolt() -> Unit { Unit::linear("mV", Dimension::voltage(), r(1, 1000)) }
pub fn kilovolt() -> Unit { Unit::linear("kV", Dimension::voltage(), i(1000)) }
pub fn ohm() -> Unit { Unit::linear("Ω", Dimension::resistance(), Rational::one()) }
pub fn mole() -> Unit { Unit::linear("mol", Dimension::amount(), Rational::one()) }
pub fn candela() -> Unit { Unit::linear("cd", Dimension::luminous(), Rational::one()) }
fn e(mantissa: i64, pow10: u32) -> Rational {
use crate::numeric::BigInt;
Rational::from_bigint(BigInt::from_i64(mantissa).mul(&BigInt::from_i64(10).pow(pow10)))
}
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()) }
pub fn peck() -> Unit { Unit::linear("pk", Dimension::volume(), us_bushel_m3().div(&i(4)).unwrap()) }
pub fn dry_gallon() -> Unit { Unit::linear("dry gal", Dimension::volume(), us_bushel_m3().div(&i(8)).unwrap()) }
pub fn dry_quart() -> Unit { Unit::linear("dry qt", Dimension::volume(), us_bushel_m3().div(&i(32)).unwrap()) }
pub fn dry_pint() -> Unit { Unit::linear("dry pt", Dimension::volume(), us_bushel_m3().div(&i(64)).unwrap()) }
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)) }
pub fn light_hour() -> Unit { Unit::linear("lh", Dimension::length(), i(299_792_458 * 3600)) }
pub fn light_day() -> Unit { Unit::linear("ld", Dimension::length(), i(299_792_458 * 86_400)) }
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)) }
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()) }
pub fn kilobit_per_second() -> Unit { Unit::linear("kbps", Dimension::data_rate(), i(1000)) }
pub fn megabit_per_second() -> Unit { Unit::linear("Mbps", Dimension::data_rate(), i(1_000_000)) }
pub fn gigabit_per_second() -> Unit { Unit::linear("Gbps", Dimension::data_rate(), i(1_000_000_000)) }
pub fn byte_per_second() -> Unit { Unit::linear("B/s", Dimension::data_rate(), i(8)) }
pub fn megabyte_per_second() -> Unit { Unit::linear("MB/s", Dimension::data_rate(), i(8_000_000)) }
pub fn cubic_metre_per_second() -> Unit { Unit::linear("m³/s", Dimension::volumetric_flow(), Rational::one()) }
pub fn litre_per_second() -> Unit { Unit::linear("L/s", Dimension::volumetric_flow(), r(1, 1000)) }
pub fn litre_per_minute() -> Unit { Unit::linear("L/min", Dimension::volumetric_flow(), r(1, 60_000)) }
pub fn gallon_per_minute() -> Unit { Unit::linear("gpm", Dimension::volumetric_flow(), us_gallon_m3().div(&i(60)).unwrap()) }
pub fn cubic_foot_per_minute() -> Unit { Unit::linear("cfm", Dimension::volumetric_flow(), cube(foot_m()).div(&i(60)).unwrap()) }
pub fn molar() -> Unit { Unit::linear("M", Dimension::molar_concentration(), i(1000)) } pub fn millimolar() -> Unit { Unit::linear("mM", Dimension::molar_concentration(), Rational::one()) }
pub fn micromolar() -> Unit { Unit::linear("µM", Dimension::molar_concentration(), r(1, 1000)) }
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()) }
pub fn poise() -> Unit { Unit::linear("P", Dimension::dynamic_viscosity(), r(1, 10)) }
pub fn centipoise() -> Unit { Unit::linear("cP", Dimension::dynamic_viscosity(), r(1, 1000)) }
pub fn square_metre_per_second() -> Unit { Unit::linear("m²/s", Dimension::kinematic_viscosity(), Rational::one()) }
pub fn stokes() -> Unit { Unit::linear("St", Dimension::kinematic_viscosity(), r(1, 10_000)) }
pub fn centistokes() -> Unit { Unit::linear("cSt", Dimension::kinematic_viscosity(), r(1, 1_000_000)) }
pub fn mile_per_gallon() -> Unit { Unit::linear("mpg", Dimension::fuel_economy(), r(201_168, 125).div(&us_gallon_m3()).unwrap()) }
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()) }
pub fn pound_foot() -> Unit { Unit::linear("lb·ft", Dimension::energy(), lb_kg().mul(&g0()).mul(&foot_m())) }
pub fn megametre() -> Unit { Unit::linear("Mm", Dimension::length(), i(1_000_000)) }
pub fn gigametre() -> Unit { Unit::linear("Gm", Dimension::length(), i(1_000_000_000)) }
pub fn picometre() -> Unit { Unit::linear("pm", Dimension::length(), r(1, 1_000_000_000_000)) }
pub fn femtometre() -> Unit { Unit::linear("fm", Dimension::length(), r(1, 1_000_000_000_000_000)) }
pub fn nanogram() -> Unit { Unit::linear("ng", Dimension::mass(), r(1, 1_000_000_000_000)) }
pub fn picosecond() -> Unit { Unit::linear("ps", Dimension::time(), r(1, 1_000_000_000_000)) }
pub fn gigawatt() -> Unit { Unit::linear("GW", Dimension::power(), i(1_000_000_000)) }
pub fn terawatt() -> Unit { Unit::linear("TW", Dimension::power(), i(1_000_000_000_000)) }
pub fn gigajoule() -> Unit { Unit::linear("GJ", Dimension::energy(), i(1_000_000_000)) }
pub fn terajoule() -> Unit { Unit::linear("TJ", Dimension::energy(), i(1_000_000_000_000)) }
pub fn terabit() -> Unit { Unit::linear("Tbit", Dimension::information(), i(1_000_000_000_000)) }
pub fn terabyte() -> Unit { Unit::linear("TB", Dimension::information(), i(8_000_000_000_000)) }
pub fn petabyte() -> Unit { Unit::linear("PB", Dimension::information(), i(8_000_000_000_000_000)) }
pub fn siemens() -> Unit { Unit::linear("S", Dimension::conductance(), Rational::one()) }
pub fn millisiemens() -> Unit { Unit::linear("mS", Dimension::conductance(), r(1, 1000)) }
pub fn farad() -> Unit { Unit::linear("F", Dimension::capacitance(), Rational::one()) }
pub fn microfarad() -> Unit { Unit::linear("µF", Dimension::capacitance(), r(1, 1_000_000)) }
pub fn nanofarad() -> Unit { Unit::linear("nF", Dimension::capacitance(), r(1, 1_000_000_000)) }
pub fn picofarad() -> Unit { Unit::linear("pF", Dimension::capacitance(), r(1, 1_000_000_000_000)) }
pub fn weber() -> Unit { Unit::linear("Wb", Dimension::magnetic_flux(), Rational::one()) }
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()) }
pub fn millihenry() -> Unit { Unit::linear("mH", Dimension::inductance(), r(1, 1000)) }
pub fn microhenry() -> Unit { Unit::linear("µH", Dimension::inductance(), r(1, 1_000_000)) }
pub fn tesla() -> Unit { Unit::linear("T", Dimension::magnetic_flux_density(), Rational::one()) }
pub fn millitesla() -> Unit { Unit::linear("mT", Dimension::magnetic_flux_density(), r(1, 1000)) }
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()) }
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()) }
pub fn pair() -> Unit { Unit::linear("pair", Dimension::DIMENSIONLESS, i(2)) }
pub fn dozen() -> Unit { Unit::linear("dz", Dimension::DIMENSIONLESS, i(12)) }
pub fn baker_dozen() -> Unit { Unit::linear("baker's dz", Dimension::DIMENSIONLESS, i(13)) }
pub fn score() -> Unit { Unit::linear("score", Dimension::DIMENSIONLESS, i(20)) }
pub fn gross() -> Unit { Unit::linear("gross", Dimension::DIMENSIONLESS, i(144)) }
pub fn great_gross() -> Unit { Unit::linear("great gross", Dimension::DIMENSIONLESS, i(1728)) }
pub fn ream() -> Unit { Unit::linear("ream", Dimension::DIMENSIONLESS, i(500)) }
pub fn percent() -> Unit { Unit::linear("%", Dimension::DIMENSIONLESS, r(1, 100)) }
pub fn permille() -> Unit { Unit::linear("‰", Dimension::DIMENSIONLESS, r(1, 1000)) }
pub fn ppm() -> Unit { Unit::linear("ppm", Dimension::DIMENSIONLESS, r(1, 1_000_000)) }
pub fn ppb() -> Unit { Unit::linear("ppb", Dimension::DIMENSIONLESS, r(1, 1_000_000_000)) }
pub fn basis_point() -> Unit { Unit::linear("bp", Dimension::DIMENSIONLESS, r(1, 10_000)) }
pub fn by_name(name: &str) -> Option<Unit> {
let n = name.trim().to_ascii_lowercase();
let u = match n.as_str() {
"meter" | "metre" | "meters" | "metres" | "m" => metre(),
"kilometer" | "kilometre" | "kilometers" | "kilometres" | "km" => kilometre(),
"centimeter" | "centimetre" | "centimeters" | "centimetres" | "cm" => centimetre(),
"millimeter" | "millimetre" | "millimeters" | "millimetres" | "mm" => millimetre(),
"micrometer" | "micrometre" | "micron" | "microns" | "µm" | "um" => micrometre(),
"nanometer" | "nanometre" | "nanometers" | "nanometres" | "nm" => nanometre(),
"inch" | "inches" | "in" => inch(),
"foot" | "feet" | "ft" => foot(),
"yard" | "yards" | "yd" => yard(),
"mile" | "miles" | "mi" => mile(),
"nautical mile" | "nautical miles" | "nmi" => nautical_mile(),
"angstrom" | "angstroms" | "ångström" | "å" => angstrom(),
"light year" | "light years" | "lightyear" | "ly" => light_year(),
"astronomical unit" | "astronomical units" | "au" => astronomical_unit(),
"kilogram" | "kilograms" | "kg" => kilogram(),
"gram" | "grams" | "g" => gram(),
"milligram" | "milligrams" | "mg" => milligram(),
"microgram" | "micrograms" | "µg" | "ug" => microgram(),
"tonne" | "tonnes" | "metric ton" | "metric tons" | "t" => tonne(),
"pound" | "pounds" | "lb" | "lbs" => pound(),
"ounce" | "ounces" | "oz" => ounce(),
"stone" | "stones" | "st" => stone(),
"carat" | "carats" | "ct" => carat(),
"second" | "seconds" | "sec" | "secs" | "s" => second(),
"millisecond" | "milliseconds" | "ms" => millisecond(),
"microsecond" | "microseconds" | "µs" | "us" => microsecond(),
"nanosecond" | "nanoseconds" | "ns" => nanosecond(),
"minute" | "minutes" | "min" | "mins" => minute(),
"hour" | "hours" | "hr" | "hrs" | "h" => hour(),
"day" | "days" => day(),
"week" | "weeks" | "wk" => week(),
"year" | "years" | "yr" | "yrs" => julian_year(),
"kelvin" | "k" => kelvin(),
"celsius" | "centigrade" | "°c" | "c" => celsius(),
"fahrenheit" | "°f" | "f" => fahrenheit(),
"rankine" | "°r" => rankine(),
"reaumur" | "réaumur" | "°ré" => reaumur(),
"liter" | "litre" | "liters" | "litres" | "l" => litre(),
"milliliter" | "millilitre" | "milliliters" | "millilitres" | "ml" => millilitre(),
"cubic meter" | "cubic metre" | "m3" | "m³" => cubic_metre(),
"gallon" | "gallons" | "gal" => us_gallon(),
"quart" | "quarts" | "qt" => us_quart(),
"pint" | "pints" | "pt" => us_pint(),
"cup" | "cups" => us_cup(),
"tablespoon" | "tablespoons" | "tbsp" => tablespoon(),
"teaspoon" | "teaspoons" | "tsp" => teaspoon(),
"fluid ounce" | "fluid ounces" | "fl oz" => us_fluid_ounce(),
"square meter" | "square metre" | "square meters" | "square metres" | "m2" => square_metre(),
"square foot" | "square feet" | "sq ft" => square_foot(),
"square inch" | "square inches" | "sq in" => square_inch(),
"hectare" | "hectares" | "ha" => hectare(),
"acre" | "acres" => acre(),
"meter per second" | "metre per second" | "meters per second" | "m/s" => metre_per_second(),
"kilometer per hour" | "kilometre per hour" | "km/h" | "kph" => kilometre_per_hour(),
"mile per hour" | "miles per hour" | "mph" => mile_per_hour(),
"knot" | "knots" | "kn" => knot(),
"hertz" | "hz" => hertz(),
"kilohertz" | "khz" => kilohertz(),
"megahertz" | "mhz" => megahertz(),
"gigahertz" | "ghz" => gigahertz(),
"joule" | "joules" | "j" => joule(),
"kilojoule" | "kilojoules" | "kj" => kilojoule(),
"calorie" | "calories" | "cal" => calorie(),
"kilocalorie" | "kilocalories" | "kcal" => kilocalorie(),
"watt hour" | "watt hours" | "wh" => watt_hour(),
"kilowatt hour" | "kilowatt hours" | "kwh" => kilowatt_hour(),
"electronvolt" | "electronvolts" | "ev" => electronvolt(),
"watt" | "watts" | "w" => watt(),
"kilowatt" | "kilowatts" | "kw" => kilowatt(),
"megawatt" | "megawatts" | "mw" => megawatt(),
"horsepower" | "hp" => horsepower(),
"pascal" | "pascals" | "pa" => pascal(),
"kilopascal" | "kilopascals" | "kpa" => kilopascal(),
"bar" | "bars" => bar(),
"atmosphere" | "atmospheres" | "atm" => atmosphere(),
"psi" => psi(),
"bit" | "bits" => bit(),
"byte" | "bytes" => byte(),
"kilobyte" | "kilobytes" | "kb" => kilobyte(),
"megabyte" | "megabytes" | "mb" => megabyte(),
"gigabyte" | "gigabytes" | "gb" => gigabyte(),
"turn" | "turns" => turn(),
"degree" | "degrees" | "deg" | "°" => degree(),
"gradian" | "gradians" | "grad" => gradian(),
"arcminute" | "arcminutes" => arcminute(),
"arcsecond" | "arcseconds" => arcsecond(),
"gray" | "grays" | "gy" => gray(),
"sievert" | "sieverts" | "sv" => sievert(),
"rad" | "rads" => rad_unit(),
"rem" | "rems" => rem(),
"becquerel" | "becquerels" | "bq" => becquerel(),
"curie" | "curies" | "ci" => curie(),
"roentgen" | "roentgens" => roentgen(),
"katal" | "katals" | "kat" => katal(),
"lumen" | "lumens" | "lm" => lumen(),
"lux" | "lx" => lux(),
"phot" | "phots" => phot(),
"foot candle" | "foot-candle" | "footcandle" | "fc" => foot_candle(),
"nit" | "nits" => nit(),
"siemens" => siemens(),
"farad" | "farads" => farad(),
"microfarad" | "microfarads" | "µf" | "uf" => microfarad(),
"nanofarad" | "nanofarads" | "nf" => nanofarad(),
"picofarad" | "picofarads" | "pf" => picofarad(),
"weber" | "webers" | "wb" => weber(),
"maxwell" | "maxwells" | "mx" => maxwell(),
"henry" | "henries" => henry(),
"tesla" | "teslas" => tesla(),
"gauss" => gauss(),
"pascal second" | "pascal-second" | "pa·s" | "pas" => pascal_second(),
"poise" => poise(),
"centipoise" | "cp" => centipoise(),
"stokes" | "stoke" => stokes(),
"centistokes" | "cst" => centistokes(),
"gallon per minute" | "gpm" => gallon_per_minute(),
"cubic foot per minute" | "cfm" => cubic_foot_per_minute(),
"litre per second" | "liter per second" | "l/s" => litre_per_second(),
"molar" => molar(),
"millimolar" | "mm/l" => millimolar(),
"bit per second" | "bits per second" | "bps" => bit_per_second(),
"kilobit per second" | "kbps" => kilobit_per_second(),
"megabit per second" | "mbps" => megabit_per_second(),
"gigabit per second" | "gbps" => gigabit_per_second(),
"byte per second" | "bytes per second" => byte_per_second(),
"newton per meter" | "newton per metre" | "n/m" => newton_per_metre(),
"stick" | "sticks" | "stick of butter" => stick_of_butter(),
"dash" | "dashes" => dash(),
"pinch" | "pinches" => pinch(),
"smidgen" | "smidgens" => smidgen(),
"fathom" | "fathoms" => fathom(),
"cable" | "cables" => cable(),
"league" | "leagues" => league(),
"light second" | "light-second" | "light seconds" => light_second(),
"light minute" | "light-minute" | "light minutes" => light_minute(),
"each" | "ea" | "count" => each(),
"pair" | "pairs" => pair(),
"dozen" | "dozens" | "dz" => dozen(),
"score" | "scores" => score(),
"gross" => gross(),
"ream" | "reams" => ream(),
"percent" | "percents" | "%" => percent(),
"permille" | "per mille" | "‰" => permille(),
"ppm" => ppm(),
"ppb" => ppb(),
"basis point" | "basis points" => basis_point(),
"megameter" | "megametre" => megametre(),
"gigameter" | "gigametre" => gigametre(),
"picometer" | "picometre" | "pm" => picometre(),
"nanogram" | "nanograms" => nanogram(),
"gigawatt" | "gigawatts" | "gw" => gigawatt(),
"terawatt" | "terawatts" | "tw" => terawatt(),
"terabyte" | "terabytes" | "tb" => terabyte(),
"petabyte" | "petabytes" | "pb" => petabyte(),
_ => return None,
};
Some(u)
}
}
pub mod constants {
use super::Quantity;
use crate::dimension::Dimension;
use crate::numeric::{BigInt, Rational};
fn ep(mantissa: i64, pow10: u32) -> Rational {
Rational::from_bigint(BigInt::from_i64(mantissa).mul(&BigInt::from_i64(10).pow(pow10)))
}
fn en(mantissa: i64, pow10: u32) -> Rational {
Rational::new(BigInt::from_i64(mantissa), BigInt::from_i64(10).pow(pow10)).unwrap()
}
fn r(n: i64, d: i64) -> Rational {
Rational::new(BigInt::from_i64(n), BigInt::from_i64(d)).unwrap()
}
fn action() -> Dimension { Dimension::energy().mul(Dimension::time()) }
pub fn speed_of_light() -> Quantity { Quantity::si(ep(299_792_458, 0), Dimension::speed()) }
pub fn planck_constant() -> Quantity { Quantity::si(en(662_607_015, 42), action()) }
pub fn elementary_charge() -> Quantity { Quantity::si(en(1_602_176_634, 28), Dimension::charge()) }
pub fn boltzmann_constant() -> Quantity {
Quantity::si(en(1_380_649, 29), Dimension::energy().div(Dimension::temperature()))
}
pub fn avogadro_constant() -> Quantity {
Quantity::si(ep(602_214_076, 15), Dimension::amount().recip())
}
pub fn molar_gas_constant() -> Quantity {
avogadro_constant().mul(&boltzmann_constant())
}
pub fn gravitational_constant() -> Quantity {
let dim = Dimension::volume().div(Dimension::mass()).div(Dimension::time().powi(2));
Quantity::si(en(667_430, 16), dim)
}
pub fn standard_gravity() -> Quantity { Quantity::si(r(980_665, 100_000), Dimension::acceleration()) }
pub fn standard_atmosphere() -> Quantity { Quantity::si(ep(101_325, 0), Dimension::pressure()) }
}
pub fn light_travel_time(distance: &Quantity) -> Option<Quantity> {
if distance.dimension() != Dimension::length() {
return None;
}
distance.div(&constants::speed_of_light())
}
pub fn time_quantity_to_nanos(q: &Quantity) -> Option<i64> {
if q.dimension() != Dimension::time() {
return None;
}
q.magnitude_si().mul(&Rational::from_i64(1_000_000_000)).round().to_i64()
}
pub fn observed_arrival_nanos(event_nanos: i64, distance: &Quantity) -> Option<i64> {
let delay = time_quantity_to_nanos(&light_travel_time(distance)?)?;
event_nanos.checked_add(delay)
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Position {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Position {
pub fn distance_to(self, other: Position) -> f64 {
let (dx, dy, dz) = (self.x - other.x, self.y - other.y, self.z - other.z);
(dx * dx + dy * dy + dz * dz).sqrt()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SpacetimeStamp {
pub instant_nanos: i64,
pub position: Position,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CausalRelation {
Before,
After,
Concurrent,
}
impl SpacetimeStamp {
pub fn light_separation_nanos(self, other: SpacetimeStamp) -> i64 {
let metres = self.position.distance_to(other.position);
(metres / 299_792_458.0 * 1e9).round() as i64
}
pub fn causal_relation(self, other: SpacetimeStamp) -> CausalRelation {
let dt = other.instant_nanos as i128 - self.instant_nanos as i128;
let light = self.light_separation_nanos(other) as i128;
if dt >= light {
CausalRelation::Before } else if -dt >= light {
CausalRelation::After } else {
CausalRelation::Concurrent }
}
pub fn observed_at_nanos(self, observer: Position) -> i64 {
let other = SpacetimeStamp { instant_nanos: self.instant_nanos, position: observer };
self.instant_nanos + self.light_separation_nanos(other)
}
}
pub fn lorentz_factor(beta: f64) -> Option<f64> {
if !(beta.abs() < 1.0) {
return None;
}
Some(1.0 / (1.0 - beta * beta).sqrt())
}
pub fn proper_time_seconds(coordinate_seconds: f64, beta: f64) -> Option<f64> {
Some(coordinate_seconds / lorentz_factor(beta)?)
}
pub fn time_dilation_factor(velocity: &Quantity) -> Option<f64> {
if velocity.dimension() != Dimension::speed() {
return None;
}
let c = constants::speed_of_light();
let beta = velocity.magnitude_si().to_f64() / c.magnitude_si().to_f64();
lorentz_factor(beta)
}
#[cfg(test)]
mod tests {
use super::units::*;
use super::*;
fn r(n: i64, d: i64) -> Rational {
Rational::from_ratio_i64(n, d).unwrap()
}
fn i(n: i64) -> Rational {
Rational::from_i64(n)
}
#[test]
fn golden_two_inches_plus_five_cm_in_feet_is_exactly_42_over_127() {
let a = Quantity::of(i(2), &inch());
let b = Quantity::of(i(5), ¢imetre());
let sum = a.add(&b).expect("both are Length");
assert_eq!(sum.dimension(), Dimension::length());
assert_eq!(sum.in_unit(&metre()).unwrap(), r(63, 625), "= 63/625 m exactly");
assert_eq!(sum.in_unit(&foot()).unwrap(), r(42, 127), "= 42/127 ft exactly");
assert_ne!((42.0_f64 / 127.0) as f32 as f64, 42.0 / 127.0);
}
#[test]
fn exact_unit_conversions_within_a_dimension() {
assert_eq!(Quantity::of(i(1), &mile()).in_unit(&foot()).unwrap(), i(5280));
assert_eq!(Quantity::of(i(1), &foot()).in_unit(&inch()).unwrap(), i(12));
assert_eq!(Quantity::of(i(1), &yard()).in_unit(&foot()).unwrap(), i(3));
assert_eq!(Quantity::of(i(1), &kilometre()).in_unit(&metre()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &kilogram()).in_unit(&gram()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), £()).in_unit(&ounce()).unwrap(), i(16));
assert_eq!(Quantity::of(i(1), &hour()).in_unit(&minute()).unwrap(), i(60));
assert_eq!(Quantity::of(i(1), £()).in_unit(&gram()).unwrap(), r(45_359_237, 100_000));
}
#[test]
fn affine_temperature_conversion_is_offset_aware_and_exact() {
assert_eq!(Quantity::of(i(0), &celsius()).in_unit(&kelvin()).unwrap(), r(5463, 20));
assert_eq!(Quantity::of(i(20), &celsius()).in_unit(&fahrenheit()).unwrap(), i(68));
assert_eq!(Quantity::of(i(100), &celsius()).in_unit(&fahrenheit()).unwrap(), i(212));
assert_eq!(Quantity::of(i(-40), &celsius()).in_unit(&fahrenheit()).unwrap(), i(-40));
assert_eq!(Quantity::of(i(212), &fahrenheit()).in_unit(&celsius()).unwrap(), i(100));
}
#[test]
fn cross_dimension_operations_are_forbidden() {
let length = Quantity::of(i(1), &metre());
let mass = Quantity::of(i(1), &kilogram());
assert!(length.add(&mass).is_none(), "Length + Mass is forbidden");
assert!(length.sub(&mass).is_none(), "Length − Mass is forbidden");
assert!(length.in_unit(&kilogram()).is_none(), "Length → Mass cast is forbidden");
}
#[test]
fn multiplication_and_division_combine_dimensions() {
let area = Quantity::of(i(3), &metre()).mul(&Quantity::of(i(4), &metre()));
assert_eq!(area.dimension(), Dimension::area());
assert_eq!(area.magnitude_si(), &i(12)); let speed = Quantity::of(i(100), &metre()).div(&Quantity::of(i(10), &second())).unwrap();
assert_eq!(speed.dimension(), Dimension::speed());
assert_eq!(speed.magnitude_si(), &i(10)); let back = area.div(&Quantity::of(i(3), &metre())).unwrap();
assert_eq!(back.dimension(), Dimension::length());
let scaled = Quantity::of(i(2), &metre()).mul(&Quantity::scalar(i(3)));
assert_eq!(scaled.dimension(), Dimension::length());
assert_eq!(scaled.in_unit(&metre()).unwrap(), i(6));
}
#[test]
fn dimensional_algebra_gauntlet_produces_correct_derived_dimensions_and_magnitudes() {
let accel = Quantity::of(i(3), &metre_per_second()).div(&Quantity::of(i(1), &second())).unwrap();
let force = Quantity::of(i(2), &kilogram()).mul(&accel);
assert_eq!(force.dimension(), Dimension::force());
assert_eq!(force.magnitude_si(), &i(6)); let energy = force.mul(&Quantity::of(i(4), &metre()));
assert_eq!(energy.dimension(), Dimension::energy());
assert_eq!(energy.magnitude_si(), &i(24)); let power = energy.div(&Quantity::of(i(2), &second())).unwrap();
assert_eq!(power.dimension(), Dimension::power());
assert_eq!(power.magnitude_si(), &i(12)); let pressure = force.div(&Quantity::of(i(2), &square_metre())).unwrap();
assert_eq!(pressure.dimension(), Dimension::pressure());
assert_eq!(pressure.magnitude_si(), &i(3)); let charge = Quantity::of(i(5), &ere()).mul(&Quantity::of(i(2), &second()));
assert_eq!(charge.dimension(), Dimension::charge());
assert_eq!(charge.magnitude_si(), &i(10)); let rate = Quantity::of(i(16), &bit()).div(&Quantity::of(i(2), &second())).unwrap();
assert_eq!(rate.dimension(), Dimension::data_rate());
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();
assert_eq!(flow.dimension(), Dimension::volumetric_flow());
assert_eq!(flow.in_unit(&cubic_metre_per_second()).unwrap(), i(3));
let conc = Quantity::of(i(2), &mole()).div(&Quantity::of(i(1), &cubic_metre())).unwrap();
assert_eq!(conc.dimension(), Dimension::molar_concentration());
assert_eq!(conc.in_unit(&millimolar()).unwrap(), i(2));
let kv = Quantity::of(i(4), &square_metre()).div(&Quantity::of(i(2), &second())).unwrap();
assert_eq!(kv.dimension(), Dimension::kinematic_viscosity());
assert_eq!(kv.in_unit(&square_metre_per_second()).unwrap(), i(2));
let dv = Quantity::of(i(4), &pascal()).mul(&Quantity::of(i(2), &second()));
assert_eq!(dv.dimension(), Dimension::dynamic_viscosity());
assert_eq!(dv.in_unit(&pascal_second()).unwrap(), i(8));
let fe = Quantity::of(i(100), &kilometre()).div(&Quantity::of(i(10), &litre())).unwrap();
assert_eq!(fe.dimension(), Dimension::fuel_economy());
assert_eq!(fe.in_unit(&km_per_litre()).unwrap(), i(10));
let mu = Quantity::of(i(6), &pascal_second());
let rho = Quantity::of(i(2), &kilogram()).div(&Quantity::of(i(1), &cubic_metre())).unwrap();
let nu = mu.div(&rho).unwrap();
assert_eq!(nu.dimension(), Dimension::kinematic_viscosity());
assert_eq!(nu.in_unit(&square_metre_per_second()).unwrap(), i(3)); }
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
}
#[test]
fn conversion_round_trips_and_add_commutes_under_fuzz() {
let length_units = [metre(), kilometre(), centimetre(), inch(), foot(), yard(), mile()];
let mass_units = [kilogram(), gram(), pound(), ounce()];
let temp_units = [kelvin(), celsius(), fahrenheit()];
let mut rng = Rng(0x_FACE_C0DE_1234_5678);
for _ in 0..2000 {
let n = (rng.next() % 4001) as i64 - 2000;
let d = ((rng.next() % 99) as i64) + 1;
let value = r(n, d);
for set in [&length_units[..], &mass_units[..], &temp_units[..]] {
let u1 = &set[(rng.next() as usize) % set.len()];
let u2 = &set[(rng.next() as usize) % set.len()];
let q = Quantity::of(value.clone(), u1);
assert_eq!(q.in_unit(u1).unwrap(), value, "round-trip in {}", u1.symbol);
let via = Quantity::of(q.in_unit(u2).unwrap(), u2);
assert_eq!(via.in_unit(u1).unwrap(), value, "{} → {} → {}", u1.symbol, u2.symbol, u1.symbol);
let q2 = Quantity::of(value.clone(), u2);
assert_eq!(q.add(&q2), q2.add(&q), "add commutes");
}
}
}
#[test]
fn whole_catalog_round_trips_through_every_sibling_under_fuzz() {
let groups = catalog();
let mut rng = Rng(0x_C0FF_EE_D0_0D_F0_0D);
for _ in 0..400 {
let n = (rng.next() % 20_001) as i64 - 10_000;
let d = ((rng.next() % 199) as i64) + 1;
let value = r(n, d);
for group in &groups {
let u1 = &group[(rng.next() as usize) % group.len()];
let u2 = &group[(rng.next() as usize) % group.len()];
let q = Quantity::of(value.clone(), u1);
let through = q.in_unit(u2).expect("same-dimension conversion is total");
let back = Quantity::of(through, u2).in_unit(u1).unwrap();
assert_eq!(back, value, "{} → {} → {}", u1.symbol, u2.symbol, u1.symbol);
}
}
}
#[test]
fn affine_temperature_fixed_points_are_exact() {
assert_eq!(Quantity::of(i(0), &celsius()).in_unit(&kelvin()).unwrap(), r(27315, 100));
assert_eq!(Quantity::of(i(0), &celsius()).in_unit(&fahrenheit()).unwrap(), i(32));
assert_eq!(Quantity::of(i(0), &celsius()).in_unit(&rankine()).unwrap(), r(49167, 100));
assert_eq!(Quantity::of(i(0), &celsius()).in_unit(&reaumur()).unwrap(), i(0));
assert_eq!(Quantity::of(i(100), &celsius()).in_unit(&fahrenheit()).unwrap(), i(212));
assert_eq!(Quantity::of(i(100), &celsius()).in_unit(&kelvin()).unwrap(), r(37315, 100));
assert_eq!(Quantity::of(i(100), &celsius()).in_unit(&reaumur()).unwrap(), i(80));
assert_eq!(Quantity::of(i(-40), &celsius()).in_unit(&fahrenheit()).unwrap(), i(-40));
assert_eq!(Quantity::of(i(0), &kelvin()).in_unit(&celsius()).unwrap(), r(-27315, 100));
assert_eq!(Quantity::of(i(0), &kelvin()).in_unit(&fahrenheit()).unwrap(), r(-45967, 100));
assert_eq!(Quantity::of(i(0), &kelvin()).in_unit(&rankine()).unwrap(), i(0));
assert_eq!(Quantity::of(i(37), &celsius()).in_unit(&fahrenheit()).unwrap(), r(986, 10));
let v = r(37, 7);
for u in [kelvin(), celsius(), fahrenheit(), rankine(), reaumur()] {
assert_eq!(Quantity::of(v.clone(), &u).in_unit(&u).unwrap(), v, "round-trip {}", u.symbol);
}
}
#[test]
fn cooking_measurements_convert_exactly() {
assert_eq!(Quantity::of(i(1), &us_cup()).in_unit(&tablespoon()).unwrap(), i(16), "1 cup = 16 tbsp");
assert_eq!(Quantity::of(i(1), &us_cup()).in_unit(&teaspoon()).unwrap(), i(48), "1 cup = 48 tsp");
assert_eq!(Quantity::of(i(1), &us_cup()).in_unit(&us_fluid_ounce()).unwrap(), i(8), "1 cup = 8 fl oz");
assert_eq!(Quantity::of(i(1), &tablespoon()).in_unit(&teaspoon()).unwrap(), i(3), "1 tbsp = 3 tsp");
assert_eq!(Quantity::of(i(1), &us_fluid_ounce()).in_unit(&tablespoon()).unwrap(), i(2), "1 fl oz = 2 tbsp");
assert_eq!(Quantity::of(i(1), &us_pint()).in_unit(&us_cup()).unwrap(), i(2), "1 pint = 2 cups");
assert_eq!(Quantity::of(i(1), &us_quart()).in_unit(&us_cup()).unwrap(), i(4), "1 quart = 4 cups");
assert_eq!(Quantity::of(i(1), &us_gallon()).in_unit(&us_cup()).unwrap(), i(16), "1 gallon = 16 cups");
assert_eq!(Quantity::of(i(1), &us_gallon()).in_unit(&teaspoon()).unwrap(), i(768), "1 gallon = 768 tsp");
assert_eq!(Quantity::of(i(1), &oil_barrel()).in_unit(&us_gallon()).unwrap(), i(42), "1 oil barrel = 42 gal");
assert_eq!(Quantity::of(i(1), &us_gallon()).in_unit(&litre()).unwrap(), r(473_176_473, 125_000_000));
let mixed = Quantity::of(i(3), &teaspoon()).add(&Quantity::of(i(1), &tablespoon())).unwrap();
assert_eq!(mixed.in_unit(&tablespoon()).unwrap(), i(2));
}
#[test]
fn general_cross_unit_conversions_are_exact() {
assert_eq!(Quantity::of(i(1), &cubic_metre()).in_unit(&litre()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &cubic_foot()).in_unit(&cubic_inch()).unwrap(), i(1728));
assert_eq!(Quantity::of(i(1), &litre()).in_unit(&millilitre()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &acre()).in_unit(&square_foot()).unwrap(), i(43_560));
assert_eq!(Quantity::of(i(1), &hectare()).in_unit(&square_metre()).unwrap(), i(10_000));
assert_eq!(Quantity::of(i(1), &square_foot()).in_unit(&square_inch()).unwrap(), i(144));
assert_eq!(Quantity::of(i(1), &kilometre_per_hour()).in_unit(&metre_per_second()).unwrap(), r(5, 18));
assert_eq!(Quantity::of(i(1), &day()).in_unit(&hour()).unwrap(), i(24));
assert_eq!(Quantity::of(i(1), &fortnight()).in_unit(&day()).unwrap(), i(14));
assert_eq!(Quantity::of(i(1), &gigahertz()).in_unit(&hertz()).unwrap(), i(1_000_000_000));
assert_eq!(Quantity::of(i(1), &kilocalorie()).in_unit(&calorie()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &kilowatt_hour()).in_unit(&joule()).unwrap(), i(3_600_000));
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));
assert_eq!(Quantity::of(i(1), &byte()).in_unit(&bit()).unwrap(), i(8));
assert_eq!(Quantity::of(i(1), &kibibyte()).in_unit(&byte()).unwrap(), i(1024));
assert_eq!(Quantity::of(i(1), &mebibyte()).in_unit(&kibibyte()).unwrap(), i(1024));
assert_eq!(Quantity::of(i(1), &megabyte()).in_unit(&byte()).unwrap(), i(1_000_000));
assert_eq!(Quantity::of(i(1), &turn()).in_unit(°ree()).unwrap(), i(360));
assert_eq!(Quantity::of(i(1), °ree()).in_unit(&arcminute()).unwrap(), i(60));
assert_eq!(Quantity::of(i(1), &arcminute()).in_unit(&arcsecond()).unwrap(), i(60));
assert_eq!(Quantity::of(i(1), &ere_hour()).in_unit(&coulomb()).unwrap(), i(3600));
}
#[test]
fn extended_units_cooking_nautical_astronomical_radiation_illumination_convert_exactly() {
assert_eq!(Quantity::of(i(1), &teaspoon()).in_unit(&dash()).unwrap(), i(8), "1 tsp = 8 dashes");
assert_eq!(Quantity::of(i(1), &teaspoon()).in_unit(&pinch()).unwrap(), i(16), "1 tsp = 16 pinches");
assert_eq!(Quantity::of(i(1), &teaspoon()).in_unit(&smidgen()).unwrap(), i(32), "1 tsp = 32 smidgens");
assert_eq!(Quantity::of(i(1), &dash()).in_unit(&pinch()).unwrap(), i(2), "1 dash = 2 pinches");
assert_eq!(Quantity::of(i(1), &stick_of_butter()).in_unit(&tablespoon()).unwrap(), i(8), "1 stick = 8 tbsp");
assert_eq!(Quantity::of(i(1), &stick_of_butter()).in_unit(&us_cup()).unwrap(), r(1, 2), "1 stick = ½ cup");
assert_eq!(Quantity::of(i(1), &bushel()).in_unit(&peck()).unwrap(), i(4), "1 bushel = 4 pecks");
assert_eq!(Quantity::of(i(1), &bushel()).in_unit(&dry_gallon()).unwrap(), i(8));
assert_eq!(Quantity::of(i(1), &dry_gallon()).in_unit(&dry_quart()).unwrap(), i(4));
assert_eq!(Quantity::of(i(1), &nautical_mile()).in_unit(&cable()).unwrap(), i(10), "1 nmi = 10 cables");
assert_eq!(Quantity::of(i(1), &nautical_league()).in_unit(&nautical_mile()).unwrap(), i(3));
assert_eq!(Quantity::of(i(1), &league()).in_unit(&mile()).unwrap(), i(3), "1 league = 3 miles");
assert_eq!(Quantity::of(i(1), &light_minute()).in_unit(&light_second()).unwrap(), i(60));
assert_eq!(Quantity::of(i(1), &light_hour()).in_unit(&light_minute()).unwrap(), i(60));
assert_eq!(Quantity::of(i(1), &light_day()).in_unit(&light_hour()).unwrap(), i(24));
assert_eq!(Quantity::of(i(1), &light_second()).in_unit(&metre()).unwrap(), i(299_792_458));
assert_eq!(Quantity::of(i(1), &gray()).in_unit(&rad_unit()).unwrap(), i(100), "1 Gy = 100 rad");
assert_eq!(Quantity::of(i(1), &sievert()).in_unit(&rem()).unwrap(), i(100), "1 Sv = 100 rem");
assert_eq!(Quantity::of(i(1), &curie()).in_unit(&becquerel()).unwrap(), i(37_000_000_000), "1 Ci = 3.7e10 Bq");
assert_eq!(gray().dimension, Dimension::absorbed_dose());
assert_eq!(sievert().dimension, Dimension::absorbed_dose()); assert_eq!(becquerel().dimension, Dimension::radioactivity());
assert_eq!(roentgen().dimension, Dimension::exposure());
assert_eq!(katal().dimension, Dimension::catalytic_activity());
assert_eq!(Quantity::of(i(1), &phot()).in_unit(&lux()).unwrap(), i(10_000), "1 phot = 10000 lux");
assert_eq!(Quantity::of(i(1), &foot_candle()).in_unit(&lux()).unwrap(), r(1_562_500, 145_161));
assert_eq!(lumen().dimension, Dimension::luminous_flux());
assert_eq!(lux().dimension, Dimension::illuminance());
assert_eq!(nit().dimension, Dimension::luminance());
}
#[test]
fn rate_flow_concentration_viscosity_torque_and_prefixes_convert_exactly() {
assert_eq!(Quantity::of(i(1), &byte_per_second()).in_unit(&bit_per_second()).unwrap(), i(8));
assert_eq!(Quantity::of(i(1), &gigabit_per_second()).in_unit(&megabit_per_second()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &megabyte_per_second()).in_unit(&bit_per_second()).unwrap(), i(8_000_000));
assert_eq!(Quantity::of(i(1), &cubic_metre_per_second()).in_unit(&litre_per_second()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &litre_per_second()).in_unit(&litre_per_minute()).unwrap(), i(60));
assert_eq!(Quantity::of(i(1), &molar()).in_unit(&millimolar()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &millimolar()).in_unit(µmolar()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &pascal_second()).in_unit(&poise()).unwrap(), i(10));
assert_eq!(Quantity::of(i(1), &poise()).in_unit(¢ipoise()).unwrap(), i(100));
assert_eq!(Quantity::of(i(1), &square_metre_per_second()).in_unit(&stokes()).unwrap(), i(10_000));
assert_eq!(Quantity::of(i(1), &stokes()).in_unit(¢istokes()).unwrap(), i(100));
assert_eq!(newton_metre().dimension, Dimension::energy());
assert_eq!(Quantity::of(i(1), &newton_metre()).in_unit(&joule()).unwrap(), i(1));
assert_eq!(km_per_litre().dimension, Dimension::fuel_economy());
assert_eq!(mile_per_gallon().dimension, Dimension::fuel_economy());
assert_eq!(Quantity::of(i(1), &gigametre()).in_unit(&megametre()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &megametre()).in_unit(&metre()).unwrap(), i(1_000_000));
assert_eq!(Quantity::of(i(1), &terawatt()).in_unit(&gigawatt()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &petabyte()).in_unit(&terabyte()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &picometre()).in_unit(&femtometre()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &terajoule()).in_unit(&gigajoule()).unwrap(), i(1000));
}
#[test]
fn electromagnetic_and_surface_tension_units_convert_exactly() {
assert_eq!(siemens().dimension, Dimension::resistance().recip());
assert_eq!(Quantity::of(i(1), &siemens()).in_unit(&millisiemens()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), µfarad()).in_unit(&nanofarad()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &nanofarad()).in_unit(&picofarad()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &farad()).in_unit(&picofarad()).unwrap(), i(1_000_000_000_000));
assert_eq!(Quantity::of(i(1), &weber()).in_unit(&maxwell()).unwrap(), i(100_000_000));
assert_eq!(Quantity::of(i(1), &henry()).in_unit(&millihenry()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &millihenry()).in_unit(µhenry()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &tesla()).in_unit(&gauss()).unwrap(), i(10_000));
assert_eq!(Quantity::of(i(1), &tesla()).in_unit(&millitesla()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &newton_per_metre()).in_unit(&dyne_per_centimetre()).unwrap(), i(1000));
assert_eq!(farad().dimension, Dimension::charge().div(Dimension::voltage()));
assert_eq!(henry().dimension, Dimension::magnetic_flux().div(Dimension::current()));
assert_eq!(tesla().dimension, Dimension::magnetic_flux().div(Dimension::area()));
assert!(Quantity::of(i(1), &farad()).in_unit(&henry()).is_none());
}
#[test]
fn dimensionless_counting_and_ratio_units_convert_exactly() {
assert_eq!(Quantity::of(i(1), &dozen()).in_unit(&each()).unwrap(), i(12));
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));
assert_eq!(Quantity::of(i(1), &score()).in_unit(&each()).unwrap(), i(20));
assert_eq!(Quantity::of(i(1), &baker_dozen()).in_unit(&each()).unwrap(), i(13));
assert_eq!(Quantity::of(i(1), &ream()).in_unit(&each()).unwrap(), i(500));
assert_eq!(Quantity::of(i(1), &each()).in_unit(&percent()).unwrap(), i(100));
assert_eq!(Quantity::of(i(1), &each()).in_unit(&permille()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &each()).in_unit(&basis_point()).unwrap(), i(10_000));
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));
assert_eq!(Quantity::of(i(1), &each()).in_unit(&ppm()).unwrap(), i(1_000_000));
assert_eq!(Quantity::of(i(1), &ppm()).in_unit(&ppb()).unwrap(), i(1000));
assert_eq!(Quantity::of(i(1), &dozen()).in_unit(&percent()).unwrap(), i(1200));
for u in [each(), pair(), dozen(), gross(), percent(), ppm(), basis_point()] {
assert_eq!(u.dimension, Dimension::DIMENSIONLESS, "{} is dimensionless", u.symbol);
assert!(Quantity::of(i(1), &u).in_unit(&metre()).is_none(), "{} → m forbidden", u.symbol);
}
}
#[test]
fn light_travel_time_is_exact_and_dimension_safe() {
let secs = |q: Quantity| q.in_unit(&second()).unwrap();
let ltt = |u: Unit| super::light_travel_time(&Quantity::of(i(1), &u)).unwrap();
assert_eq!(secs(ltt(light_second())), i(1));
assert_eq!(secs(ltt(light_minute())), i(60));
assert_eq!(secs(ltt(light_year())), i(31_557_600)); let sun = ltt(astronomical_unit());
assert_eq!(sun.dimension(), Dimension::time());
let sun_secs = secs(sun);
assert_eq!(sun_secs, r(149_597_870_700, 299_792_458));
assert!(sun_secs > i(498) && sun_secs < i(500));
assert!(super::light_travel_time(&Quantity::of(i(5), &kilogram())).is_none());
}
#[test]
fn observed_arrival_adds_light_delay() {
const NS: i64 = 1_000_000_000;
assert_eq!(super::time_quantity_to_nanos(&Quantity::of(i(1), &second())), Some(NS));
assert_eq!(super::time_quantity_to_nanos(&Quantity::of(i(1), &millisecond())), Some(1_000_000));
assert_eq!(super::time_quantity_to_nanos(&Quantity::of(i(1), &metre())), None);
assert_eq!(
super::observed_arrival_nanos(0, &Quantity::of(i(1), &light_minute())),
Some(60 * NS)
);
assert_eq!(
super::observed_arrival_nanos(5 * NS, &Quantity::of(i(1), &light_second())),
Some(6 * NS)
);
let sun = super::observed_arrival_nanos(0, &Quantity::of(i(1), &astronomical_unit())).unwrap();
assert!(sun > 498 * NS && sun < 500 * NS);
let expected = super::time_quantity_to_nanos(&super::light_travel_time(&Quantity::of(i(1), &astronomical_unit())).unwrap());
assert_eq!(Some(sun), expected);
assert_eq!(super::observed_arrival_nanos(0, &Quantity::of(i(1), &kilogram())), None);
}
#[test]
fn spacetime_stamp_is_space_aware_like_a_timezone_for_position() {
use super::{CausalRelation, Position, SpacetimeStamp};
const C: f64 = 299_792_458.0; let pos = |x: f64| Position { x, y: 0.0, z: 0.0 };
let at = |ns: i64, x: f64| SpacetimeStamp { instant_nanos: ns, position: pos(x) };
let here = pos(0.0);
let one_ls = C;
assert_eq!(at(0, 0.0).causal_relation(at(1_000_000_000, 0.0)), CausalRelation::Before);
assert_eq!(at(1_000_000_000, 0.0).causal_relation(at(0, 0.0)), CausalRelation::After);
assert_eq!(at(0, 0.0).light_separation_nanos(at(0, one_ls)), 1_000_000_000);
assert_eq!(at(0, 0.0).causal_relation(at(2_000_000_000, one_ls)), CausalRelation::Before);
assert_eq!(at(0, 0.0).causal_relation(at(1_000_000_000, one_ls)), CausalRelation::Before);
assert_eq!(at(0, 0.0).causal_relation(at(500_000_000, one_ls)), CausalRelation::Concurrent);
assert_eq!(at(0, 0.0).observed_at_nanos(pos(one_ls)), 1_000_000_000);
assert!((Position { x: 3.0, y: 4.0, z: 0.0 }.distance_to(here) - 5.0).abs() < 1e-9);
}
#[test]
fn relativistic_time_dilation() {
let approx = |a: f64, b: f64| (a - b).abs() < 1e-9;
assert_eq!(super::lorentz_factor(0.0), Some(1.0));
assert!(approx(super::lorentz_factor(0.6).unwrap(), 1.25));
assert!(approx(super::lorentz_factor(0.8).unwrap(), 5.0 / 3.0));
assert!(approx(super::proper_time_seconds(10.0, 0.6).unwrap(), 8.0));
assert_eq!(super::lorentz_factor(1.0), None);
assert_eq!(super::lorentz_factor(1.5), None);
assert_eq!(super::lorentz_factor(f64::NAN), None);
let c = super::constants::speed_of_light();
let v = Quantity::si(c.magnitude_si().mul(&r(3, 5)), Dimension::speed());
assert!(approx(super::time_dilation_factor(&v).unwrap(), 1.25));
assert_eq!(super::time_dilation_factor(&Quantity::of(i(1), &metre())), None);
}
#[test]
fn physical_constants_carry_correct_dimensions_and_compose() {
use super::constants::*;
let c = speed_of_light();
assert_eq!(c.dimension(), Dimension::speed());
assert_eq!(c.in_unit(&metre_per_second()).unwrap(), i(299_792_458));
assert_eq!(c.in_unit(&kilometre_per_hour()).unwrap(), r(5_396_264_244, 5));
let energy = Quantity::of(i(2), &kilogram()).mul(&c).mul(&c);
assert_eq!(energy.dimension(), Dimension::energy());
let c_sq = i(299_792_458).mul(&i(299_792_458));
assert_eq!(energy.magnitude_si(), &i(2).mul(&c_sq));
let r_gas = molar_gas_constant();
assert_eq!(r_gas.dimension(), Dimension::energy().div(Dimension::amount()).div(Dimension::temperature()));
assert_eq!(r_gas.magnitude_si(), avogadro_constant().mul(&boltzmann_constant()).magnitude_si());
assert_eq!(planck_constant().dimension(), Dimension::energy().mul(Dimension::time())); assert_eq!(elementary_charge().dimension(), Dimension::charge());
assert_eq!(boltzmann_constant().dimension(), Dimension::energy().div(Dimension::temperature()));
assert_eq!(avogadro_constant().dimension(), Dimension::amount().recip());
assert_eq!(gravitational_constant().dimension(),
Dimension::volume().div(Dimension::mass()).div(Dimension::time().powi(2)));
assert_eq!(standard_gravity().dimension(), Dimension::acceleration());
assert_eq!(standard_atmosphere().in_unit(&pascal()).unwrap(), i(101_325));
assert_eq!(standard_gravity().in_unit(&metre_per_second()).is_none(), true); let n = Quantity::of(i(1), &mole());
let t = Quantity::of(i(300), &kelvin());
let v = Quantity::of(i(1), &cubic_metre());
let p = n.mul(&r_gas).mul(&t).div(&v).unwrap();
assert_eq!(p.dimension(), Dimension::pressure());
}
#[test]
fn in_best_unit_auto_scales_to_the_most_human_unit() {
let ladder = [millimetre(), centimetre(), metre(), kilometre()];
let (mag, u) = Quantity::of(i(1500), &metre()).in_best_unit(&ladder).unwrap();
assert_eq!((u.symbol, mag), ("km", r(3, 2)));
let (mag, u) = Quantity::of(r(1, 2), &kilometre()).in_best_unit(&ladder).unwrap();
assert_eq!((u.symbol, mag), ("m", i(500)));
let (mag, u) = Quantity::of(r(3, 1000), &metre()).in_best_unit(&ladder).unwrap();
assert_eq!((u.symbol, mag), ("mm", i(3)));
let (mag, u) = Quantity::of(i(1), &kilometre()).in_best_unit(&ladder).unwrap();
assert_eq!((u.symbol, mag), ("km", i(1)));
let (mag, u) = Quantity::of(r(1, 10), &millimetre()).in_best_unit(&ladder).unwrap();
assert_eq!((u.symbol, mag), ("mm", r(1, 10)));
let (mag, u) = Quantity::of(i(-2000), &metre()).in_best_unit(&ladder).unwrap();
assert_eq!((u.symbol, mag), ("km", i(-2)));
assert!(Quantity::of(i(1), &metre()).in_best_unit(&[kilogram(), second()]).is_none());
let q = Quantity::of(i(1500), &metre());
let (mag, u) = q.in_best_unit(&ladder).unwrap();
assert_eq!(Quantity::of(mag, u).magnitude_si(), q.magnitude_si());
let mass_ladder = [gram(), kilogram(), tonne()];
let (mag, u) = Quantity::of(i(2_500_000), &gram()).in_best_unit(&mass_ladder).unwrap();
assert_eq!((u.symbol, mag), ("t", r(5, 2)));
}
#[test]
fn by_name_resolves_units_from_english_names_plurals_and_symbols() {
for alias in ["inch", "inches", "in", " Inch "] {
assert_eq!(by_name(alias), Some(inch()), "{alias}");
}
for alias in ["centimeter", "centimetre", "cm"] {
assert_eq!(by_name(alias), Some(centimetre()), "{alias}");
}
for alias in ["foot", "feet", "ft"] {
assert_eq!(by_name(alias), Some(foot()), "{alias}");
}
assert_eq!(by_name("KM"), Some(kilometre()));
assert_eq!(by_name("Kilogram"), Some(kilogram()));
assert_eq!(by_name("celsius"), Some(celsius()));
assert_eq!(by_name("tablespoon"), Some(tablespoon()));
assert_eq!(by_name("MPH"), Some(mile_per_hour()));
assert_eq!(by_name("zorgles"), None);
assert_eq!(by_name(""), None);
let a = Quantity::of(i(2), &by_name("inches").unwrap());
let b = Quantity::of(i(5), &by_name("cm").unwrap());
let sum = a.add(&b).unwrap();
assert_eq!(sum.in_unit(&by_name("feet").unwrap()).unwrap(), r(42, 127));
}
#[test]
fn by_name_covers_radiation_photometry_em_dimensionless_and_prefixes() {
assert_eq!(by_name("gray"), Some(gray()));
assert_eq!(by_name("sievert"), Some(sievert()));
assert_eq!(by_name("becquerel"), Some(becquerel()));
assert_eq!(by_name("lux"), Some(lux()));
assert_eq!(by_name("lumen"), Some(lumen()));
assert_eq!(by_name("tesla"), Some(tesla()));
assert_eq!(by_name("farad"), Some(farad()));
assert_eq!(by_name("henry"), Some(henry()));
assert_eq!(by_name("poise"), Some(poise()));
assert_eq!(by_name("molar"), Some(molar()));
assert_eq!(by_name("Mbps"), Some(megabit_per_second()));
assert_eq!(by_name("dozen"), Some(dozen()));
assert_eq!(by_name("percent"), Some(percent()));
assert_eq!(by_name("ppm"), Some(ppm()));
assert_eq!(by_name("terabyte"), Some(terabyte()));
assert_eq!(by_name("pinch"), Some(pinch()));
assert_eq!(by_name("fathom"), Some(fathom()));
assert_eq!(by_name("light second"), Some(light_second()));
for name in ["gray", "tesla", "poise", "dozen", "percent", "terabyte", "pinch", "light second", "lux", "molar"] {
let u = by_name(name).unwrap();
let v = r(3, 1);
assert_eq!(Quantity::of(v.clone(), &u).in_unit(&u).unwrap(), v, "{name}");
}
}
fn catalog() -> Vec<Vec<Unit>> {
vec![
vec![metre(), kilometre(), hectometre(), dekametre(), decimetre(), centimetre(), millimetre(),
micrometre(), nanometre(), picometre(), femtometre(), megametre(), gigametre(), angstrom(),
inch(), foot(), yard(), mile(), nautical_mile(),
fathom(), furlong(), chain(), rod(), hand(), point(), pica(), thou(), astronomical_unit(),
light_year(), cable(), league(), nautical_league(), light_second(), light_minute(),
light_hour(), light_day(), lunar_distance(), solar_radius(), earth_radius()],
vec![kilogram(), gram(), milligram(), microgram(), nanogram(), tonne(), pound(), ounce(), stone(), short_ton(),
long_ton(), hundredweight(), grain(), dram(), carat(), troy_ounce(), troy_pound(),
solar_mass(), earth_mass(), jupiter_mass()],
vec![second(), millisecond(), microsecond(), nanosecond(), picosecond(), minute(), hour(), day(), week(),
fortnight(), julian_year(), decade(), century()],
vec![kelvin(), celsius(), fahrenheit(), rankine(), reaumur()],
vec![cubic_metre(), litre(), millilitre(), centilitre(), decilitre(), cubic_centimetre(),
cubic_inch(), cubic_foot(), us_gallon(), us_quart(), us_pint(), us_cup(), us_gill(),
us_fluid_ounce(), tablespoon(), teaspoon(), oil_barrel(), imperial_gallon(), imperial_pint(),
imperial_fluid_ounce(), stick_of_butter(), dash(), pinch(), smidgen(), bushel(), peck(),
dry_gallon(), dry_quart(), dry_pint()],
vec![square_metre(), square_kilometre(), square_centimetre(), square_inch(), square_foot(),
square_yard(), square_mile(), hectare(), are(), acre()],
vec![metre_per_second(), kilometre_per_hour(), mile_per_hour(), foot_per_second(), knot()],
vec![hertz(), kilohertz(), megahertz(), gigahertz(), rpm()],
vec![newton(), kilonewton(), dyne(), kilogram_force(), pound_force()],
vec![joule(), kilojoule(), megajoule(), gigajoule(), terajoule(), calorie(), kilocalorie(),
watt_hour(), kilowatt_hour(), erg(), electronvolt(), newton_metre(), pound_foot()],
vec![watt(), kilowatt(), megawatt(), gigawatt(), terawatt(), horsepower()],
vec![pascal(), kilopascal(), bar(), millibar(), atmosphere(), psi()],
vec![bit(), byte(), kilobit(), kilobyte(), megabyte(), gigabyte(), terabit(), terabyte(), petabyte(),
kibibyte(), mebibyte(), gibibyte()],
vec![turn(), degree(), gradian(), arcminute(), arcsecond()],
vec![ampere(), milliampere()],
vec![coulomb(), ampere_hour(), milliampere_hour()],
vec![volt(), millivolt(), kilovolt()],
vec![ohm()],
vec![mole()],
vec![candela()],
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(),
byte_per_second(), megabyte_per_second()], vec![cubic_metre_per_second(), litre_per_second(), litre_per_minute(),
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(),
percent(), permille(), ppm(), ppb(), basis_point()],
]
}
#[test]
fn every_unit_in_the_catalog_is_well_formed_and_dimension_safe() {
let groups = catalog();
let v = r(7, 3); for group in &groups {
for u in group {
assert_eq!(Quantity::of(v.clone(), u).in_unit(u).unwrap(), v, "round-trip {}", u.symbol);
assert!(!u.scale.is_zero() && !u.scale.is_negative(), "scale > 0 for {}", u.symbol);
for other in group {
assert!(
Quantity::of(v.clone(), u).in_unit(other).is_some(),
"{} convertible to {}",
u.symbol,
other.symbol
);
assert_eq!(u.dimension, other.dimension, "{} and {} share a dimension", u.symbol, other.symbol);
}
}
}
for (gi, ga) in groups.iter().enumerate() {
for (gj, gb) in groups.iter().enumerate() {
if gi == gj || ga[0].dimension == gb[0].dimension {
continue;
}
assert!(
Quantity::of(v.clone(), &ga[0]).in_unit(&gb[0]).is_none(),
"{} → {} must be a forbidden cross-dimension cast",
ga[0].symbol,
gb[0].symbol
);
}
}
}
}