LESSON 93 · DEEPER PYTHON30 min

Calculate Exact Ratios with Fraction

Use Fraction for exact rational ratios and reject ambiguous float input.

Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons

Video lesson: Calculate Exact Ratios with Fraction

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    Some quantities are naturally ratios: one third of a recipe, two votes out of five, or a scale factor of three over seven. A floating-point number approximates many such ratios. That is often fine for measurements, but it can surprise us when a program expects an exact rational answer. Python's `fractions.Fraction` stores a numerator and denominator as integers and reduces them automatically. In this lesson we will first see a decimal approximation, then compare two ways to construct a fraction from the number 0.1. That distinction matters because a float already contains an approximation before `Fraction` sees it.

  2. 00:42

    We will build exact arithmetic from integer pairs and decimal strings, then verify simplification and invalid denominators. This is not a blanket instruction to replace every float. Numerical work has different requirements: some applications need fast approximate real-number calculations, while some need exact ratios or decimal rounding rules. We will choose the representation that fits the data. Try the examples in the lesson console and inspect the printed numerator and denominator, not just the final decimal display. The first value is a finite approximation to one third.

  3. 01:22

    In this particular multiplication, the rounded float result is one point zero, so this test happens to look exact. That does not mean the stored `third` was an exact rational one third. The last comparison gives us a familiar counterexample: 0.1 plus 0.2 is not stored as exactly the same float as 0.3. We must not judge the representation by one successful arithmetic identity. A recipe program might only display a rounded quantity, where a float is fine. A symbolic ratio or a test that must preserve exact numerator and denominator calls for a different representation.

  4. 02:03

    A common attempted fix is to take a float that already exists and wrap it in `Fraction`, assuming this recovers the decimal value the human intended. It cannot know that intent. The next run will show the exact rational value of the float itself, then the value created from a decimal string. `Fraction(0.1)` describes the exact rational value of the stored binary float. That value has a large denominator and is slightly different from the mathematical decimal one tenth. The string form says what we actually mean: decimal text `0.1`, which becomes one tenth. The integer pair makes the same intent even more direct.

  5. 02:46

    Both latter constructions compare equal. This is a subtle but useful rule: choose an exact input before constructing the fraction. Converting an already approximate float into a fraction does not reverse earlier rounding. The method `limit_denominator` can find a nearby simple rational from a float, which can be useful for recovery or display, but it is a heuristic and should not replace knowing the intended ratio. If your source data is a decimal string from a CSV file, passing the string directly can preserve the decimal meaning. If it is genuinely a sensor float, the large denominator reflects the actual stored approximation.

  6. 03:28

    Those are different situations, and the constructor cannot decide which one applies. Three exact thirds sum to one exactly. `Fraction(6, 8)` is reduced to three fourths, and multiplying two fifths by fifteen produces six. The numerator and denominator attributes let us inspect the stored ratio instead of relying on a decimal rendering. These calculations use integer arithmetic internally, so they do not accumulate binary rounding error from each step. There are still practical limits. Numerators and denominators can grow large during complex calculations, which can cost time and memory.

  7. 04:09

    At the edge of an application, you may need to convert a fraction to a decimal string or a float for display or for another system. That conversion is where you choose a rounding rule. Do it deliberately and as late as the application allows. A price computation may be better served by `Decimal` and currency-specific rounding than by `Fraction`; the fact that a ratio is exact does not automatically answer financial rules. In this lesson, the domain is ratios, and integer-pair construction communicates that domain clearly. These checks cover identities that should be exact when the inputs are exact.

  8. 04:49

    One third times three is one; six eighths equals three fourths after simplification; and decimal strings representing one tenth and two tenths sum to three tenths. We also inspect the numerator and reject a zero denominator, because no rational number has that form. Notice that the decimal-string test has a different result from the familiar float comparison in the broken run. That is the whole point: the input representation changed. Try substituting `Fraction(0.1)` for `Fraction('0.1')` and predict whether the equality with three tenths still holds. Then print the denominator and explain why.

  9. 05:30

    The practice task on the page asks you to express quantities as ratios, carry out a calculation, and test a boundary case. When you finish, you should be able to answer not just how to import `Fraction`, but when an exact ratio is a better fit than an approximate float and how to avoid importing an approximation by accident.

Understand the concept

A ratio such as 2:3 describes exact parts of a whole. Python's fractions.Fraction stores numerator and denominator as integers reduced to lowest terms. Fraction(2, 5) represents two fifths exactly; repeated rational arithmetic need not introduce binary floating-point approximation. This is useful for recipe scaling, proportions, and symbolic calculations.

The constructor's input matters. Fraction('0.1') interprets a decimal string as exactly one tenth. Fraction(0.1) instead captures the exact rational value of the already-approximated binary float, which has a large denominator. Converting a float after the fact cannot recover the original decimal intent. The task accepts integer, Fraction, or decimal-string total values and deliberately rejects float totals.

For a left:right split, add positive integer weights, multiply the total by left/(left+right), and calculate the right part as total minus left. Returning two Fraction values keeps the sum exact. The result may be a nonterminating decimal, so a display layer must choose a rounding policy separately. Do not confuse an exact ratio calculation with money formatting; the Decimal lesson handles currency-style decimal rules.

The browser task splits 1 by weights 2 and 3, yielding 2/5 and 3/5. Tests also use a decimal-string total and confirm both shares add back exactly. Reject zero or negative weights, booleans used as weights, and float totals. Keeping input contracts explicit is what makes the exactness claim meaningful.

  • Fraction stores exact rational numbers
  • String decimal and binary float constructors differ
  • Weights must be positive
  • Exact calculation and formatted display are separate

See it step by step

Read the code, predict the output, then compare it with the result.

01. Construct the decimal you meant

PYTHON
from fractions import Fraction
print(Fraction('0.1') + Fraction('0.2') == Fraction('0.3'))
print(Fraction(0.1) == Fraction('0.1'))
EXPECTED OUTPUT
True
False

A decimal string can represent the intended tenth; a float already contains a binary approximation.

02. Split a whole in a two-to-three ratio

PYTHON
from fractions import Fraction
left = Fraction(2, 5)
right = Fraction(3, 5)
print(left, right, left + right)
EXPECTED OUTPUT
2/5 3/5 1

The exact shares sum to the whole without rounding.

03. Split an exact decimal-string total

PYTHON
from fractions import Fraction
amount = Fraction('1.5')
print(amount * Fraction(2, 5), amount * Fraction(3, 5))
EXPECTED OUTPUT
3/5 9/10

Parsing the decimal text produces three halves, which splits exactly into rational shares.

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Construct the decimal value you intended

The first run compares Fraction values built from decimal strings and from an already-approximated float. String tenths add exactly to three tenths, while Fraction(0.1) captures the binary float's precise rational value and differs from one tenth. Converting an approximate float afterward cannot recover the original decimal intent.

Choose input forms deliberately. This exercise accepts integers, Fraction values, or decimal strings for an exact total and rejects float totals. That makes the exactness claim meaningful. If a source provides measurements with uncertainty, an exact rational representation may not be the right model; document what the input numbers mean before choosing arithmetic.

PYTHON
from fractions import Fraction
print(Fraction('0.1') + Fraction('0.2') == Fraction('0.3'))
print(Fraction(0.1) == Fraction('0.1'))
EXPECTED OUTPUT
True
False
Follow the reasoning
  1. Build Fraction from decimal text and from a float.
  2. Compare the resulting values.
  3. Reject ambiguous float totals under this exercise's contract.
02 / 03

Split a whole without rounding drift

The second example creates two fifths and three fifths directly and prints a sum of one. A ratio of 2:3 divides a whole into five total parts. Multiplying by the exact fractions retains the relationship through subsequent arithmetic without inserting a binary floating-point approximation.

Validate weights before dividing: both must be positive, non-Boolean integers. Python's bool is a subclass of int, so an isinstance check alone would admit True as weight one. The checked function calculates the left share using a Fraction, then subtracts it from the total for the right share, guaranteeing that the two returned parts add back to the original exact total.

PYTHON
from fractions import Fraction
left = Fraction(2, 5)
right = Fraction(3, 5)
print(left, right, left + right)
EXPECTED OUTPUT
2/5 3/5 1
Follow the reasoning
  1. Add the positive ratio weights.
  2. Compute one exact share with Fraction.
  3. Derive the other share from the total and verify the sum.
03 / 03

Apply a ratio to an exact decimal-string total

The final run parses 1.5 as a Fraction from text, giving three halves. Multiplying by the two ratio shares produces three fifths and nine tenths, both exact. Their sum is three halves even though the displayed forms differ from ordinary decimal formatting.

Keep exact calculation separate from presentation. A user interface may later need decimal places or a domain-specific rounding rule, especially for money. That choice belongs at the display or settlement boundary. Test a decimal-string total, invalid weights, booleans, and floats so callers cannot accidentally promise exact ratios while feeding in approximate binary values.

PYTHON
from fractions import Fraction
amount = Fraction('1.5')
print(amount * Fraction(2, 5), amount * Fraction(3, 5))
EXPECTED OUTPUT
3/5 9/10
Follow the reasoning
  1. Parse a decimal string into Fraction.
  2. Multiply by both ratio shares.
  3. Check the exact sum before any display rounding.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnMake split_ratio(total, left_weight, right_weight) return two Fraction values that add to the exact total. Accept integer, Fraction, or decimal-string totals; reject float totals with TypeError and require positive integer weights with ValueError. The prepared output must be 2/5 3/5.
Need a hint?

Import Fraction. Reject isinstance(total, float), validate type(weight) is int and weight > 0, convert amount = Fraction(total), then left = amount * left_weight / (left_weight + right_weight), right = amount - left.

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.Which expression represents the decimal one tenth exactly?

    02.What should two shares of a whole add up to?

    03.Does Fraction choose how to display money?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Converting an approximated float

    COMMON MISTAKE
    Fraction(0.1)
    FIX
    Fraction('0.1')

    What happens: The denominator reflects the binary float, not exactly one tenth.
    Pass decimal text when the decimal value is the source of truth.

    Dividing by invalid weights

    COMMON MISTAKE
    left = total * left_weight / (left_weight + right_weight)
    FIX
    if type(left_weight) is not int or left_weight <= 0: raise ValueError('positive weight required')

    What happens: Zero total weight divides by zero; negative weights make confusing shares.
    Validate both ratio weights before calculation.

    Browse this lesson’s problem fixes

    Sources and further reading