I am looking for an elegant way to pretty-print physical quantities with the most appropriate prefix (as in 12300 grams are 12.3 kilograms). A simple approach looks like this:
def pprint_units(v, unit_str, num_fmt="{:.3f}"):
    """ Pretty printer for physical quantities """
    # prefixes and power:
    u_pres = [(-9, u'n'), (-6, u'µ'), (-3, u'm'), (0, ''),
              (+3, u'k'), (+6, u'M'), (+9, u'G')]
    if v == 0:
        return num_fmt.format(v) + " " + unit_str
    p = np.log10(1.0*abs(v))
    p_diffs = np.array([(p - u_p[0]) for u_p in u_pres])
    idx = np.argmin(p_diffs * (1+np.sign(p_diffs))) - 1
    u_p = u_pres[idx if idx >= 0 else 0]
    return num_fmt.format(v / 10.**u_p[0]) + " " + u_p[1]  + unit_str
for v in [12e-6, 3.4, .123, 3452]:
    print(pprint_units(v, 'g', "{: 7.2f}"))
# Prints:
#  12.00 µg
#   3.40 g
# 123.00 mg
#   3.45 kg
Looking over units and Pint, I could not find that functionality. Are there any other libraries which typeset SI units more comprehensively (to handle special cases like angles, temperatures, etc)?
I have solved the same problem once. And IMHO with more elegance. No degrees or temperatures though.
def sign(x, value=1):
    """Mathematical signum function.
    :param x: Object of investigation
    :param value: The size of the signum (defaults to 1)
    :returns: Plus or minus value
    """
    return -value if x < 0 else value
def prefix(x, dimension=1):
    """Give the number an appropriate SI prefix.
    :param x: Too big or too small number.
    :returns: String containing a number between 1 and 1000 and SI prefix.
    """
    if x == 0:
        return "0  "
    l = math.floor(math.log10(abs(x)))
    if abs(l) > 24:
        l = sign(l, value=24)
    div, mod = divmod(l, 3*dimension)
    return "%.3g %s" % (x * 10**(-l + mod), " kMGTPEZYyzafpnµm"[div])
CommaCalc
Degrees like that:
def intfloatsplit(x):
    i = int(x)
    f = x - i
    return i, f
def prettydegrees(d):
    degrees, rest = intfloatsplit(d)
    minutes, rest = intfloatsplit(60*rest)
    seconds = round(60*rest)
    return "{degrees}° {minutes}' {seconds}''".format(**locals())
edit:
Added dimension of the unit
>>> print(prefix(0.000009, 2))
9 m
>>> print(prefix(0.9, 2))
9e+05 m
The second output is not very pretty, I know. You may want to edit the formating string.
edit:
Parse inputs like 0.000009 m². Works on dimensions less than 10.
import unicodedata
def unitprefix(val):
    """Give the unit an appropriate SI prefix.
    :param val: Number and a unit, e.g. "0.000009 m²"
    """
    xstr, unit = val.split(None, 2)
    x = float(xstr)
    try:
        dimension = unicodedata.digit(unit[-1])
    except ValueError:
        dimension = 1
    return prefix(x, dimension) + unit
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With