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]