def _binary_float_to_ratio(x): """x -> (top, bot), a pair of ints s.t. x = top/bot. The conversion is done exactly, without rounding. bot > 0 guaranteed. Some form of binary fp is assumed. Pass NaNs or infinities at your own risk. >>> _binary_float_to_ratio(10.0) (10, 1) >>> _binary_float_to_ratio(0.0) (0, 1) >>> _binary_float_to_ratio(-.25) (-1, 4) """ if x == 0: return 0, 1 f, e = math.frexp(x) signbit = 1 if f < 0: f = -f signbit = -1 assert 0.5 <= f < 1.0 # x = signbit * f * 2**e exactly # Suck up CHUNK bits at a time; 28 is enough so that we suck # up all bits in 2 iterations for all known binary double- # precision formats, and small enough to fit in an int. CHUNK = 28 top = 0 # invariant: x = signbit * (top + f) * 2**e exactly while f: f = math.ldexp(f, CHUNK) digit = int(f) assert digit >> CHUNK == 0 top = (top << CHUNK) | digit f = f - digit assert 0.0 <= f < 1.0 e = e - CHUNK assert top # reduce to lowest terms while not top&1: top >>= 1 e += 1 # Add in the sign bit. top = signbit * top # now x = top * 2**e exactly; fold in 2**e if e>0: return (top * 2**e, 1) else: return (top, 2 ** -e) import math for c in (math.pi, 7, 7.5, 0.875): n, d = _binary_float_to_ratio(c) print (n, d), (n+0.0)/d, c