unitforge 0.4.3

A library for unit and quantity consistent computations in Rust
Documentation
use crate::MAX_ABS_QUANTITY_POWER;
#[cfg(feature = "no_std")]
use num_traits::float::FloatCore;

#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
const SQRT_10: f64 = 3.162_277_660_168_379;
#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
const INV_SQRT_10: f64 = 0.31622776601683794;

const fn clamp_power_const(power: i64) -> i32 {
    if power < -(MAX_ABS_QUANTITY_POWER as i64) {
        -MAX_ABS_QUANTITY_POWER
    } else if power > MAX_ABS_QUANTITY_POWER as i64 {
        MAX_ABS_QUANTITY_POWER
    } else {
        power as i32
    }
}

fn clamp_power(power: i64) -> i32 {
    clamp_power_const(power)
}

pub const fn saturating_power_add_const(lhs: i32, rhs: i32) -> i32 {
    clamp_power_const(lhs as i64 + rhs as i64)
}

pub const fn saturating_power_sub_const(lhs: i32, rhs: i32) -> i32 {
    clamp_power_const(lhs as i64 - rhs as i64)
}

fn pow10(power: i32) -> f64 {
    #[cfg(feature = "no_std")]
    {
        <f64 as FloatCore>::powi(10_f64, power)
    }
    #[cfg(not(feature = "no_std"))]
    {
        10_f64.powi(power)
    }
}

pub const fn pow10_const(power: i32) -> f64 {
    let mut result = 1.0;
    if power >= 0 {
        let mut remaining = power;
        while remaining > 0 {
            result *= 10.0;
            remaining -= 1;
        }
    } else {
        let mut remaining = power;
        while remaining < 0 {
            result /= 10.0;
            remaining += 1;
        }
    }
    result
}

#[allow(dead_code)]
pub fn saturating_power_add(lhs: i32, rhs: i32) -> i32 {
    saturating_power_add_const(lhs, rhs)
}

#[allow(dead_code)]
pub fn saturating_power_sub(lhs: i32, rhs: i32) -> i32 {
    saturating_power_sub_const(lhs, rhs)
}

#[allow(dead_code)]
pub fn average_power(lhs: i32, rhs: i32) -> i32 {
    let average = (i64::from(lhs) + i64::from(rhs)) / 2;
    clamp_power(average)
}

#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
pub const fn canonicalize_quantity_parts_const(multiplier: f64, power: i32) -> (f64, i32) {
    if multiplier == 0.0 {
        return (0.0, 0);
    }

    if multiplier.is_nan() {
        return (f64::NAN, 0);
    }

    if multiplier.is_infinite() {
        return (multiplier, 0);
    }

    let mut normalized_multiplier = multiplier;
    let mut normalized_power = power;
    let mut abs_multiplier = if normalized_multiplier < 0.0 {
        -normalized_multiplier
    } else {
        normalized_multiplier
    };

    while abs_multiplier >= SQRT_10 {
        normalized_multiplier /= 10.0;
        abs_multiplier /= 10.0;
        normalized_power = saturating_power_add_const(normalized_power, 1);
    }

    while abs_multiplier < INV_SQRT_10 {
        normalized_multiplier *= 10.0;
        abs_multiplier *= 10.0;
        normalized_power = saturating_power_sub_const(normalized_power, 1);
    }

    if normalized_power <= -MAX_ABS_QUANTITY_POWER {
        (0.0, 0)
    } else if normalized_power >= MAX_ABS_QUANTITY_POWER {
        if normalized_multiplier < 0.0 {
            (f64::NEG_INFINITY, 0)
        } else {
            (f64::INFINITY, 0)
        }
    } else {
        (normalized_multiplier, normalized_power)
    }
}

#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
pub const fn value_to_quantity_parts_const(value: f64) -> (f64, i32) {
    if value == 0.0 {
        return (0.0, 0);
    }

    if value.is_nan() {
        return (f64::NAN, 0);
    }

    if value.is_infinite() {
        return (value, 0);
    }

    let mut multiplier = value;
    let mut power = 0;
    let mut abs_multiplier = if multiplier < 0.0 {
        -multiplier
    } else {
        multiplier
    };

    while abs_multiplier >= 10.0 {
        multiplier /= 10.0;
        abs_multiplier /= 10.0;
        power = saturating_power_add_const(power, 1);
    }

    while abs_multiplier < 1.0 {
        multiplier *= 10.0;
        abs_multiplier *= 10.0;
        power = saturating_power_sub_const(power, 1);
    }

    canonicalize_quantity_parts_const(multiplier, power)
}

#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
pub fn canonicalize_quantity_parts(multiplier: f64, power: i32) -> (f64, i32) {
    if multiplier == 0.0 {
        return (0.0, 0);
    }

    if multiplier.is_nan() {
        return (f64::NAN, 0);
    }

    if multiplier.is_infinite() {
        return (multiplier, 0);
    }

    let power_on_multiplier = multiplier.abs().log10().round() as i32;
    let normalized_multiplier = multiplier / 10_f64.powi(power_on_multiplier);
    let normalized_power = saturating_power_add(power, power_on_multiplier);

    if normalized_power <= -MAX_ABS_QUANTITY_POWER {
        (0.0, 0)
    } else if normalized_power >= MAX_ABS_QUANTITY_POWER {
        if normalized_multiplier.is_sign_negative() {
            (f64::NEG_INFINITY, 0)
        } else {
            (f64::INFINITY, 0)
        }
    } else {
        (normalized_multiplier, normalized_power)
    }
}

#[allow(dead_code)]
pub const fn quantity_parts_to_value_const(multiplier: f64, power: i32) -> f64 {
    if multiplier == 0.0 {
        0.0
    } else if multiplier.is_nan() {
        f64::NAN
    } else if multiplier.is_infinite() {
        multiplier
    } else {
        multiplier * pow10_const(power)
    }
}

#[allow(dead_code)]
pub fn quantity_parts_to_value(multiplier: f64, power: i32) -> f64 {
    if multiplier == 0.0 {
        0.0
    } else if multiplier.is_nan() {
        f64::NAN
    } else if multiplier.is_infinite() {
        multiplier
    } else {
        multiplier * pow10(power)
    }
}

#[cfg(any(feature = "storage-f64", feature = "storage-f32"))]
#[allow(dead_code)]
pub fn value_to_quantity_parts(value: f64) -> (f64, i32) {
    (value, 0)
}

#[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
#[allow(dead_code)]
pub fn value_to_quantity_parts(value: f64) -> (f64, i32) {
    if value == 0.0 {
        (0.0, 0)
    } else if value.is_nan() {
        (f64::NAN, 0)
    } else if value.is_infinite() {
        (value, 0)
    } else {
        let power = value.abs().log10().floor() as i32;
        let multiplier = value / 10_f64.powi(power);
        canonicalize_quantity_parts(multiplier, power)
    }
}

#[allow(dead_code)]
pub fn pow10_delta(lhs: i32, rhs: i32) -> f64 {
    pow10(saturating_power_sub(lhs, rhs))
}

#[cfg(feature = "no_std")]
pub fn sqrt(value: f64) -> f64 {
    if value < 0.0 {
        return f64::NAN;
    }
    if value == 0.0 || value.is_nan() || value.is_infinite() {
        return value;
    }

    let mut estimate = if value >= 1.0 { value } else { 1.0 };
    for _ in 0..32 {
        estimate = 0.5 * (estimate + value / estimate);
    }
    estimate
}

#[cfg(not(feature = "no_std"))]
pub fn sqrt(value: f64) -> f64 {
    value.sqrt()
}