In Ruby, what do Float#round, floor and ceil return when you pass a digits argument or the half: keyword?
answer
- return class follows the digits sign
- positive digits: Float; else Integer
- default half: :up, away from zero
- half: :even is banker's rounding
- floor and ceil take no half:
basics
~20 sWith no digits, zero or negative digits they return an Integer; with positive digits a Float. round breaks ties away from zero unless half: :even or half: :down is given; floor and ceil always move toward negative or positive infinity.
solid answer
~30 s`Float#round`, `floor` and `ceil` take an optional `ndigits`. Positive digits keep that many decimals and return a `Float` (`1234.567.round(1) # => 1234.6`); zero or negative digits return an `Integer` with trailing zeros (`1234.567.round(-2) # => 1200`). `round` also takes `half:` for exact ties: the default `:up` rounds away from zero (`2.5.round # => 3`, `(-2.5).round # => -3`), `:even` is banker's rounding (`2.5.round(half: :even) # => 2`), `:down` rounds toward zero, and anything else raises `ArgumentError`. `floor` and `ceil` have no `half:`; they move toward negative and positive infinity, so `-1234.567.floor(1)` is `-1234.6`. `Integer#round` and `Rational#round` accept the same `half:` keyword.
code
ruby · 13 lines2.5.round # => 3
(-2.5).round # => -3
2.5.round(half: :even) # => 2
2.5.round(half: :down) # => 2
1234.567.round(1) # => 1234.6
1234.567.round(-2) # => 1200
-1234.567.floor(1) # => -1234.6
1234.567.ceil(-2) # => 1300
25.round(-1) # => 30
25.round(-1, half: :even) # => 20
(0.3 / 0.1).floor # => 2go deeper
Know that round, floor and ceil take a digits argument and that plain round gives an Integer, with 2.5 rounding to 3.
Explain how the sign of ndigits sets the return class, what each half: mode does at exact ties, and why floor differs from truncate below zero.
Spot rounding done on Float intermediates in billing or reporting code and move it to BigDecimal or Rational with an explicit half: mode.
Treat rounding policy as a business rule: pick one mode per domain, document it, and apply it once at a defined boundary.
## One digits argument, three directions `Float#round`, `Float#floor` and `Float#ceil` all accept an optional **`ndigits`** argument that says *where* to cut: | Call | Result | Class | |---|---|---| | `1234.567.round` | `1235` | `Integer` | | `1234.567.round(1)` | `1234.6` | `Float` | | `1234.567.round(-2)` | `1200` | `Integer` | | `1234.567.floor(1)` | `1234.5` | `Float` | | `-1234.567.floor(1)` | `-1234.6` | `Float` | | `1234.567.ceil(-2)` | `1300` | `Integer` | | `12.34.floor` | `12` | `Integer` | ## Return types follow the sign of ndigits - **No argument or `0`**: an `Integer`. `2.7.round` is `3`, not `3.0`. - **Positive `ndigits`**: a `Float` with at most that many decimals, `1.0.round(2)` is still `1.0` - the method does not pad. - **Negative `ndigits`**: an `Integer` with at least `ndigits.abs` trailing zeros, for rounding to tens, hundreds or thousands. `round` never returns a formatted string. For display with fixed decimals use `format("%.2f", x)`; for stored values that must keep two decimals, use `BigDecimal` or integer minor units. ## Tie-breaking with half: `half:` matters only when the value is **exactly** halfway between the two candidates: | `half:` | `2.5.round(half: ...)` | `3.5.round(...)` | `(-2.5).round(...)` | |---|---|---|---| | `:up` (default, or `nil`) | `3` | `4` | `-3` | | `:down` | `2` | `3` | `-2` | | `:even` | `2` | `4` | `-2` | - `:up` means **away from zero**, not toward positive infinity. - `:even` is **banker's rounding**: ties go to the even neighbour, so repeated rounding does not drift upward on average. - An unknown mode such as `half: :odd` raises `ArgumentError` (`invalid rounding mode: odd`). - The same keyword works on `Integer#round` (`25.round(-1, half: :even) # => 20`, while `25.round(-1)` is `30`) and on `Rational#round`, and `BigDecimal#round` accepts it too. ## floor and ceil `floor` and `ceil` take `ndigits` but **no `half:`**; they have nothing to break. `floor` moves toward **negative infinity** and `ceil` toward **positive infinity**, which differs from truncation for negative numbers: `-1234.567.floor(1)` is `-1234.6`, while `truncate(1)` would give `-1234.5`. ## Binary surprises The digits argument is decimal, but the value is still a binary double: 1. `0.3 / 0.1` is `2.9999999999999996`, so `(0.3 / 0.1).floor` returns `2`, not `3`. 2. `2.675` is stored slightly below 2.675, yet `2.675.round(2)` returns `2.68`: for positive digits in the default `half: :up` mode, CRuby's `round` checks whether the decimal halfway point, computed in double arithmetic, is not above the value, so it rounds as the decimal text suggests. 3. `floor` and `ceil` have no such correction, which is why the first case bites. ## The same API on other numeric types The digits-and-`half:` design is shared across Ruby's numeric classes, which makes it easy to move rounding off Float without learning a new vocabulary: | Receiver | Positive digits return | Tie example | |---|---|---| | `Float` | `Float` | `2.5.round(half: :even) # => 2` | | `Integer` | the Integer itself (nothing to cut) | `25.round(-1, half: :even) # => 20` | | `Rational` | `Rational` | `Rational(5, 2).round(half: :even) # => 2` | | `BigDecimal` (gem) | `BigDecimal` | `BigDecimal("0.125").round(2, half: :even) # => 0.12` | `Rational` and `BigDecimal` apply the mode to an exact value, so a tie is really a tie: `(100r / 3).round(2)` is `(3333/100)`, and nothing depends on how a double happened to be stored. ## Common mistakes 1. Expecting `round(2)` to produce a string with two decimals for a receipt - it returns a Float, and `1.5.round(2)` prints as `1.5`. 2. Assuming banker's rounding is the default because another language or a database uses it. 3. Using `floor` where `truncate` was meant, and getting a different answer for negative amounts. 4. Rounding a Float intermediate in the middle of a calculation, then rounding again at the end - double rounding can move a value across a tie. ## Choosing a method in real code - Rounding a **displayed** measurement: `round(n)` on a Float is fine. - Rounding **money**: do it on `BigDecimal` or `Rational` with an explicit `half:`, never on a Float intermediate. - Bucketing (for example to the nearest hundred): `round(-2)`, `floor(-2)` or `ceil(-2)` return Integers, which suits keys and labels.
- In Ruby, what does 2.675.round(2) return, given that 2.675 is stored slightly below 2.675?It returns `2.68`. For positive digits, CRuby's `Float#round` in the default `half: :up` mode checks whether the decimal halfway point, computed in double arithmetic, is not above the stored value, so it rounds as the decimal literal suggests; `1.005.round(2)` gives `1.01` the same way. `floor` and `ceil` have no such correction, and money should not depend on either.
- In Ruby, how do floor and truncate differ for negative Floats?`floor` moves toward negative infinity and `truncate` toward zero. For positive numbers they agree, but `-1234.567.floor(1)` is `-1234.6` while `-1234.567.truncate(1)` is `-1234.5`. Both take the same `ndigits` argument and neither accepts `half:`.
saying these in an interview costs you the question
- Ruby's Float#round uses banker's rounding by default, so 2.5.round is 2.
- round with a negative digits count returns a Float such as 1200.0.
- floor and ceil accept the half: keyword just like round.
- round(2) returns a string padded to exactly two decimals.
- floor always moves toward zero, so -1234.567.floor(1) is -1234.5.