Will's Digital Garden

Rational Numbers

High-level programming languages should default to using rational number structures to represent most numbers. While there are some advantages to IEEE 754 floating point numbers, absolute precision is not one.

Rationals could be easily stored in one of two ways:

The two-integer approach is the lower memory option, but requires reducing rational fractions on a regular basis (akin to garbage collecting). When I've implemented rationals in personal programming languages, I've opted to reduce when parsing and after each operation on the number. There maybe a more advanced mathematic approach, but my understanding is that these reduction operations require prime factorization to find the greatest common denominator, then adjusting the underlying integers to fit that.

The key-value approach is certainly higher memory, but minimizes reduction costs. When a rational in this form is parsed or initiated, its numerator and denominator will be factored into primes. For example 18 / 1 would be factored into 2×3², so that's stored as {2: 1, 3: 2}. As a reminder, denominator factors would have negative exponents. This structure automatically forbids unreduced fractions as they're unrepresentable.

Either way, a system that uses rationals as a base numeric value would ideally perform all computation on the rationals, only converting to floating point representations as a final step (when displaying to a user, when being handled to a separate API or system that requires floating point numbers, etc). This delays rounding issues introduced by the conversion until the last possible moment.

Rationals also avoid comparison pitfalls of IEEE 754 floating numbers. In languages that use floating numbers, 0.1 + 0.2 == 0.3 returns false. With rationals, this would naturally evaluate as: 0.1 + 0.2 → 1/10 + 1/5 → 3/10, which is in fact equivalent to 0.3 → 3/10.