@@ -458,3 +458,55 @@ def clamp(
458458 "Invalid format. Must be either a valid formatting string, or a function "
459459 "that accepts value and returns a string."
460460 )
461+
462+
463+ def metric (value : float , unit : str = "" , precision : int = 3 ) -> str :
464+ """Return a value with a metric SI unit-prefix appended.
465+
466+ Examples:
467+ ```pycon
468+ >>> metric(1500, "V")
469+ '1.50kV'
470+ >>> metric(2e8, "W")
471+ '200MW'
472+ >>> metric(220e-6, "F")
473+ '220μF'
474+ >>> metric(1e-14, precision=4)
475+ '10.00f'
476+
477+ ```
478+
479+ The unit prefix is always chosen so that non-significant zero digits are required.
480+ i.e. `123,000` will become `123k` instead of `0.123M` and `1,230,000` will become
481+ `1.23M` instead of `1230K`. For numbers that are either too huge or too tiny to
482+ represent without resorting to either leading or trailing zeroes, it falls back to
483+ `scientific()`.
484+ ```pycon
485+ >>> metric(1e40)
486+ '1.00 x 10⁴⁰'
487+
488+ ```
489+
490+ Args:
491+ value (int, float): Input number.
492+ unit (str): Optional base unit.
493+ precision (int): The number of digits the output should contain.
494+
495+ Returns:
496+ str:
497+ """
498+ exponent = int (math .floor (math .log10 (abs (value ))))
499+
500+ if exponent >= 27 or exponent < - 24 :
501+ return scientific (value , precision - 1 ) + unit
502+
503+ value /= 10 ** (exponent // 3 * 3 )
504+ if exponent >= 3 :
505+ ordinal = "kMGTPEZY" [exponent // 3 - 1 ]
506+ elif exponent < 0 :
507+ ordinal = "mμnpfazy" [(- exponent - 1 ) // 3 ]
508+ else :
509+ ordinal = ""
510+ value_ = format (value , ".%if" % (precision - (exponent % 3 ) - 1 ))
511+
512+ return f"{ value_ } { ordinal } { unit } "
0 commit comments