smart-calculator 1.0.3

Natural-language calculator for math, units, time zones, dates, fiat, and crypto conversions
Documentation
use fixed_decimal::Decimal as FixedDecimal;
use icu::decimal::DecimalFormatter;
use icu::locale::Locale;
use icu::locale::locale;

pub const MAX_DISPLAY_FRACTION_DIGITS: u8 = 3;

pub fn normalize_locale(locale: Option<String>) -> String {
    locale
        .unwrap_or_else(|| "en-US".to_string())
        .trim()
        .replace('_', "-")
}

pub fn format_display_number(value: f64, locale_str: &str, max_fraction_digits: u8) -> String {
    format_localized_number(value, locale_str, max_fraction_digits)
        .unwrap_or_else(|| compact_decimal_string(value, max_fraction_digits))
}

fn format_localized_number(
    value: f64,
    locale_str: &str,
    max_fraction_digits: u8,
) -> Option<String> {
    let locale = locale_str
        .parse::<Locale>()
        .ok()
        .unwrap_or(locale!("en-US"));
    let formatter = DecimalFormatter::try_new(locale.into(), Default::default()).ok()?;
    let decimal = compact_decimal_string(value, max_fraction_digits)
        .parse::<FixedDecimal>()
        .ok()?;

    Some(format!("{}", formatter.format(&decimal)))
}

fn compact_decimal_string(value: f64, max_fraction_digits: u8) -> String {
    if !value.is_finite() {
        return value.to_string();
    }

    let precision = usize::from(max_fraction_digits);
    let mut formatted = format!("{value:.precision$}");

    if formatted.contains('.') {
        while formatted.ends_with('0') {
            formatted.pop();
        }

        if formatted.ends_with('.') {
            formatted.pop();
        }
    }

    if formatted == "-0" {
        formatted.clear();
        formatted.push('0');
    }

    formatted
}