In Ruby, how do Rational values like 3r and 0.1r stay exact, and what do quo, fdiv and Float#to_r return?
answer
- numerator over denominator, both Integers
- r suffix: 2/3r, 1.2r
- quo exact, fdiv Float
- Float#to_r exposes the stored double
- Rational plus Float gives Float
basics
~10 sA Rational stores a reduced Integer numerator and denominator, so 1/3r + 1/6r is exactly (1/2). 7.quo(2) returns (7/2), 7.fdiv(2) returns 3.5, and 0.1.to_r returns the stored double's exact fraction, not (1/10).
solid answer
~40 s`Rational` is a core class holding an `Integer` numerator and denominator, always reduced, so arithmetic on it never rounds: `1/3r + 1/6r` is `(1/2)` and `0.1r + 0.2r == 0.3r` is `true`. The `r` suffix builds one from digits, `0.1r` is `(1/10)` and `1.2r` is `(6/5)`, and so do `Rational(2, 3)` and `Rational("0.1")`. `Integer#quo` returns the exact quotient as a Rational (`7.quo(2) # => (7/2)`), while `fdiv` returns a Float (`7.fdiv(2) # => 3.5`). `Float#to_r` converts the stored double exactly, so `0.1.to_r` is a huge fraction; `0.1.rationalize` finds `(1/10)`. Mixing in a Float makes the result a Float, and `Complex` (`1i`) covers imaginary values.
code
ruby · 11 lines1/3r + 1/6r # => (1/2)
0.1r + 0.2r # => (3/10)
7.quo(2) # => (7/2)
7.fdiv(2) # => 3.5
0.1.to_r # => (3602879701896397/36028797018963968)
0.1.rationalize # => (1/10)
Rational("0.1") # => (1/10)
(Rational(1, 3) + 0.5).class # => Float
1i * 1i # => (-1+0i)go deeper
Know that Ruby has an exact Rational type with an r suffix, and that 0.1r + 0.2r equals 0.3r.
Explain why Float#to_r keeps the binary error, how quo and fdiv differ, and which class a mixed expression returns.
Use Rational for exact rate chains and test expectations, and keep Floats from creeping in through a single operand or conversion.
Weigh exact arithmetic against cost: growing denominators and slower maths versus correctness, and decide where values are rounded and persisted.
## What a Rational is **`Rational`** is a core numeric class (no `require`, no gem) representing a fraction `a/b` where both parts are `Integer`s. The fraction is **always reduced** and the denominator is positive, and because `Integer` has unlimited precision, operations on Rationals never round: - `1/3r + 1/6r` returns `(1/2)`. - `0.1r + 0.2r` returns `(3/10)`, so `0.1r + 0.2r == 0.3r` is `true`. - `2/4r` is stored as `(1/2)`. ## Ways to build one | Expression | Result | Exact decimal? | |---|---|---| | `3r` | `(3/1)` | yes | | `2/3r` | `(2/3)` | yes | | `1.2r` | `(6/5)` | yes - the literal is read as decimal digits | | `Rational(2, 3)` | `(2/3)` | yes | | `Rational("0.1")`, `"0.1".to_r` | `(1/10)` | yes - parsed from text | | `0.1.rationalize` | `(1/10)` | the simplest fraction near the double | | `0.1.to_r`, `Rational(0.1)` | `(3602879701896397/36028797018963968)` | no - the stored binary double, exactly | The trap is the last row: once a value has been a `Float`, `to_r` faithfully converts the *binary* approximation. Build Rationals from literals or strings, not from Floats. One more parsing note from the Ruby literal grammar: `1.2/3r` is a Float divided by a Rational, which returns the Float `0.39999999999999997`. ## Dividing: quo and fdiv Two Integer methods make the intent of a division explicit: 1. **`Integer#quo`** returns the most exact result: a `Rational` for Integer or Rational arguments (`7.quo(2) # => (7/2)`, `6.quo(3) # => (2/1)`), and a Float if the argument is a Float. 2. **`Integer#fdiv`** always returns a `Float`: `7.fdiv(2) # => 3.5`, `4.fdiv(Rational(3, 4)) # => 5.333333333333333`. Use `quo` when the quotient feeds further exact arithmetic, and `fdiv` when you want a floating-point ratio such as a percentage for display. ## Mixing numeric types | Left | Right | Result class | |---|---|---| | `Integer` | `Rational` | `Rational` | | `Rational` | `Float` | `Float` | | `Integer` or `Rational` | `Complex` | `Complex` | The rule is that the result moves toward the less exact type: a single `Float` in an expression makes the whole result a `Float`, so `Rational(1, 3) + 0.5` is `0.8333333333333333`. ## Complex numbers **`Complex`** is also core. The `i` suffix makes an imaginary literal: `1i * 1i` returns `(-1+0i)`, and `12.3ri` is a Complex with a Rational imaginary part (the `r` must come before the `i`). `Complex(3, 4).abs` returns `5.0`. Web and business code rarely needs it; signal processing and geometry do. ## When Rational is the right tool - **Exact ratios and chains of rates**, such as converting through several currencies before a final rounding. - **Unit conversions** where each factor is a fraction (`1/3r` of a unit, `5/9r` of a degree). - **Tests** that must assert exact results without tolerances. ## Reading and converting a Rational A `Rational` exposes its parts and converts in several directions: - `Rational(7, 3).numerator` is `7` and `denominator` is `3`. - `Rational(7, 3).truncate` is `2`, while `Rational(-7, 3).floor` is `-3`; `floor`, `ceil`, `truncate` and `round` all accept a digits argument. - `to_f` gives the nearest Float, for display or for APIs that need one. - `(100r / 3).round(2)` is `(3333/100)`, and its `to_f` is `33.33`. A worked example: splitting a bill of 100 three ways. As Floats, `100.0 / 3` is `33.333333333333336`, an approximation that only happens to add back to `100.0` because the rounding errors cancel. As Rationals, `100r / 3` is `(100/3)` and three shares add to exactly `100` by construction; rounding to cents (33.33 each, one cent short) and deciding who pays the extra cent happens once, explicitly, at the end. Costs to remember: - Denominators can grow large in long calculations, and big-integer arithmetic gets slower. - Output needs a decision: `Rational#round(2)` returns another Rational, `to_f` gives a Float for display, and `Rational#round` accepts `half:` just like `Float#round`.
- In Ruby, how do you turn a Rational such as (20904573/15625) into a two-decimal amount?Call `round(2)`, optionally with `half: :even`: `Rational#round` with positive digits returns another Rational, here `(133789/100)`, still exact. Convert only at the edge: `to_f` for display gives `1337.89`, or pass it to `BigDecimal` with an explicit precision for storage. Converting to Float before rounding reintroduces binary error.
- In Ruby, why is 1.2/3r a Float while 2/3r is a Rational?The `r` suffix binds to the number literal it follows, so `3r` is the Rational and the division is an ordinary method call. In `2/3r` an Integer divided by a Rational gives a Rational. In `1.2/3r` the left operand is the Float `1.2`, and a Float mixed with a Rational yields a Float, `0.39999999999999997`. Write `1.2r/3` for an exact result.
saying these in an interview costs you the question
- 0.1r is a Float wrapped in a Rational, so it carries the same binary error.
- Float#to_r turns 0.1 into (1/10).
- Adding a Float to a Rational keeps the result exact.
- Integer#quo and Integer#fdiv both return a Float.
- Rational is a gem you must add to the Gemfile, like bigdecimal.