Skip to content

to_i never fails; Integer() does

Level: 101 · anyone turning user input into a number

One line: "42abc".to_i is 42 and "abc".to_i is 0, with no error; Integer("42abc") raises, reads 0x, 0b and a leading 0 the way a Ruby literal would, and — like to_i — does not read the Arabic-Indic digits that Python's int() accepts.

Verified output of to_i_never_fails_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

  input      to_i   Integer()
  "42"       42     42
  " 42\n"    42     42
  "42abc"    42     ArgumentError
  "abc"      0      ArgumentError
  ""         0      ArgumentError
  "0x1A"     0      26
  "0b101"    0      5
  "010"      10     8
  "1_000"    1000   1000
  "1__000"   1      ArgumentError
  "4 2"      4      ArgumentError
  "١٢"       0      ArgumentError

  Integer("abc", exception: false)   nil
  Integer("010", 10)                 10
  "0x1A".hex                         26
  "1.5 kg".to_f                      1.5
  "1,5".to_f                         1.0
  Float("1.")                        1.0

Reading the output

  • to_i reads leading digits and stops. Whitespace in front is skipped, and anything that is not a digit ends the number. "" and "abc" give 0, which cannot be told apart from "0".
  • Integer() takes the whole string or raises. Surrounding whitespace is allowed; anything else is ArgumentError.
  • Integer() reads prefixes like a literal. "0x1A" is 26 and "0b101" is 5, where to_i sees 0. "010" is 8 — octal — while to_i says 10, so a zero-padded id changes value depending on which one parsed it. Integer("010", 10) insists on decimal.
  • One underscore between digits is allowed, as in a literal; two are not, and to_i stops at them.
  • exception: false makes Integer() return nil instead of raising.
  • to_f is as lenient as to_i, and knows nothing about locales: "1,5".to_f is 1.0. Float("1.") is accepted since Ruby 3.4.

What to do

Parse input with Integer(s, 10) — or Integer(s, 10, exception: false) and a check for nil — so that a typo is an error and a leading zero is not octal. Keep to_i for strings you produced yourself.

If you are coming from another language

Perl numifies a string the way to_i reads it — leading number, no exception — and warns under use warnings when there was anything else in the string:

Verified output of to_i_never_fails_pl.pl — regenerated by tools/run_examples.py, never hand-typed.

  "42"       + 0   42
  " 42\n"    + 0   42
  "42abc"    + 0   42  warning: isn't numeric
  "abc"      + 0   0   warning: isn't numeric
  "0x1A"     + 0   0   warning: isn't numeric
  "1_000"    + 0   1   warning: isn't numeric
  "١٢"       + 0   0   warning: isn't numeric

"0x1A" is 0 to Perl as well (its hex function reads it), and "1_000" is 1: underscores belong to Perl's literals, not to its strings. The Perl library's Numbers from text ↗ goes further, into "nan" and "inf".

Python's int() is strict like Integer(), but reads every Unicode decimal digit and needs base 0 before it accepts a prefix:

Verified output of to_i_never_fails_py.py — regenerated by tools/run_examples.py, never hand-typed.

  '42'       int(s)     42
  ' 42\n'    int(s)     42
  '42abc'    int(s)     ValueError
  '0x1A'     int(s)     ValueError
  '1_000'    int(s)     1000
  '1__000'   int(s)     ValueError
  '١٢'       int(s)     12
  '0x1A'     int(s, 0)  26

The C library's Parsing a number from text ↗ is the same question asked of atoi and strtol, where the lenient answer is also the short one.