Functional Weave
Code in Rust

charts.format@1.0.0

impl/python/format_si.py

1,991 bytes · the Python 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.

import math
from typing import Tuple

from .math_round_float import round_float  ← from math.round-float ^1.0.0 · built alongside by fune
from .text_format_decimal import 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).
_PREFIXES = ["y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y"]
_THOUSANDS = [1.0, 1e3, 1e6, 1e9, 1e12, 1e15, 1e18, 1e21, 1e24]
_TENS = [1.0, 10.0, 100.0]


def _scale_and_round(value: float, group: int, places: int) -> Tuple[float, int]:
    scaled = value / _THOUSANDS[group] if group >= 0 else value * _THOUSANDS[-group]
    if places < 0:
        unit = _TENS[-places]
        return round_float(scaled / unit, 0) * unit, 0
    p = 12 if places > 12 else places
    return round_float(scaled, p), p


def format_si(value: float, significant: int) -> str:
    """A number with an SI prefix and ``significant`` significant digits,
    trailing zeros dropped: 1200 is "1.2k", 0.0015 is "1.5m"."""
    if isinstance(significant, bool) or not isinstance(significant, int) or significant < 1 or significant > 15:
        raise ValueError("significant must be a whole number from 1 to 15, received %r" % (significant,))
    if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value):
        raise ValueError("value must be a finite number, received %r" % (value,))
    value = float(value)
    if value == 0:
        return "0"
    a = abs(value)
    exponent = 0
    if a >= 1:
        p = 1.0
        while p * 10 <= a:
            p *= 10
            exponent += 1
    else:
        q = 1.0
        while a * q < 1:
            q *= 10
            exponent -= 1
    group = exponent // 3
    group = max(-8, min(8, group))
    rounded, places = _scale_and_round(value, group, significant - 1 - (exponent - 3 * group))
    if abs(rounded) >= 1000 and group < 8:
        group += 1
        rounded, places = _scale_and_round(value, group, significant - 1)
    return format_decimal(rounded, places, True, "") + _PREFIXES[group + 8]