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
Jump to a chapter
Read the video transcript
Select a timestamp to watch that moment on YouTube.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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
from fractions import Fraction
print(Fraction('0.1') + Fraction('0.2') == Fraction('0.3'))
print(Fraction(0.1) == Fraction('0.1'))True
FalseA decimal string can represent the intended tenth; a float already contains a binary approximation.
02. Split a whole in a two-to-three ratio
from fractions import Fraction
left = Fraction(2, 5)
right = Fraction(3, 5)
print(left, right, left + right)2/5 3/5 1The exact shares sum to the whole without rounding.
03. Split an exact decimal-string total
from fractions import Fraction
amount = Fraction('1.5')
print(amount * Fraction(2, 5), amount * Fraction(3, 5))3/5 9/10Parsing 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.
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.
from fractions import Fraction
print(Fraction('0.1') + Fraction('0.2') == Fraction('0.3'))
print(Fraction(0.1) == Fraction('0.1'))True
False- Build Fraction from decimal text and from a float.
- Compare the resulting values.
- Reject ambiguous float totals under this exercise's contract.
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.
from fractions import Fraction
left = Fraction(2, 5)
right = Fraction(3, 5)
print(left, right, left + right)2/5 3/5 1- Add the positive ratio weights.
- Compute one exact share with Fraction.
- Derive the other share from the total and verify the sum.
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.
from fractions import Fraction
amount = Fraction('1.5')
print(amount * Fraction(2, 5), amount * Fraction(3, 5))3/5 9/10- Parse a decimal string into Fraction.
- Multiply by both ratio shares.
- 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 runNeed input()? Add one value per line
Your output appears here.
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.
Typical mistakes
Everyone meets these errors. See what causes them and how to fix them.
Converting an approximated float
Fraction(0.1)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
left = total * left_weight / (left_weight + right_weight)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.