In Ruby, how do Integer("12abc") and "12abc".to_i differ, and when should parsing a quantity raise instead of returning 0?
answer
- lenient reader versus strict parser
- to_i stops at the first bad character
- ArgumentError for bad text, TypeError for nil
- exception: false returns nil
- a leading 0 means octal
basics
~10 s"12abc".to_i returns 12 and returns 0 when nothing parses. Integer("12abc") raises ArgumentError, and Integer(nil) raises TypeError. Use Integer() or Float() when bad input must be reported; exception: false returns nil instead.
solid answer
~40 s`String#to_i` and `to_f` are lenient: they read leading numeric characters, ignore the rest, and return `0` or `0.0` when nothing parses, so `"12abc"`, `"12 kg"` and `""` all yield numbers. `Kernel#Integer` and `Kernel#Float` are strict: the whole string must be a number, with surrounding whitespace and underscores allowed, otherwise they raise `ArgumentError` (`invalid value for Integer(): "12abc"`), and `nil` raises `TypeError`. Passing `exception: false` returns `nil` instead, which you can test without `rescue`. For a grocery-order CSV, a blank or garbled quantity must not quietly become `0`, so parse with `Integer(cell, 10, exception: false)` and report the nils. The explicit base matters: by default `Integer` reads prefixes, so `Integer("08")` raises because a leading `0` means octal. Since Ruby 3.4, `Float("2.")` returns `2.0` instead of raising.
code
ruby · 9 lines"12abc".to_i # => 12
"".to_i # => 0
Integer(" 12 ") # => 12
Integer("12abc") # ArgumentError: invalid value for Integer(): "12abc"
Integer(nil) # TypeError: can't convert nil into Integer
Integer("12abc", exception: false) # => nil
Integer("08") # ArgumentError: leading 0 selects octal
Integer("08", 10) # => 8
Float("2.") # => 2.0 on Ruby 3.4 and latergo deeper
Recall that to_i never raises and returns 0 for junk, while Integer() raises ArgumentError for bad strings and TypeError for nil.
Explain what Integer() accepts, whitespace, underscores and radix prefixes, the octal trap with leading zeros, and exception: false returning nil.
Choose per boundary between raising, collecting nils for a report, and lenient parsing, and justify it by what a silent 0 would cost downstream.
Set a team rule that external numeric input is parsed strictly at the edge, with explicit bases and a defined policy for blanks.
## Two families of numeric conversion Ruby offers two ways to turn text into a number, and they disagree about bad input: | | Lenient | Strict | |---|---|---| | Methods | `String#to_i`, `String#to_f`, `nil.to_i` | `Kernel#Integer`, `Kernel#Float` | | `"12abc"` | `12` | raises `ArgumentError` | | `""` | `0` | raises `ArgumentError` | | `"abc"` | `0` | raises `ArgumentError` | | `nil` | `nil.to_i` is `0` | raises `TypeError` | | With `exception: false` | not applicable | returns `nil` | The lenient methods read as many leading characters as form a number and ignore everything after. When no leading number exists they return zero, so a missing value and a real zero look identical. The strict functions require the **whole** string to be a valid number and raise otherwise. ## What Integer() accepts `Integer(object, base = 0, exception: true)` tries `to_int` first and `to_i` second for non-strings, and parses strings strictly: - **Whitespace and underscores**: `Integer(" 100 ")` is `100` and `Integer("1_000")` is `1000`. - **Radix prefixes** with the default base `0`: `0x`, `0b` and `0o` select hex, binary and octal, and a bare leading `0` also means octal, so `Integer("0100")` is `64`. - **An explicit base** switches prefixes off for decimal input: `Integer("08", 10)` is `8`, while `Integer("08")` raises because `8` is not an octal digit. - **Floats are truncated** toward zero: `Integer(1.9)` is `1`. The string `"1.5"` is not an integer literal, so `Integer("1.5")` raises. - **nil** raises `TypeError` with `can't convert nil into Integer`. ## exception: false With `exception: false`, any failure returns `nil` instead of raising. That turns validation into an ordinary value check: 1. Parse: `qty = Integer(cell, 10, exception: false)`. 2. Test: `if qty.nil?`, record the row as invalid. 3. Continue with a real `Integer` everywhere else. This is cheaper and clearer than `rescue ArgumentError, TypeError` around each cell, and it cannot accidentally swallow an unrelated error. ## Float() and the 3.4 change `Kernel#Float` follows the same strict rules: `Float("2.5")` is `2.5`, `Float("2.5kg")` raises `ArgumentError`, and `Float(nil)` raises `TypeError`. Ruby 3.4 relaxed one case: a decimal string with its fractional part omitted is now valid, so `Float("2.")` returns `2.0` where earlier versions raised, and `"2.".to_f` is `2.0` as well. On Ruby 4.0, weights typed as `"2."` in a spreadsheet therefore parse. ## Worked example: blank cells in a grocery order A grocery-order CSV has an item column and a quantity column. Shoppers leave some quantities blank, type `"12 "` with a trailing space, or write `"two"`. A CSV parser returns each cell as a `String`, or `nil` for an empty unquoted cell. - With `to_i`, the blank becomes `0` and `"two"` becomes `0`: the order ships with missing items and no one is told. - With `Integer(cell, 10, exception: false)`, `"12 "` parses to `12`, while `nil` and `"two"` both return `nil`, and the row can be reported back to the shopper. - With plain `Integer(cell)`, the first bad cell raises and aborts the import, which suits a batch job that must be all-or-nothing. ## Why the lenient methods exist `to_i` and `to_f` are not mistakes to be avoided everywhere. They suit input you control or have already validated: - Reading a counter back from a file your own program wrote. - Pulling the number out of text with a known suffix, such as `"12 kg".to_i`, after the format has been checked. - Converting in a base other than 10 with `to_i(16)` when stray trailing characters really should be ignored. The danger is only in using them where a person or another system supplied the text and "no number" must be reported rather than read as zero. ## Choosing - Use **`to_i` / `to_f`** when the input is known to be numeric or when "no number" genuinely means zero, such as a counter read from a file you wrote. - Use **`Integer()` / `Float()`** at boundaries where people or other systems supply the text. - Pass **base `10`** when input may carry leading zeros, such as zero-padded quantities or codes. - Pick **raising or `exception: false`** depending on whether one bad value should stop the whole operation.
- In Ruby, why does Integer("08") raise while "08".to_i returns 8?`Integer` defaults to base `0`, which reads radix prefixes, and a bare leading `0` means octal, where `8` is not a valid digit, so it raises `ArgumentError`. `String#to_i` defaults to base `10` and ignores that prefix rule. Pass the base explicitly, `Integer("08", 10)`, for zero-padded decimal input.
- What does Integer() do with the Float 1.9 and with the string "1.5"?`Integer(1.9)` truncates toward zero and returns `1`, and `Integer(-1.9)` returns `-1`. `Integer("1.5")` raises `ArgumentError`, because a string must be a complete integer literal. Parse fractional quantities with `Float()` or `Rational()` instead.
saying these in an interview costs you the question
- Integer("12abc") returns 12, the same as to_i
- "abc".to_i raises ArgumentError
- Integer(nil) returns 0
- exception: false makes Integer() return 0 for bad input
- Integer("08") returns 8 because leading zeros are ignored