RedEnginePress logo
RedEnginePress
AlgorithmsLanguagesPlaygroundAbout

Weight

P
//! Conversion of weight units.
//!
//! This module provides conversion between various weight units including:
//! - Metric: Gigatonne (Gt), Megatonne (Mt), Metric Ton (t), Kilogram (kg), Gram (g),
//!   Milligram (mg), Microgram (μg), Nanogram (ng), Picogram (pg)
//! - Imperial/US: Long Ton, Short Ton, Hundredweight (cwt), Quarter (qtr), Stone (st),
//!   Pound (lb), Ounce (oz), Dram (dr), Grain (gr)
//! - Troy: Troy Pound (lb t), Troy Ounce (oz t), Pennyweight (dwt)
//! - Other: Carat (ct), Atomic Mass Unit (amu)
//!
//! # References
//! - [Kilogram](https://en.wikipedia.org/wiki/Kilogram)
//! - [Gram](https://en.wikipedia.org/wiki/Gram)
//! - [Milligram](https://en.wikipedia.org/wiki/Milligram)
//! - [Microgram](https://en.wikipedia.org/wiki/Microgram)
//! - [Nanogram](https://en.wikipedia.org/wiki/Orders_of_magnitude_(mass))
//! - [Picogram](https://en.wikipedia.org/wiki/Orders_of_magnitude_(mass))
//! - [Tonne](https://en.wikipedia.org/wiki/Tonne)
//! - [Gigatonne](https://en.wikipedia.org/wiki/Tonne#Derived_units)
//! - [Megatonne](https://en.wikipedia.org/wiki/Tonne#Derived_units)
//! - [Long Ton](https://en.wikipedia.org/wiki/Long_ton)
//! - [Short Ton](https://en.wikipedia.org/wiki/Short_ton)
//! - [Pound](https://en.wikipedia.org/wiki/Pound_(mass))
//! - [Ounce](https://en.wikipedia.org/wiki/Ounce)
//! - [Stone](https://en.wikipedia.org/wiki/Stone_(unit))
//! - [Quarter](https://en.wikipedia.org/wiki/Quarter_(unit))
//! - [Hundredweight](https://en.wikipedia.org/wiki/Hundredweight)
//! - [Grain](https://en.wikipedia.org/wiki/Grain_(unit))
//! - [Dram](https://en.wikipedia.org/wiki/Dram_(unit))
//! - [Troy Pound](https://en.wikipedia.org/wiki/Troy_weight)
//! - [Troy Ounce](https://en.wikipedia.org/wiki/Troy_weight)
//! - [Pennyweight](https://en.wikipedia.org/wiki/Pennyweight)
//! - [Carat](https://en.wikipedia.org/wiki/Carat_(mass))
//! - [Dalton (Atomic Mass Unit)](https://en.wikipedia.org/wiki/Dalton_(unit))

use std::fmt;
use std::str::FromStr;

/// Trait for types that can be converted into a WeightUnit
pub trait IntoWeightUnit {
    fn into_weight_unit(self) -> Result<WeightUnit, String>;
}

impl IntoWeightUnit for WeightUnit {
    fn into_weight_unit(self) -> Result<WeightUnit, String> {
        Ok(self)
    }
}

impl IntoWeightUnit for &str {
    fn into_weight_unit(self) -> Result<WeightUnit, String> {
        WeightUnit::from_str(self)
    }
}

impl IntoWeightUnit for String {
    fn into_weight_unit(self) -> Result<WeightUnit, String> {
        WeightUnit::from_str(&self)
    }
}

/// Supported weight units
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WeightUnit {
    // Large metric units
    Gigatonne,
    Megatonne,
    MetricTon,

    // Standard metric units
    Kilogram,
    Gram,
    Milligram,
    Microgram,
    Nanogram,
    Picogram,

    // Imperial/US tons and large units
    LongTon,
    ShortTon,
    Hundredweight,
    Quarter,

    // Imperial/US common units
    Stone,
    Pound,
    Ounce,
    Dram,
    Grain,

    // Troy weight system
    TroyPound,
    TroyOunce,
    Pennyweight,

    // Other units
    Carat,
    AtomicMassUnit,
}

impl fmt::Display for WeightUnit {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            // Large metric units
            Self::Gigatonne => "Gt",
            Self::Megatonne => "Mt",
            Self::MetricTon => "t",

            // Standard metric units
            Self::Kilogram => "kg",
            Self::Gram => "g",
            Self::Milligram => "mg",
            Self::Microgram => "μg",
            Self::Nanogram => "ng",
            Self::Picogram => "pg",

            // Imperial/US tons and large units
            Self::LongTon => "long ton",
            Self::ShortTon => "short ton",
            Self::Hundredweight => "cwt",
            Self::Quarter => "qtr",

            // Imperial/US common units
            Self::Stone => "st",
            Self::Pound => "lb",
            Self::Ounce => "oz",
            Self::Dram => "dr",
            Self::Grain => "gr",

            // Troy weight system
            Self::TroyPound => "lb t",
            Self::TroyOunce => "oz t",
            Self::Pennyweight => "dwt",

            // Other units
            Self::Carat => "ct",
            Self::AtomicMassUnit => "amu",
        };
        write!(f, "{s}")
    }
}

impl WeightUnit {
    /// Get the conversion factor to convert this unit to kilograms
    fn to_kilogram_factor(self) -> f64 {
        match self {
            // Large metric units
            Self::Gigatonne => 1e12,
            Self::Megatonne => 1e9,
            Self::MetricTon => 1_000.0,

            // Standard metric units (based on 1 kg = 1000 g)
            Self::Kilogram => 1.0,
            Self::Gram => 1e-3,
            Self::Milligram => 1e-6,
            Self::Microgram => 1e-9,
            Self::Nanogram => 1e-12,
            Self::Picogram => 1e-15,

            // Imperial/US tons and large units
            // Using precise values: 1 lb = 0.45359237 kg exactly (international avoirdupois pound)
            Self::LongTon => 1_016.046_908_8, // 2240 lb × 0.45359237 kg/lb
            Self::ShortTon => 907.184_74,     // 2000 lb × 0.45359237 kg/lb
            Self::Hundredweight => 50.802_345_44, // 112 lb × 0.45359237 kg/lb
            Self::Quarter => 12.700_586_36,   // 28 lb × 0.45359237 kg/lb

            // Imperial/US common units (based on 1 lb = 0.45359237 kg exactly)
            Self::Stone => 6.350_293_18,      // 14 lb × 0.45359237 kg/lb
            Self::Pound => 0.453_592_37,      // Exactly defined
            Self::Ounce => 0.028_349_523_125, // 1/16 lb
            Self::Dram => 0.001_771_845_195_312_5, // 1/256 lb
            Self::Grain => 0.000_064_798_91,  // 1/7000 lb

            // Troy weight system (1 troy lb = 0.3732417216 kg exactly)
            Self::TroyPound => 0.373_241_721_6, // Exactly defined
            Self::TroyOunce => 0.031_103_476_8, // 1/12 troy lb
            Self::Pennyweight => 0.001_555_173_84, // 1/240 troy lb

            // Other units
            Self::Carat => 0.000_2,                       // Exactly 200 mg
            Self::AtomicMassUnit => 1.660_539_066_60e-27, // 2019 CODATA value
        }
    }

    /// Get all supported units as strings
    pub fn supported_units() -> Vec<&'static str> {
        vec![
            "gigatonne",
            "megatonne",
            "metric-ton",
            "kilogram",
            "gram",
            "milligram",
            "microgram",
            "nanogram",
            "picogram",
            "long-ton",
            "short-ton",
            "hundredweight",
            "quarter",
            "stone",
            "pound",
            "ounce",
            "dram",
            "grain",
            "troy-pound",
            "troy-ounce",
            "pennyweight",
            "carat",
            "atomic-mass-unit",
        ]
    }
}

impl FromStr for WeightUnit {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let unit = match s.to_lowercase().as_str() {
            // Large metric units
            "gigatonne" | "gt" | "gigaton" => Self::Gigatonne,
            "megatonne" | "mt" | "megaton" => Self::Megatonne,
            "metric-ton" | "metric_ton" | "tonne" | "t" | "ton" => Self::MetricTon,

            // Standard metric units
            "kilogram" | "kg" | "kilo" => Self::Kilogram,
            "gram" | "g" | "gm" => Self::Gram,
            "milligram" | "mg" => Self::Milligram,
            "microgram" | "μg" | "ug" | "mcg" => Self::Microgram,
            "nanogram" | "ng" => Self::Nanogram,
            "picogram" | "pg" => Self::Picogram,

            // Imperial/US tons and large units
            "long-ton" | "long_ton" | "imperial_ton" | "uk_ton" => Self::LongTon,
            "short-ton" | "short_ton" | "us_ton" => Self::ShortTon,
            "hundredweight" | "cwt" => Self::Hundredweight,
            "quarter" | "qtr" => Self::Quarter,

            // Imperial/US common units
            "stone" | "st" => Self::Stone,
            "pound" | "lb" | "lbs" => Self::Pound,
            "ounce" | "oz" => Self::Ounce,
            "dram" | "drachm" | "dr" => Self::Dram,
            "grain" | "gr" => Self::Grain,

            // Troy weight system
            "troy-pound" | "troy_pound" | "lb_t" | "lbt" => Self::TroyPound,
            "troy-ounce" | "troy_ounce" | "oz_t" | "ozt" => Self::TroyOunce,
            "pennyweight" | "dwt" | "pwt" => Self::Pennyweight,

            // Other units
            "carat" | "carrat" | "ct" => Self::Carat,
            "atomic-mass-unit" | "atomic_mass_unit" | "amu" | "dalton" | "da" => {
                Self::AtomicMassUnit
            }
            _ => return Err(format!("Unknown weight unit: {s}")),
        };
        Ok(unit)
    }
}

/// Convert weight from one unit to another.
///
/// This function accepts both `WeightUnit` enums and string identifiers.
///
/// # Arguments
///
/// * `value` - The numerical value to convert
/// * `from_unit` - The unit to convert from (can be a `WeightUnit` enum or a string)
/// * `to_unit` - The unit to convert to (can be a `WeightUnit` enum or a string)
///
/// # Returns
///
/// The converted value, or an error if the unit is invalid
///
/// # Examples
///
/// Using enums (type-safe):
/// ```ignore
/// let result = convert_weight(100.0, WeightUnit::Pound, WeightUnit::Kilogram);
/// ```
///
/// Using strings (convenient):
/// ```ignore
/// let result = convert_weight(100.0, "pound", "kilogram");
/// ```
pub fn convert_weight<F, T>(value: f64, from_unit: F, to_unit: T) -> Result<f64, String>
where
    F: IntoWeightUnit,
    T: IntoWeightUnit,
{
    let from = from_unit.into_weight_unit().map_err(|_| {
        format!(
            "Invalid 'from_unit' value. Supported values are:\n{}",
            WeightUnit::supported_units().join(", ")
        )
    })?;

    let to = to_unit.into_weight_unit().map_err(|_| {
        format!(
            "Invalid 'to_unit' value. Supported values are:\n{}",
            WeightUnit::supported_units().join(", ")
        )
    })?;

    // Convert to kilograms first, then to target unit
    let kilograms = value * from.to_kilogram_factor();
    Ok(kilograms / to.to_kilogram_factor())
}

#[cfg(test)]
mod tests {
    use super::*;

    const EPSILON: f64 = 1e-6; // Tolerance for floating point comparisons

    fn approx_eq(a: f64, b: f64) -> bool {
        let diff = (a - b).abs();
        // Use relative comparison for large numbers, absolute for small numbers
        if a.abs() > 1e10 || b.abs() > 1e10 {
            let max = a.abs().max(b.abs());
            diff / max < EPSILON
        } else {
            diff < EPSILON
        }
    }

    #[test]
    fn test_kilogram_conversions() {
        // Test conversions from kilogram
        assert!(approx_eq(
            convert_weight(4.0, "kilogram", "kilogram").unwrap(),
            4.0
        ));
        assert!(approx_eq(
            convert_weight(1.0, "kilogram", "gram").unwrap(),
            1_000.0
        ));
        assert!(approx_eq(
            convert_weight(4.0, "kilogram", "milligram").unwrap(),
            4_000_000.0
        ));
        assert!(approx_eq(
            convert_weight(4.0, "kilogram", "metric-ton").unwrap(),
            0.004
        ));
        assert!(approx_eq(
            convert_weight(3.0, "kilogram", "long-ton").unwrap(),
            0.002_952_396_2
        ));
        assert!(approx_eq(
            convert_weight(1.0, "kilogram", "short-ton").unwrap(),
            0.001_102_311_3
        ));
        assert!(approx_eq(
            convert_weight(4.0, "kilogram", "pound").unwrap(),
            8.818_490_487
        ));
        assert!(approx_eq(
            convert_weight(5.0, "kilogram", "stone").unwrap(),
            0.787_365_222
        ));
        assert!(approx_eq(
            convert_weight(4.0, "kilogram", "ounce").unwrap(),
            141.095_847_8
        ));
        assert!(approx_eq(
            convert_weight(3.0, "kilogram", "carat").unwrap(),
            15_000.0
        ));
        assert!(approx_eq(
            convert_weight(1.0, "kilogram", "atomic-mass-unit").unwrap(),
            6.022_140_762e26
        ));
    }

    #[test]
    fn test_large_metric_conversions() {
        // Test gigatonne conversions
        assert!(approx_eq(
            convert_weight(1.0, "gigatonne", "megatonne").unwrap(),
            1_000.0
        ));
        assert!(approx_eq(
            convert_weight(1.0, "gigatonne", "metric-ton").unwrap(),
            1e9
        ));
        assert!(approx_eq(
            convert_weight(1.0, "gigatonne", "kilogram").unwrap(),
            1e12
        ));
        assert!(approx_eq(
            convert_weight(1.0, "gigatonne", "gram").unwrap(),
            1e15
        ));

        // Test megatonne conversions
        assert!(approx_eq(
            convert_weight(1.0, "megatonne", "metric-ton").unwrap(),
            1_000_000.0
        ));
        assert!(approx_eq(
            convert_weight(1.0, "megatonne", "kilogram").unwrap(),
            1e9
        ));
        assert!(approx_eq(
            convert_weight(1.0, "megatonne", "gram").unwrap(),
            1e12
        ));
    }

    #[test]
    fn test_gram_conversions() {
        // Test conversions from gram
        assert!(approx_eq(
            convert_weight(1.0, "gram", "kilogram").unwrap(),
            0.001
        ));
        assert!(approx_eq(convert_weight(3.0, "gram", "gram").unwrap(), 3.0));
        assert!(approx_eq(
            convert_weight(2.0, "gram", "milligram").unwrap(),
            2_000.0
        ));
        assert!(approx_eq(
            convert_weight(4.0, "gram", "metric-ton").unwrap(),
            4e-6
        ));
        assert!(approx_eq(
            convert_weight(3.0, "gram", "pound").unwrap(),
            0.006_613_867_8
        ));
    }

    #[test]
    fn test_milligram_conversions() {
        // Test conversions from milligram
        assert!(approx_eq(
            convert_weight(1.0, "milligram", "kilogram").unwrap(),
            1e-6
        ));
        assert!(approx_eq(
            convert_weight(2.0, "milligram", "gram").unwrap(),
            0.002
        ));
        assert!(approx_eq(
            convert_weight(3.0, "milligram", "milligram").unwrap(),
            3.0
        ));
        assert!(approx_eq(
            convert_weight(1.0, "milligram", "carat").unwrap(),
            0.005
        ));
    }

    #[test]
    fn test_small_metric_conversions() {
        // Test microgram conversions (1 μg = 0.000001 g)
        assert!(approx_eq(
            convert_weight(1.0, "microgram", "gram").unwrap(),
            1e-6
        ));
        assert!(approx_eq(
            convert_weight(1_000.0, "microgram", "milligram").unwrap(),
            1.0
        ));
        assert!(approx_eq(
            convert_weight(1.0, "microgram", "kilogram").unwrap(),
            1e-9
        ));

        // Test nanogram conversions (1 ng = 0.000000001 g)
        assert!(approx_eq(
            convert_weight(1.0, "nanogram", "gram").unwrap(),
            1e-9
        ));
        assert!(approx_eq(
            convert_weight(1_000.0, "nanogram", "microgram").unwrap(),
            1.0
        ));
        assert!(approx_eq(
            convert_weight(1.0, "nanogram", "kilogram").unwrap(),
            1e-12
        ));

        // Test picogram conversions (1 pg = 0.000000000001 g)
        assert!(approx_eq(
            convert_weight(1.0, "picogram", "gram").unwrap(),
            1e-12
        ));
        assert!(approx_eq(
            convert_weight(1_000.0, "picogram", "nanogram").unwrap(),
            1.0
        ));
        assert!(approx_eq(
            convert_weight(1.0, "picogram", "kilogram").unwrap(),
            1e-15
        ));
    }

    #[test]
    fn test_metric_ton_conversions() {
        // Test conversions from metric ton
        assert!(approx_eq(
            convert_weight(2.0, "metric-ton", "kilogram").unwrap(),
            2_000.0
        ));
        assert!(approx_eq(
            convert_weight(2.0, "metric-ton", "gram").unwrap(),
            2_000_000.0
        ));
        assert!(approx_eq(
            convert_weight(3.0, "metric-ton", "milligram").unwrap(),
            3_000_000_000.0
        ));
        assert!(approx_eq(
            convert_weight(2.0, "metric-ton", "metric-ton").unwrap(),
            2.0
        ));
        assert!(approx_eq(
            convert_weight(3.0, "metric-ton", "pound").unwrap(),
            6_613.867_865
        ));
    }

    #[test]
    fn test_long_ton_conversions() {
        // Test conversions from long ton (UK ton = 1016.0469088 kg precisely)
        assert!(approx_eq(
            convert_weight(4.0, "long-ton", "kilogram").unwrap(),
            4_064.187_635_2
        ));
        assert!(approx_eq(
            convert_weight(4.0, "long-ton", "gram").unwrap(),
            4_064_187.635_2
        ));
        assert!(approx_eq(
            convert_weight(4.0, "long-ton", "metric-ton").unwrap(),
            4.064_187_635_2
        ));
        assert!(approx_eq(
            convert_weight(3.0, "long-ton", "long-ton").unwrap(),
            3.0
        ));
        assert!(approx_eq(
            convert_weight(1.0, "long-ton", "short-ton").unwrap(),
            1.12
        ));
    }

    #[test]
    fn test_imperial_large_units() {
        // Test hundredweight (112 lb = 50.80234544 kg precisely)
        assert!(approx_eq(
            convert_weight(1.0, "hundredweight", "kilogram").unwrap(),
            50.802_345_44
        ));
        assert!(approx_eq(
            convert_weight(1.0, "hundredweight", "gram").unwrap(),
            50_802.345_44
        ));
        assert!(approx_eq(
            convert_weight(1.0, "hundredweight", "pound").unwrap(),
            112.0
        ));
        assert!(approx_eq(
            convert_weight(20.0, "hundredweight", "long-ton").unwrap(),
            1.0
        ));

        // Test quarter (28 lb = 12.70058636 kg precisely)
        assert!(approx_eq(
            convert_weight(1.0, "quarter", "kilogram").unwrap(),
            12.700_586_36
        ));
        assert!(approx_eq(
            convert_weight(1.0, "quarter", "gram").unwrap(),
            12_700.586_36
        ));
        assert!(approx_eq(
            convert_weight(1.0, "quarter", "pound").unwrap(),
            28.0
        ));
        assert!(approx_eq(
            convert_weight(4.0, "quarter", "hundredweight").unwrap(),
            1.0
        ));
    }

    #[test]
    fn test_short_ton_conversions() {
        // Test conversions from short ton (2000 lb = 907.18474 kg precisely)
        assert!(approx_eq(
            convert_weight(3.0, "short-ton", "kilogram").unwrap(),
            2_721.554_22
        ));
        assert!(approx_eq(
            convert_weight(3.0, "short-ton", "gram").unwrap(),
            2_721_554.22
        ));
        assert!(approx_eq(
            convert_weight(1.0, "short-ton", "milligram").unwrap(),
            907_184_740.0
        ));
        assert!(approx_eq(
            convert_weight(4.0, "short-ton", "metric-ton").unwrap(),
            3.628_738_96
        ));
        assert!(approx_eq(
            convert_weight(2.0, "short-ton", "pound").unwrap(),
            4_000.0
        ));
    }

    #[test]
    fn test_pound_conversions() {
        // Test conversions from pound (0.45359237 kg exactly)
        assert!(approx_eq(
            convert_weight(4.0, "pound", "kilogram").unwrap(),
            1.814_369_48
        ));
        assert!(approx_eq(
            convert_weight(2.0, "pound", "gram").unwrap(),
            907.184_74
        ));
        assert!(approx_eq(
            convert_weight(3.0, "pound", "milligram").unwrap(),
            1_360_777.11
        ));
        assert!(approx_eq(
            convert_weight(3.0, "pound", "pound").unwrap(),
            3.0
        ));
        assert!(approx_eq(
            convert_weight(1.0, "pound", "ounce").unwrap(),
            16.0
        ));
        assert!(approx_eq(
            convert_weight(1.0, "pound", "carat").unwrap(),
            2_267.961_85
        ));
    }

    #[test]
    fn test_stone_conversions() {
        // Test conversions from stone (14 lb = 6.35029318 kg precisely)
        assert!(approx_eq(
            convert_weight(5.0, "stone", "kilogram").unwrap(),
            31.751_465_9
        ));
        assert!(approx_eq(
            convert_weight(2.0, "stone", "gram").unwrap(),
            12_700.586_36
        ));
        assert!(approx_eq(
            convert_weight(2.0, "stone", "pound").unwrap(),
            28.0
        ));
        assert!(approx_eq(
            convert_weight(1.0, "stone", "ounce").unwrap(),
            224.0
        ));
    }

    #[test]
    fn test_ounce_conversions() {
        // Test conversions from ounce (1/16 lb = 0.028349523125 kg precisely)
        assert!(approx_eq(
            convert_weight(3.0, "ounce", "kilogram").unwrap(),
            0.085_048_569_375
        ));
        assert!(approx_eq(
            convert_weight(3.0, "ounce", "gram").unwrap(),
            85.048_569_375
        ));
        assert!(approx_eq(
            convert_weight(1.0, "ounce", "pound").unwrap(),
            0.0625
        ));
        assert!(approx_eq(
            convert_weight(2.0, "ounce", "ounce").unwrap(),
            2.0
        ));
        assert!(approx_eq(
            convert_weight(1.0, "ounce", "carat").unwrap(),
            141.747_615_625
        ));
    }

    #[test]
    fn test_small_imperial_units() {
        // Test dram (1/256 lb = 0.0017718451953125 kg precisely)
        assert!(approx_eq(
            convert_weight(1.0, "dram", "gram").unwrap(),
            1.771_845_195_312_5
        ));
        assert!(approx_eq(
            convert_weight(1.0, "dram", "kilogram").unwrap(),
            0.001_771_845_195_312_5
        ));
        assert!(approx_eq(
            convert_weight(256.0, "dram", "pound").unwrap(),
            1.0
        ));
        assert!(approx_eq(
            convert_weight(16.0, "dram", "ounce").unwrap(),
            1.0
        ));

        // Test grain (1/7000 lb = 0.00006479891 kg precisely)
        assert!(approx_eq(
            convert_weight(1.0, "grain", "gram").unwrap(),
            0.064_798_91
        ));
        assert!(approx_eq(
            convert_weight(1.0, "grain", "kilogram").unwrap(),
            0.000_064_798_91
        ));
        assert!(approx_eq(
            convert_weight(7000.0, "grain", "pound").unwrap(),
            1.0
        ));
    }

    #[test]
    fn test_carat_conversions() {
        // Test conversions from carat
        assert!(approx_eq(
            convert_weight(1.0, "carat", "kilogram").unwrap(),
            0.000_2
        ));
        assert!(approx_eq(
            convert_weight(4.0, "carat", "gram").unwrap(),
            0.8
        ));
        assert!(approx_eq(
            convert_weight(2.0, "carat", "milligram").unwrap(),
            400.0
        ));
        assert!(approx_eq(
            convert_weight(4.0, "carat", "carat").unwrap(),
            4.0
        ));
    }

    #[test]
    fn test_troy_weight_system() {
        // Test troy pound (0.3732417216 kg exactly)
        assert!(approx_eq(
            convert_weight(1.0, "troy-pound", "gram").unwrap(),
            373.241_721_6
        ));
        assert!(approx_eq(
            convert_weight(1.0, "troy-pound", "kilogram").unwrap(),
            0.373_241_721_6
        ));
        assert!(approx_eq(
            convert_weight(1.0, "troy-pound", "pound").unwrap(),
            0.822_857_143
        ));
        assert!(approx_eq(
            convert_weight(1.0, "troy-pound", "troy-ounce").unwrap(),
            12.0
        ));

        // Test troy ounce (1/12 troy lb = 0.0311034768 kg exactly)
        assert!(approx_eq(
            convert_weight(1.0, "troy-ounce", "gram").unwrap(),
            31.103_476_8
        ));
        assert!(approx_eq(
            convert_weight(1.0, "troy-ounce", "kilogram").unwrap(),
            0.031_103_476_8
        ));
        assert!(approx_eq(
            convert_weight(1.0, "troy-ounce", "ounce").unwrap(),
            1.097_142_857
        ));
        assert!(approx_eq(
            convert_weight(12.0, "troy-ounce", "troy-pound").unwrap(),
            1.0
        ));
        assert!(approx_eq(
            convert_weight(1.0, "troy-ounce", "pennyweight").unwrap(),
            20.0
        ));

        // Test pennyweight (1/240 troy lb = 0.00155517384 kg exactly)
        assert!(approx_eq(
            convert_weight(1.0, "pennyweight", "gram").unwrap(),
            1.555_173_84
        ));
        assert!(approx_eq(
            convert_weight(1.0, "pennyweight", "kilogram").unwrap(),
            0.001_555_173_84
        ));
        assert!(approx_eq(
            convert_weight(20.0, "pennyweight", "troy-ounce").unwrap(),
            1.0
        ));
        assert!(approx_eq(
            convert_weight(240.0, "pennyweight", "troy-pound").unwrap(),
            1.0
        ));
    }

    #[test]
    fn test_atomic_mass_unit_conversions() {
        // Test conversions from atomic mass unit
        assert!(approx_eq(
            convert_weight(4.0, "atomic-mass-unit", "kilogram").unwrap(),
            6.642_160_796e-27
        ));
        assert!(approx_eq(
            convert_weight(2.0, "atomic-mass-unit", "atomic-mass-unit").unwrap(),
            2.0
        ));
    }

    #[test]
    fn test_using_enums() {
        // Test using enums (type-safe)
        assert!(approx_eq(
            convert_weight(1.0, WeightUnit::Kilogram, WeightUnit::Gram).unwrap(),
            1_000.0
        ));
        assert!(approx_eq(
            convert_weight(100.0, WeightUnit::Pound, WeightUnit::Kilogram).unwrap(),
            45.359_237
        ));
    }

    #[test]
    fn test_mixed_usage() {
        // Test mixed usage (enum and string)
        assert!(approx_eq(
            convert_weight(1.0, WeightUnit::Kilogram, "pound").unwrap(),
            2.204_622_622
        ));
        assert!(approx_eq(
            convert_weight(16.0, "ounce", WeightUnit::Pound).unwrap(),
            1.0
        ));
    }

    #[test]
    fn test_invalid_units() {
        // Test invalid units
        assert!(convert_weight(4.0, "slug", "kilogram").is_err());
        assert!(convert_weight(4.0, "kilogram", "wrongUnit").is_err());
    }

    #[test]
    fn test_roundtrip_conversion() {
        // Test roundtrip conversion
        let original = 100.0;
        let converted = convert_weight(original, "pound", "kilogram").unwrap();
        let back = convert_weight(converted, "kilogram", "pound").unwrap();
        assert!(approx_eq(original, back));
    }

    #[test]
    fn test_string_ownership() {
        // Test String (owned) conversion
        let unit_string = String::from("kilogram");
        assert_eq!(
            unit_string.into_weight_unit().unwrap(),
            WeightUnit::Kilogram
        );

        let invalid_string = String::from("invalid");
        assert!(invalid_string.into_weight_unit().is_err());
    }

    #[test]
    fn test_display_implementation() {
        // Test Display implementation for all units
        assert_eq!(format!("{}", WeightUnit::Gigatonne), "Gt");
        assert_eq!(format!("{}", WeightUnit::Megatonne), "Mt");
        assert_eq!(format!("{}", WeightUnit::MetricTon), "t");
        assert_eq!(format!("{}", WeightUnit::Kilogram), "kg");
        assert_eq!(format!("{}", WeightUnit::Gram), "g");
        assert_eq!(format!("{}", WeightUnit::Milligram), "mg");
        assert_eq!(format!("{}", WeightUnit::Microgram), "μg");
        assert_eq!(format!("{}", WeightUnit::Nanogram), "ng");
        assert_eq!(format!("{}", WeightUnit::Picogram), "pg");
        assert_eq!(format!("{}", WeightUnit::LongTon), "long ton");
        assert_eq!(format!("{}", WeightUnit::ShortTon), "short ton");
        assert_eq!(format!("{}", WeightUnit::Hundredweight), "cwt");
        assert_eq!(format!("{}", WeightUnit::Quarter), "qtr");
        assert_eq!(format!("{}", WeightUnit::Stone), "st");
        assert_eq!(format!("{}", WeightUnit::Pound), "lb");
        assert_eq!(format!("{}", WeightUnit::Ounce), "oz");
        assert_eq!(format!("{}", WeightUnit::Dram), "dr");
        assert_eq!(format!("{}", WeightUnit::Grain), "gr");
        assert_eq!(format!("{}", WeightUnit::TroyPound), "lb t");
        assert_eq!(format!("{}", WeightUnit::TroyOunce), "oz t");
        assert_eq!(format!("{}", WeightUnit::Pennyweight), "dwt");
        assert_eq!(format!("{}", WeightUnit::Carat), "ct");
        assert_eq!(format!("{}", WeightUnit::AtomicMassUnit), "amu");
    }

    #[test]
    fn test_alternative_names() {
        // Test alternative unit names
        assert!(convert_weight(1.0, "kg", "gram").is_ok());
        assert!(convert_weight(1.0, "lb", "kilogram").is_ok());
        assert!(convert_weight(1.0, "oz", "gram").is_ok());
        assert!(convert_weight(1.0, "tonne", "kilogram").is_ok());
        assert!(convert_weight(1.0, "dalton", "kilogram").is_ok());
        assert!(convert_weight(1.0, "gt", "megatonne").is_ok());
        assert!(convert_weight(1.0, "mt", "metric-ton").is_ok());
        assert!(convert_weight(1.0, "ug", "gram").is_ok());
        assert!(convert_weight(1.0, "μg", "microgram").is_ok());
        assert!(convert_weight(1.0, "cwt", "kilogram").is_ok());
        assert!(convert_weight(1.0, "qtr", "quarter").is_ok());
        assert!(convert_weight(1.0, "dr", "gram").is_ok());
        assert!(convert_weight(1.0, "gr", "grain").is_ok());
        assert!(convert_weight(1.0, "ozt", "gram").is_ok());
        assert!(convert_weight(1.0, "lbt", "troy-pound").is_ok());
        assert!(convert_weight(1.0, "dwt", "gram").is_ok());
    }
}