Functional Weave
Code in Rust

charts.format@1.0.0

impl/rust/format_si.rs

2,323 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_float::round_float;  ← from math.round-float ^1.0.0 · built alongside by fune
use super::text_format_decimal::format_decimal;  ← from text.format-decimal ^1.0.0 · built alongside by fune

// yocto to yotta, as d3-format writes them (micro is U+00B5).
const PREFIXES: [&str; 17] = ["y", "z", "a", "f", "p", "n", "\u{b5}", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y"];
const THOUSANDS: [f64; 9] = [1.0, 1e3, 1e6, 1e9, 1e12, 1e15, 1e18, 1e21, 1e24];
const TENS: [f64; 3] = [1.0, 10.0, 100.0];

fn scale_and_round(value: f64, group: i64, places: i64) -> (f64, i64) {
    let scaled = if group >= 0 { value / THOUSANDS[group as usize] } else { value * THOUSANDS[(-group) as usize] };
    if places < 0 {
        let unit = TENS[(-places) as usize];
        return (round_float(scaled / unit, 0) * unit, 0);
    }
    let p = if places > 12 { 12 } else { places };
    (round_float(scaled, p), p)
}

/// A number with an SI prefix and `significant` significant digits, trailing
/// zeros dropped: 1200 is "1.2k", 0.0015 is "1.5m".
///
/// # Panics
/// Panics if `significant` is outside 1..=15 or the value is not finite.
pub fn format_si(value: f64, significant: i64) -> String {
    if !(1..=15).contains(&significant) {
        panic!("significant must be a whole number from 1 to 15, received {}", significant);
    }
    if !value.is_finite() {
        panic!("value must be a finite number, received {}", value);
    }
    if value == 0.0 {
        return "0".to_string();
    }
    let a = value.abs();
    let mut exponent: i64 = 0;
    if a >= 1.0 {
        let mut p = 1.0;
        while p * 10.0 <= a {
            p *= 10.0;
            exponent += 1;
        }
    } else {
        let mut q = 1.0;
        while a * q < 1.0 {
            q *= 10.0;
            exponent -= 1;
        }
    }
    let group = exponent.div_euclid(3).clamp(-8, 8);
    let (mut rounded, mut places) = scale_and_round(value, group, significant - 1 - (exponent - 3 * group));
    let mut group = group;
    if rounded.abs() >= 1000.0 && group < 8 {
        group += 1;
        let next = scale_and_round(value, group, significant - 1);
        rounded = next.0;
        places = next.1;
    }
    format!("{}{}", format_decimal(rounded, places, true, ""), PREFIXES[(group + 8) as usize])
}

pub fn fune_vector(args: &[Value]) -> Value {
    Value::str(&format_si(args[0].as_f64(), args[1].as_i64()))
}