pub mod base {
pub const INCH: (i64, i64) = (127, 5000);
pub const POUND: (i64, i64) = (45359237, 100_000_000);
pub const G_N: (i64, i64) = (980665, 100_000);
pub const CALORIE_TH: (i64, i64) = (4184, 1000);
pub const ATM: (i64, i64) = (101325, 1);
pub const NAUTICAL_MILE: (i64, i64) = (1852, 1);
pub const ASTRONOMICAL_UNIT: (i64, i64) = (149597870700, 1);
pub const BTU_IT: (i64, i64) = (52752792631, 50_000_000);
pub const IMPERIAL_GALLON: (i64, i64) = (454609, 100_000_000);
}
pub mod derived {
pub const FOOT: (i64, i64) = (381, 1250);
pub const YARD: (i64, i64) = (1143, 1250);
pub const MILE: (i64, i64) = (201168, 125);
pub const FATHOM: (i64, i64) = (1143, 625);
pub const POUND_FORCE: (i64, i64) = (8896443230521, 2_000_000_000_000);
pub const KILOGRAM_FORCE: (i64, i64) = (196133, 20000);
pub const HORSEPOWER: (i64, i64) = (37284993579113511, 50_000_000_000_000);
pub const METRIC_HORSEPOWER: (i64, i64) = (588399, 800);
pub const PSI: (i64, i64) = (8896443230521, 1_290_320_000);
pub const TORR: (i64, i64) = (20265, 152);
pub const FOOT_POUND: (i64, i64) = (3389544870828501, 2_500_000_000_000_000);
pub const US_GALLON: (i64, i64) = (473176473, 125_000_000_000);
pub const US_QUART: (i64, i64) = (473176473, 500_000_000_000);
pub const US_PINT: (i64, i64) = (473176473, 1_000_000_000_000);
pub const US_FLUID_OUNCE: (i64, i64) = (473176473, 16_000_000_000_000);
pub const US_TABLESPOON: (i64, i64) = (473176473, 32_000_000_000_000);
pub const OUNCE: (i64, i64) = (45359237, 1_600_000_000);
pub const SHORT_TON: (i64, i64) = (45359237, 50_000);
pub const LONG_TON: (i64, i64) = (317514659, 312_500);
pub const GRAIN: (i64, i64) = (6479891, 100_000_000_000);
pub const SLUG: (i64, i64) = (8896443230521, 609_600_000_000);
pub const KNOT: (i64, i64) = (463, 900);
pub const MPH: (i64, i64) = (1397, 3125);
pub const DYNE: (i64, i64) = (1, 100_000);
pub const ERG: (i64, i64) = (1, 10_000_000);
}
#[cfg(test)]
mod tests {
use super::*;
use num_bigint::BigInt;
use num_rational::Ratio;
fn r(n: i64, d: i64) -> Ratio<BigInt> {
Ratio::new(BigInt::from(n), BigInt::from(d))
}
fn check(computed: Ratio<BigInt>, expected: (i64, i64), name: &str) {
let en = BigInt::from(expected.0);
let ed = BigInt::from(expected.1);
let expected_r = Ratio::new(en.clone(), ed.clone());
assert_eq!(
computed, expected_r,
"{name}: computed {computed} != expected {}/{}",
en, ed
);
}
fn check_float(val: (i64, i64), expected: f64, name: &str) {
let actual = val.0 as f64 / val.1 as f64;
let rel_err = ((actual - expected) / expected).abs();
assert!(
rel_err < 1e-14,
"{name}: {actual} vs {expected}, relative error {rel_err}"
);
}
#[test]
fn base_inch() {
check_float(base::INCH, 0.0254, "inch");
}
#[test]
fn base_pound() {
check_float(base::POUND, 0.45359237, "pound");
}
#[test]
fn base_g_n() {
check_float(base::G_N, 9.80665, "g_n");
}
#[test]
fn base_cal() {
check_float(base::CALORIE_TH, 4.184, "calorie");
}
#[test]
fn base_atm() {
assert_eq!(base::ATM, (101325, 1));
}
#[test]
fn base_nmi() {
assert_eq!(base::NAUTICAL_MILE, (1852, 1));
}
#[test]
fn verify_foot() {
let inch = r(base::INCH.0, base::INCH.1);
check(r(12, 1) * inch, derived::FOOT, "foot = 12 inches");
}
#[test]
fn verify_yard() {
let foot = r(derived::FOOT.0, derived::FOOT.1);
check(r(3, 1) * foot, derived::YARD, "yard = 3 feet");
}
#[test]
fn verify_mile() {
let foot = r(derived::FOOT.0, derived::FOOT.1);
check(r(5280, 1) * foot, derived::MILE, "mile = 5280 feet");
}
#[test]
fn verify_fathom() {
let yard = r(derived::YARD.0, derived::YARD.1);
check(r(2, 1) * yard, derived::FATHOM, "fathom = 2 yards");
}
#[test]
fn verify_pound_force() {
let pound = r(base::POUND.0, base::POUND.1);
let g_n = r(base::G_N.0, base::G_N.1);
check(pound * g_n, derived::POUND_FORCE, "lbf = lb × g_n");
}
#[test]
fn verify_kilogram_force() {
let g_n = r(base::G_N.0, base::G_N.1);
check(g_n, derived::KILOGRAM_FORCE, "kgf = g_n");
}
#[test]
fn verify_horsepower() {
let foot = r(derived::FOOT.0, derived::FOOT.1);
let lbf = r(derived::POUND_FORCE.0, derived::POUND_FORCE.1);
let hp = r(33000, 1) * foot * lbf / r(60, 1);
check(hp, derived::HORSEPOWER, "hp = 33000 ft·lbf/min");
}
#[test]
fn verify_metric_horsepower() {
let kgf = r(derived::KILOGRAM_FORCE.0, derived::KILOGRAM_FORCE.1);
check(
r(75, 1) * kgf,
derived::METRIC_HORSEPOWER,
"PS = 75 kgf·m/s",
);
}
#[test]
fn verify_psi() {
let lbf = r(derived::POUND_FORCE.0, derived::POUND_FORCE.1);
let inch = r(base::INCH.0, base::INCH.1);
check(lbf / (inch.clone() * inch), derived::PSI, "psi = lbf/in²");
}
#[test]
fn verify_torr() {
let atm = r(base::ATM.0, base::ATM.1);
check(atm / r(760, 1), derived::TORR, "torr = atm/760");
}
#[test]
fn verify_foot_pound() {
let foot = r(derived::FOOT.0, derived::FOOT.1);
let lbf = r(derived::POUND_FORCE.0, derived::POUND_FORCE.1);
check(foot * lbf, derived::FOOT_POUND, "ft·lbf = foot × lbf");
}
#[test]
fn verify_us_gallon() {
let inch = r(base::INCH.0, base::INCH.1);
let in3 = inch.clone() * inch.clone() * inch;
check(r(231, 1) * in3, derived::US_GALLON, "US gal = 231 in³");
}
#[test]
fn verify_us_quart() {
let gal = r(derived::US_GALLON.0, derived::US_GALLON.1);
check(gal / r(4, 1), derived::US_QUART, "US qt = gal/4");
}
#[test]
fn verify_us_pint() {
let gal = r(derived::US_GALLON.0, derived::US_GALLON.1);
check(gal / r(8, 1), derived::US_PINT, "US pt = gal/8");
}
#[test]
fn verify_us_fl_oz() {
let gal = r(derived::US_GALLON.0, derived::US_GALLON.1);
check(
gal / r(128, 1),
derived::US_FLUID_OUNCE,
"US fl oz = gal/128",
);
}
#[test]
fn verify_ounce() {
let pound = r(base::POUND.0, base::POUND.1);
check(pound / r(16, 1), derived::OUNCE, "oz = lb/16");
}
#[test]
fn verify_short_ton() {
let pound = r(base::POUND.0, base::POUND.1);
check(
r(2000, 1) * pound,
derived::SHORT_TON,
"short ton = 2000 lb",
);
}
#[test]
fn verify_long_ton() {
let pound = r(base::POUND.0, base::POUND.1);
check(r(2240, 1) * pound, derived::LONG_TON, "long ton = 2240 lb");
}
#[test]
fn verify_grain() {
let pound = r(base::POUND.0, base::POUND.1);
check(pound / r(7000, 1), derived::GRAIN, "grain = lb/7000");
}
#[test]
fn verify_slug() {
let lbf = r(derived::POUND_FORCE.0, derived::POUND_FORCE.1);
let foot = r(derived::FOOT.0, derived::FOOT.1);
check(lbf / foot, derived::SLUG, "slug = lbf·s²/ft");
}
#[test]
fn verify_knot() {
let nmi = r(base::NAUTICAL_MILE.0, base::NAUTICAL_MILE.1);
check(nmi / r(3600, 1), derived::KNOT, "knot = nmi/hr");
}
#[test]
fn verify_mph() {
let mile = r(derived::MILE.0, derived::MILE.1);
check(mile / r(3600, 1), derived::MPH, "mph = mile/hr");
}
#[test]
fn verify_hp_float() {
check_float(derived::HORSEPOWER, 745.699_871_582_270_2, "horsepower");
}
#[test]
fn verify_psi_float() {
check_float(derived::PSI, 6894.757293168362, "psi");
}
}