When is fractions.Fraction a better choice than decimal.Decimal?
answer
- Two integers, no context anywhere
- One third survives, one tenth is trivial
- No scale, no trailing zeros, no format
- Denominators multiply and then explode
- Ratios inside, decimal amounts at the edges
basics
~20 sUse fractions.Fraction when the quantities are ratios and every operation must stay exact: it holds a numerator and denominator as integers, so one third is exact. decimal.Decimal is base-10 with a digit budget, so one third rounds.
solid answer
~50 s`fractions.Fraction` stores an exact rational as two arbitrary-precision integers, normalised to lowest terms. Addition, subtraction, multiplication and division of Fractions are all exact - there is no context, no precision setting and no rounding mode anywhere - so `Fraction(1, 3) * 3 == 1` is True while `Decimal(1) / 3 * 3 == 1` is False. That makes Fraction the right type for genuine ratios: proportional splits, unit-conversion factors, rates expressed as a ratio, weights that must sum to one. `Decimal` wins where the domain is base-10 with a fixed scale - money, tax, anything with a presentation format and a legal rounding rule. Fraction's costs are that denominators grow as unlike ratios accumulate, that it carries no notion of scale, and that most interfaces do not accept it, so you convert once at the end.
code
python · 8 linesfrom decimal import Decimal
from fractions import Fraction
print(Fraction(1, 3) * 3 == 1) # True - exact rational arithmetic
print(Decimal(1) / 3 * 3 == 1) # False - the division rounded
print(Fraction(Decimal('0.1'))) # 1/10
print(Fraction(0.1).limit_denominator(100)) # 1/10
print(f'{Fraction(1, 3):.4f}') # 0.3333go deeper
Know that Fraction holds a numerator and denominator as exact integers, that it normalises to lowest terms, and that one third is exact in Fraction but not in Decimal or float.
Explain the split cleanly: Fraction for ratios with no rounding anywhere, Decimal for base-10 amounts with a scale and a rounding policy. Mention denominator growth and the conversion needed at the output boundary.
Show the combination in real code - exact ratios through the intermediate steps, one conversion to a scaled Decimal at the end - and be able to say when Fraction's growing integers make it the wrong tool in a hot path.
Decide where exactness is worth its cost and where a documented approximation is fine, and make sure the codebase has one place that converts an exact intermediate into a presented value rather than rounding scattered through the calculation.
### What each type actually is `fractions.Fraction` is an exact rational number: a numerator and a denominator, both Python ints of unlimited size, normalised on construction to lowest terms with a positive denominator. `Fraction(6, 8)` is `Fraction(3, 4)`. There is no precision setting and no rounding mode, because the four basic operations on rationals produce rationals and never need to discard anything. `decimal.Decimal` is base-10 *floating point*: a coefficient of digits and a decimal exponent, with arithmetic governed by a context that caps the number of significant digits. It represents exactly those rationals whose denominator is a product of twos and fives; anything else - a third, a seventh - is rounded to the context precision. So the dividing question is not 'which is more accurate' but **what kind of quantity is this**. A price is a base-10 quantity with a scale of two places and a legal rounding rule: Decimal. A one-third share or a conversion factor of 254/10000 is a ratio: Fraction. ### Where Fraction earns its place The clearest case is proportional arithmetic where intermediate rounding would compound. If you compute a chain of weights, rates or conversion factors and only need a presentable number at the very end, doing the chain in Fraction guarantees that the single rounding happens once, where you choose it, rather than at every step. Other honest uses: exact geometry and exact probability work where a denominator is meaningful, test oracles that must be exact so you can measure how far a faster implementation drifts, and any code where a ratio is data - `Fraction('7/1000')` parses that literally. Fraction also converts cleanly from the other numeric types. `Fraction(Decimal('0.1'))` is `Fraction(1, 10)`. `Fraction(0.1)` is `Fraction(3602879701896397, 36028797018963968)` - the same exact-conversion behaviour Decimal shows, and a useful way to see what a float really holds. `Fraction.limit_denominator` finds the closest rational with a bounded denominator, which turns that monstrous ratio back into `Fraction(1, 10)` and is the standard way to recover a plausible simple ratio from a measured value. ### What Fraction costs First, **growth**. Adding fractions with unlike denominators multiplies denominators before normalising. Ten additions of arbitrary ratios can leave you with integers of hundreds of digits, and arithmetic on those is slow. Fraction is fine for a bounded chain of operations and a poor choice inside a hot loop that accumulates thousands of terms. Second, **no scale**. `Fraction(199, 10)` has no memory of being written with one decimal place, so it cannot represent 'nineteen ninety, two places' the way `Decimal('19.90')` does. Money code wants that scale. Third, **interoperability**. Databases, serializers, report formatters and numeric libraries mostly speak int, float or Decimal. A Fraction has to be converted at the boundary, either to a float or, better for money, by dividing numerator by denominator under an explicit Decimal context so you control the rounding. Since Python 3.12, Fraction supports float-style format specifications, so `f'{Fraction(1, 3):.4f}'` gives `0.3333` directly. ### Both are exact, differently A common muddle is to treat 'exact' as one property. Decimal is exact for *decimal amounts and the operations that fit its digit budget*; Fraction is exact for *all rational arithmetic*, unconditionally. Neither is exact for irrational quantities - a square root or a logarithm has to round in both worlds, and `Decimal.sqrt` rounds to the context precision while Fraction offers no square root at all. ### A practical combination The pattern that uses each type for what it is good at: parse inputs as Decimal so the amounts are exact and scaled, lift any *ratio* into Fraction for the multi-step calculation, then convert back to Decimal once and apply the scale and rounding policy at that single point. You get exactness through the intermediate steps and a properly scaled, properly rounded result at the boundary - and only one place in the code where a rounding decision is made.
- Why not use Fraction for money and get exactness everywhere?Because money is not a ratio. A Fraction has no scale, so it cannot express 'two decimal places', and it has no rounding policy, so at some point you still have to decide how a third of a cent is settled. Denominators also grow through a long ledger. Fraction is the right intermediate for a split or a rate; Decimal is the right type for the amount you store and display.
- What does Fraction.limit_denominator do, and when is it useful?It returns the closest Fraction whose denominator does not exceed a bound, computed from the continued-fraction expansion. It turns the exact but unwieldy rational behind a float back into a plausible simple ratio - `Fraction(0.1).limit_denominator(100)` is `1/10` - which is useful for recovering an intended ratio from a measured or imported value, and for displaying an approximation with a readable denominator.
- Where does the built-in complex type sit relative to these two?Apart. A complex is a pair of binary floats - the real and imaginary parts - so it inherits every float representation issue and has no exact counterpart in the standard library: there is no complex Decimal and no complex Fraction. It belongs to signal and engineering work, where float precision is appropriate, and never to money or exact-ratio arithmetic.
Fraction is the recipe written as 'a third of a cup'; Decimal is the same recipe written as '0.33 cups'. One stays true through a chain of doublings and halvings, the other is what you print on the card.
saying these in an interview costs you the question
- Calls Fraction slower but otherwise identical to Decimal
- Uses Fraction as the stored type for money
- Thinks Decimal represents one third exactly
- Ignores that denominators grow through repeated addition
- Assumes Fraction has a precision or rounding setting