Matthew Levine Matthew Levine - 3 months ago 14
Python Question

Create compact/human-friendly floats using unicode vulgar fractions

Are there any modules (preferably in the standard library), that can turn a float number into something the is more human friendly? Maybe it's not more human friendly, but at least more compact.

eg. 4.625 would become "4⅝"

(Bonus brownie points for recognizing pi to a reasonable precision)

This code outline was the best I could come up with:

import unicodedata

def simplify_float(number):
vf = "VULGAR FRACTION "
vulgars = {0.125 : unicodedata.lookup(vf + "ONE EIGHTH"),
0.2 : unicodedata.lookup(vf + "ONE FIFTH"),
0.25 : unicodedata.lookup(vf + "ONE QUARTER"),
0.375 : unicodedata.lookup(vf + "THREE EIGHTHS"),
0.4 : unicodedata.lookup(vf + "TWO FIFTHS"),
0.5 : unicodedata.lookup(vf + "ONE HALF"),
0.6 : unicodedata.lookup(vf + "THREE FIFTHS"),
0.625 : unicodedata.lookup(vf + "FIVE EIGHTHS"),
0.75 : unicodedata.lookup(vf + "THREE QUARTERS"),
0.8 : unicodedata.lookup(vf + "FOUR FIFTHS"),
0.875 : unicodedata.lookup(vf + "SEVEN EIGHTHS")}

decimal = int(number)
if number == decimal:
return unicode(decimal)

vulgar = vulgars.get(number - decimal)
if vulgar:
if decimal == 0:
return vulgar
return "%d%s" % (decimal, vulgar)
return "%.1f" % number


Going the other direction is pretty easy using the unicodedata module, but I couldn't find a good way to author these strings in the first place.

Answer

There are only twenty of these fraction forms in Unicode. It is unlikely that there will ever be more (as they only exist for backwards compatibility with other character sets), so hardcoding them is probably robust enough.

The general way of encoding fractions is to use U+2044 FRACTION SLASH. Font shaping/layout engines are allowed to render numbers with a fraction slash (e.g., 1⁄2) with as a slanted or stacked fraction (e.g., ½)—however, I have not encountered any that do (and even the plain rendering is unfortunately quite ugly).

import math
import fractions

VULGAR_FRACTIONS = {(5, 8) : '\u215D', ...}

def compact_float(number):
    parts = math.modf(number)
    fraction = fractions.Fraction(parts[0])
    simple = (fraction.numerator, fraction.denominator)
    form = VULGAR_FRACTIONS.get(simple)
    return '%i%s' % (parts[1], form) if form else str(number)

It's not clear what the best way is to handle precision (e.g., 1/3) as it will depend on the format your numbers exist in and how much error is acceptable.

Comments