/* * unit_converter length_units.rs * - definitions of length units * * Copyright (C) 2025 Real Microsoft, LLC * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ use crate::unit_defs::{BestUnit, ConvertTo, FromUnitName, MetricPrefix, UnitName}; use astro_float::{BigFloat, RoundingMode}; use once_cell::sync::Lazy; use std::collections::BTreeMap; pub const PRECISION: usize = 2048; // length unit -> value in meters pub static LENGTH_STORE: Lazy> = Lazy::new(|| { let mut store = BTreeMap::new(); store.insert( LengthUnit::Meter(MetricPrefix::Pico), BigFloat::from_f64(0.000000000001, PRECISION), ); store.insert( LengthUnit::Meter(MetricPrefix::Nano), BigFloat::from_f64(0.000000001, PRECISION), ); store.insert( LengthUnit::Meter(MetricPrefix::Micro), BigFloat::from_f64(0.000001, PRECISION), ); store.insert( LengthUnit::Meter(MetricPrefix::Milli), BigFloat::from_f64(0.001, PRECISION), ); store.insert( LengthUnit::Meter(MetricPrefix::Centi), BigFloat::from_f64(0.01, PRECISION), ); store.insert( LengthUnit::Meter(MetricPrefix::Deci), BigFloat::from_f64(0.1, PRECISION), ); store.insert( LengthUnit::Meter(MetricPrefix::None), BigFloat::from_f64(1.0, PRECISION), ); store.insert( LengthUnit::Meter(MetricPrefix::Deca), BigFloat::from_f64(10.0, PRECISION), ); store.insert( LengthUnit::Meter(MetricPrefix::Hecto), BigFloat::from_f64(100.0, PRECISION), ); store.insert( LengthUnit::Meter(MetricPrefix::Kilo), BigFloat::from_f64(1000.0, PRECISION), ); store.insert( LengthUnit::Meter(MetricPrefix::Mega), BigFloat::from_f64(1_000_000.0, PRECISION), ); store.insert( LengthUnit::Meter(MetricPrefix::Giga), BigFloat::from_f64(1_000_000_000.0, PRECISION), ); store.insert( LengthUnit::Meter(MetricPrefix::Tera), BigFloat::from_f64(1_000_000_000_000.0, PRECISION), ); // inch is 2.54 cm store.insert( LengthUnit::Inch, store .get(&LengthUnit::Meter(MetricPrefix::Centi)) .unwrap() .mul_full_prec(&BigFloat::from_f64(2.54, PRECISION)), ); // thou is 1/1000 inches store.insert( LengthUnit::Thou, store .get(&LengthUnit::Inch) .unwrap() .mul_full_prec(&BigFloat::from_f64(1.0 / 1000.0, PRECISION)), ); // foot is 12 inches store.insert( LengthUnit::Foot, store .get(&LengthUnit::Inch) .unwrap() .mul_full_prec(&BigFloat::from_f64(12.0, PRECISION)), ); // yard is 3 feet store.insert( LengthUnit::Yard, store .get(&LengthUnit::Foot) .unwrap() .mul_full_prec(&BigFloat::from_f64(3.0, PRECISION)), ); // mile is 5280 feet store.insert( LengthUnit::Mile, store .get(&LengthUnit::Foot) .unwrap() .mul_full_prec(&BigFloat::from_f64(5280.0, PRECISION)), ); // league is 3 miles store.insert( LengthUnit::League, store .get(&LengthUnit::Mile) .unwrap() .mul_full_prec(&BigFloat::from_f64(3.0, PRECISION)), ); // fathom is 2 yards store.insert( LengthUnit::Fathom, store .get(&LengthUnit::Yard) .unwrap() .mul_full_prec(&BigFloat::from_f64(2.0, PRECISION)), ); // nautical mile is 1852 m store.insert( LengthUnit::NauticalMile, store .get(&LengthUnit::Meter(MetricPrefix::None)) .unwrap() .mul_full_prec(&BigFloat::from_f64(1852.0, PRECISION)), ); // chain is 22 yards store.insert( LengthUnit::Chain, store .get(&LengthUnit::Yard) .unwrap() .mul_full_prec(&BigFloat::from_f64(22.0, PRECISION)), ); // rod is 5 / 12 yards store.insert( LengthUnit::Rod, store .get(&LengthUnit::Yard) .unwrap() .mul_full_prec(&BigFloat::from_f64(5.5, PRECISION)), ); // earth radius is 6371 km store.insert( LengthUnit::EarthRadius, store .get(&LengthUnit::Meter(MetricPrefix::Kilo)) .unwrap() .mul_full_prec(&BigFloat::from_f64(6371.0, PRECISION)), ); // lunar distance is 384402 km store.insert( LengthUnit::LunarDistance, store .get(&LengthUnit::Meter(MetricPrefix::Kilo)) .unwrap() .mul_full_prec(&BigFloat::from_f64(384402.0, PRECISION)), ); // astronomical unit is 149597870700 m store.insert( LengthUnit::AstronomicalUnit, store .get(&LengthUnit::Meter(MetricPrefix::None)) .unwrap() .mul_full_prec(&BigFloat::from_f64(149597870700.0, PRECISION)), ); // lightyear is 9460730472580.8 km store.insert( LengthUnit::LightYear, store .get(&LengthUnit::Meter(MetricPrefix::Kilo)) .unwrap() .mul_full_prec(&BigFloat::from_f64(9460730472580.8, PRECISION)), ); // parsec is 30856775814671.9 km store.insert( LengthUnit::Parsec, store .get(&LengthUnit::Meter(MetricPrefix::Kilo)) .unwrap() .mul_full_prec(&BigFloat::from_f64(30856775814671.9, PRECISION)), ); // hubble length is 14400000000 light years store.insert( LengthUnit::HubbleLength, store .get(&LengthUnit::LightYear) .unwrap() .mul_full_prec(&BigFloat::from_f64(14400000000.0, PRECISION)), ); // planck length is 0.000000000000000000000000000000000016163 meters store.insert( LengthUnit::PlanckLength, store .get(&LengthUnit::Meter(MetricPrefix::None)) .unwrap() .mul_full_prec(&BigFloat::from_f64( 0.000000000000000000000000000000000016163, PRECISION, )), ); // cana is 1.57 meters store.insert( LengthUnit::Cana, store .get(&LengthUnit::Meter(MetricPrefix::None)) .unwrap() .mul_full_prec(&BigFloat::from_f64(1.57, PRECISION)), ); // cubit is 52.92 cm store.insert( LengthUnit::Cubit, store .get(&LengthUnit::Meter(MetricPrefix::Centi)) .unwrap() .mul_full_prec(&BigFloat::from_f64(52.92, PRECISION)), ); // rope is 6.096 meters store.insert( LengthUnit::Rope, store .get(&LengthUnit::Meter(MetricPrefix::None)) .unwrap() .mul_full_prec(&BigFloat::from_f64(6.096, PRECISION)), ); // li is 500 meters store.insert( LengthUnit::Li, store .get(&LengthUnit::Meter(MetricPrefix::None)) .unwrap() .mul_full_prec(&BigFloat::from_f64(500.0, PRECISION)), ); // pace is 0.75 meters store.insert( LengthUnit::Pace, store .get(&LengthUnit::Meter(MetricPrefix::None)) .unwrap() .mul_full_prec(&BigFloat::from_f64(0.75, PRECISION)), ); // verst is 1.0668 km store.insert( LengthUnit::Verst, store .get(&LengthUnit::Meter(MetricPrefix::Kilo)) .unwrap() .mul_full_prec(&BigFloat::from_f64(1.0668, PRECISION)), ); // double decker bus is 10 meters store.insert( LengthUnit::DoubleDeckerBus, store .get(&LengthUnit::Meter(MetricPrefix::None)) .unwrap() .mul_full_prec(&BigFloat::from_f64(10.0, PRECISION)), ); // football field is 100 yards store.insert( LengthUnit::FootballField, store .get(&LengthUnit::Yard) .unwrap() .mul_full_prec(&BigFloat::from_f64(100.0, PRECISION)), ); // human hair thickness is 0.08 mm store.insert( LengthUnit::HumanHairThickness, store .get(&LengthUnit::Meter(MetricPrefix::Milli)) .unwrap() .mul_full_prec(&BigFloat::from_f64(0.08, PRECISION)), ); // furlong is 1/8 mile store.insert( LengthUnit::Furlong, store .get(&LengthUnit::Mile) .unwrap() .mul_full_prec(&BigFloat::from_f64(1.0 / 8.0, PRECISION)), ); // horse is 2.4 meters store.insert( LengthUnit::HorseLength, store .get(&LengthUnit::Meter(MetricPrefix::None)) .unwrap() .mul_full_prec(&BigFloat::from_f64(2.4, PRECISION)), ); // horizontal pitch is 5.08 mm store.insert( LengthUnit::HorizontalPitch, store .get(&LengthUnit::Meter(MetricPrefix::Milli)) .unwrap() .mul_full_prec(&BigFloat::from_f64(5.08, PRECISION)), ); // hammer unit is 19.05 mm store.insert( LengthUnit::HammerUnit, store .get(&LengthUnit::Meter(MetricPrefix::Milli)) .unwrap() .mul_full_prec(&BigFloat::from_f64(19.05, PRECISION)), ); // rack unit is 1.75 inches store.insert( LengthUnit::RackUnit, store .get(&LengthUnit::Inch) .unwrap() .mul_full_prec(&BigFloat::from_f64(1.75, PRECISION)), ); // hand is 4 inches store.insert( LengthUnit::Hand, store .get(&LengthUnit::Inch) .unwrap() .mul_full_prec(&BigFloat::from_f64(4.0, PRECISION)), ); // light nanosecond is 29.9792458 cm store.insert( LengthUnit::LightNanosecond, store .get(&LengthUnit::Meter(MetricPrefix::Centi)) .unwrap() .mul_full_prec(&BigFloat::from_f64(29.9792458, PRECISION)), ); // metric foot is 300 mm store.insert( LengthUnit::MetricFoot, store .get(&LengthUnit::Meter(MetricPrefix::Milli)) .unwrap() .mul_full_prec(&BigFloat::from_f64(300.0, PRECISION)), ); // boat length is 19 meters store.insert( LengthUnit::BoatLength, store .get(&LengthUnit::Meter(MetricPrefix::None)) .unwrap() .mul_full_prec(&BigFloat::from_f64(19.0, PRECISION)), ); // block is 150 meters store.insert( LengthUnit::Block, store .get(&LengthUnit::Meter(MetricPrefix::None)) .unwrap() .mul_full_prec(&BigFloat::from_f64(150.0, PRECISION)), ); // siriometer is 15.8 light years store.insert( LengthUnit::Siriometer, store .get(&LengthUnit::LightYear) .unwrap() .mul_full_prec(&BigFloat::from_f64(15.8, PRECISION)), ); // banana is 8 inches store.insert( LengthUnit::Banana, store .get(&LengthUnit::Inch) .unwrap() .mul_full_prec(&BigFloat::from_f64(8.0, PRECISION)), ); store }); // length unit -> Vec<(name, plural)> pub static LENGTH_NAMES: Lazy>> = Lazy::new(|| { let mut store = BTreeMap::new(); store.insert( LengthUnit::Meter(MetricPrefix::Pico), vec![("picometer", "picometers")], ); store.insert( LengthUnit::Meter(MetricPrefix::Nano), vec![("nanometer", "nanometers")], ); store.insert( LengthUnit::Meter(MetricPrefix::Micro), vec![("micrometer", "micrometers")], ); store.insert( LengthUnit::Meter(MetricPrefix::Milli), vec![("millimeter", "millimeters"), ("mm", "mm")], ); store.insert( LengthUnit::Meter(MetricPrefix::Centi), vec![("centimeter", "centimeters"), ("cm", "cm")], ); store.insert( LengthUnit::Meter(MetricPrefix::Deci), vec![("decimeter", "decimeters")], ); store.insert( LengthUnit::Meter(MetricPrefix::None), vec![ ("meter", "meters"), ("m", "m"), ("minecraft block", "minecraft blocks"), ], ); store.insert( LengthUnit::Meter(MetricPrefix::Deca), vec![("decameter", "decameters")], ); store.insert( LengthUnit::Meter(MetricPrefix::Hecto), vec![("hectometer", "hectometers")], ); store.insert( LengthUnit::Meter(MetricPrefix::Kilo), vec![("kilometer", "kilometers"), ("km", "km")], ); store.insert( LengthUnit::Meter(MetricPrefix::Mega), vec![("megameter", "megameters")], ); store.insert( LengthUnit::Meter(MetricPrefix::Giga), vec![("gigameter", "gigameters")], ); store.insert( LengthUnit::Meter(MetricPrefix::Tera), vec![("terameter", "terameters")], ); // inch is 25.4 m store.insert( LengthUnit::Inch, vec![("inch", "inches"), ("in", "in"), ("\"", "\"")], ); // thou is 1/1000 inches store.insert(LengthUnit::Thou, vec![("thou", "thous")]); // foot is 12 inches store.insert( LengthUnit::Foot, vec![("foot", "feet"), ("ft", "ft"), ("'", "'ft'")], ); // yard is 3 feet store.insert(LengthUnit::Yard, vec![("yard", "yards"), ("yd", "yd")]); // mile is 5280 feet store.insert(LengthUnit::Mile, vec![("mile", "miles")]); // league is 3 miles store.insert(LengthUnit::League, vec![("league", "leagues")]); // fathom is 2 yards store.insert(LengthUnit::Fathom, vec![("fathom", "fathoms")]); // nautical mile is 1852 m store.insert( LengthUnit::NauticalMile, vec![("nautical mile", "nautical miles")], ); // chain is 22 yards store.insert(LengthUnit::Chain, vec![("chain", "chains")]); // rod is 5 / 12 yards store.insert(LengthUnit::Rod, vec![("rod", "rods")]); // earth radius is 6371 km store.insert( LengthUnit::EarthRadius, vec![("earth radius", "earth radii")], ); // lunar distance is 384402 km store.insert( LengthUnit::LunarDistance, vec![("lunar distance", "lunar distances")], ); // astronomical unit is 149597870700 m store.insert( LengthUnit::AstronomicalUnit, vec![("astronomical unit", "astronomical units")], ); // lightyear is 9460730472580.8 km store.insert(LengthUnit::LightYear, vec![("lightyear", "lightyears")]); // parsec is 30856775814671.9 km store.insert(LengthUnit::Parsec, vec![("parsec", "parsecs")]); // hubble length is 14400000000 light years store.insert( LengthUnit::HubbleLength, vec![("hubble length", "hubble lengths")], ); // planck length is 0.000000000000000000000000000000000016163 meters store.insert( LengthUnit::PlanckLength, vec![("planck length", "planck lengths")], ); // cana is 1.57 meters store.insert(LengthUnit::Cana, vec![("cana", "canas")]); // cubit is 52.92 cm store.insert(LengthUnit::Cubit, vec![("cubit", "cubits")]); // rope is 6.096 meters store.insert(LengthUnit::Rope, vec![("rope", "ropes")]); // li is 500 meters store.insert(LengthUnit::Li, vec![("li", "lis")]); // pace is 0.75 meters store.insert(LengthUnit::Pace, vec![("pace", "paces")]); // verst is 1.0668 km store.insert(LengthUnit::Verst, vec![("verst", "versts")]); // double decker bus is 10 meters store.insert( LengthUnit::DoubleDeckerBus, vec![("double decker bus", "double decker busses")], ); // football field is 100 yards store.insert( LengthUnit::FootballField, vec![("football field", "football fields")], ); // human hair thickness is 0.08 mm store.insert( LengthUnit::HumanHairThickness, vec![("human hair thickness", "human hair thicknesses")], ); // furlong is 1/8 mile store.insert(LengthUnit::Furlong, vec![("furlong", "furlongs")]); // horse is 2.4 meters store.insert( LengthUnit::HorseLength, vec![("horse", "horses"), ("horse length", "horse lengths")], ); // horizontal pitch is 5.08 mm store.insert( LengthUnit::HorizontalPitch, vec![("horizontal pitch", "horizontal pitches")], ); // hammer unit is 19.05 mm store.insert( LengthUnit::HammerUnit, vec![("hammer unit", "hammer units")], ); // rack unit is 1.75 inches store.insert(LengthUnit::RackUnit, vec![("rack unit", "rack units")]); // hand is 4 inches store.insert(LengthUnit::Hand, vec![("hand", "hands")]); // light nanosecond is 29.9792458 cm store.insert( LengthUnit::LightNanosecond, vec![("light nanosecond", "light nanoseconds")], ); // metric foot is 300 mm store.insert(LengthUnit::MetricFoot, vec![("metric foot", "metric feet")]); // boat length is 19 meters store.insert( LengthUnit::BoatLength, vec![("boat length", "boat lengths")], ); // block is 150 meters store.insert(LengthUnit::Block, vec![("block", "blocks")]); // siriometer is 15.8 light years store.insert(LengthUnit::Siriometer, vec![("siriometer", "siriometers")]); // banana is 8 inches store.insert(LengthUnit::Banana, vec![("banana", "bananas")]); let mut store_string = BTreeMap::new(); for (k, v) in store { store_string.insert( k, v.into_iter() .map(|(s1, s2)| (s1.to_string(), s2.to_string())) .collect(), ); } store_string }); #[derive(Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Debug)] pub enum LengthUnit { Meter(MetricPrefix), // 1/1000 inch Thou, // 25.4 mm Inch, // 12 inches Foot, // 3 feet Yard, // 5280 feet Mile, // 3 miles League, // 2 yards Fathom, // 1852 m NauticalMile, // 22 yards Chain, // 5 1/2 yards Rod, // 6371 km EarthRadius, // 384402 km LunarDistance, // 149597870700 m AstronomicalUnit, // 9460730472580.8 km LightYear, // 30856775814671.9 km Parsec, // 14400000000 light years HubbleLength, // 0.000000000000000000000000000000000016163 meters PlanckLength, // 1.57 meters Cana, // 52.92 cm Cubit, // 6.096 meters Rope, // 500 meters Li, // 0.75 meters Pace, // 1.0668 kilometers Verst, // 10 meters DoubleDeckerBus, // 100 yards FootballField, // 0.08 mm HumanHairThickness, // 1/8 mile Furlong, // 2.4 meters HorseLength, // 5.08 mm HorizontalPitch, // 19.05 mm HammerUnit, // 1.75 inches RackUnit, // 4 inches Hand, // 29.9792458 cm LightNanosecond, // 300 mm MetricFoot, // 19 meters BoatLength, // 150 meters Block, // 15.8 light years Siriometer, // 8 inches Banana, } impl ConvertTo for LengthUnit { // convert to meters: // 1. find the desired type in the length store // 2. multiply value by the type's meter value fn convert_to(self, value: BigFloat, into: LengthUnit) -> BigFloat { match self { LengthUnit::Meter(prefix) => { // unprefix let denom = LENGTH_STORE.get(&LengthUnit::Meter(prefix)).unwrap(); let store_value = LENGTH_STORE.get(&into).unwrap(); value .mul_full_prec(denom) .div(store_value, PRECISION, RoundingMode::None) } _ => { match into { LengthUnit::Meter(prefix) => { // unprefix let denom = LENGTH_STORE.get(&LengthUnit::Meter(prefix)).unwrap(); let store_value = LENGTH_STORE.get(&self).unwrap(); value .mul_full_prec(store_value) .div(denom, PRECISION, RoundingMode::None) } _ => { // convert to meters, then to desired value let meters = self.convert_to(value, LengthUnit::Meter(MetricPrefix::None)); LengthUnit::Meter(MetricPrefix::None).convert_to(meters, into) } } } } } } impl BestUnit for LengthUnit { fn best_unit(self, value: BigFloat) -> Self { let mut lowest_remainder = BigFloat::from_f64(f64::MAX, PRECISION); let mut lowest_value = BigFloat::from_f64(f64::MAX, PRECISION); let mut best_unit = LengthUnit::Meter(MetricPrefix::None); for unit in LENGTH_STORE.keys() { if *unit == self { continue; } match self { LengthUnit::Meter(prefix) => { // convert to meters, then to desired value let meters = self .convert_to(value.clone(), LengthUnit::Meter(MetricPrefix::None)); let store_value = LENGTH_STORE.get(unit).unwrap(); let rem = meters.rem(store_value); let div = meters.div(store_value, PRECISION, RoundingMode::None); if rem.le(&lowest_remainder) && div.le(&lowest_value) && div.ge(&BigFloat::from_f64(1.0, PRECISION)) { lowest_remainder = rem; lowest_value = div; best_unit = *unit; } } _ => { match unit { LengthUnit::Meter(prefix) => { // unprefix let denom = LENGTH_STORE.get(&LengthUnit::Meter(*prefix)).unwrap(); let store_value = LENGTH_STORE.get(&self).unwrap(); let rem = value.mul_full_prec(store_value).rem(denom); let div = value.mul_full_prec(store_value).div( denom, PRECISION, RoundingMode::None, ); if rem.le(&lowest_remainder) && div.le(&lowest_value) && div.ge(&BigFloat::from_f64(1.0, PRECISION)) { lowest_remainder = rem; lowest_value = div; best_unit = *unit; } } _ => { // convert to meters, then to desired value let meters = self .convert_to(value.clone(), LengthUnit::Meter(MetricPrefix::None)); let store_value = LENGTH_STORE.get(unit).unwrap(); let rem = meters.rem(store_value); let div = meters.div(store_value, PRECISION, RoundingMode::None); if rem.le(&lowest_remainder) && div.le(&lowest_value) && div.ge(&BigFloat::from_f64(1.0, PRECISION)) { lowest_remainder = rem; lowest_value = div; best_unit = *unit; } } } } } } best_unit } } impl FromUnitName for LengthUnit { fn parse(value: String) -> Option<(LengthUnit, String)> { let mut sorted_checklist: Vec<(String, String, LengthUnit)> = vec![]; for (unit, names) in LENGTH_NAMES.iter() { for (single, plural) in names { sorted_checklist.push((single.clone(), plural.clone(), *unit)); } } sorted_checklist.sort_by(|b, a| a.1.len().cmp(&b.1.len())); let input = value.trim_start(); let diff = value.len() - input.len(); for (name, plural, unit) in sorted_checklist { if input.to_lowercase().starts_with(&name) || input.to_lowercase().starts_with(&plural) { let len = if plural.len() > name.len() { if input.to_lowercase().starts_with(&plural) { plural.len() } else { name.len() } } else if input.to_lowercase().starts_with(&name) { name.len() } else { plural.len() }; return Some((unit, value[diff + len..].to_string())); } } None } } impl UnitName for LengthUnit { fn unit_name(self, value: BigFloat) -> String { if value == BigFloat::from_f64(1.0, PRECISION) { // singular LENGTH_NAMES.get(&self).unwrap().first().unwrap().0.clone() } else { // plural LENGTH_NAMES.get(&self).unwrap().first().unwrap().1.clone() } } }