tonumber
tonumber() converts a value to a number. If the value cannot be converted, it returns nil.
Signature
tonumber(e, base)
Parameters:
e— the value to convert (string or number)base— optional number base (2 to 36, default 10)
Returns: number, or nil if conversion fails.
The function handles three distinct input types: plain numbers pass through unchanged, numeric strings get parsed according to the specified base, and anything else produces nil. This three-way behaviour means you can use tonumber as both a type normaliser and a parser in the same call — pass a number and get a number back, or pass a string and get a parsed number if the string is valid.
Converting Numbers
Pass a number and it comes back unchanged:
tonumber(42) -- 42
tonumber(3.14) -- 3.14
tonumber(-17) -- -17
Passing a number through tonumber returns it unchanged, which is mostly useful when you need a guaranteed number type — for instance, after reading from a config file where values might arrive as strings. The function performs no rounding or type coercion; a float stays a float and an integer stays an integer.
The far more common scenario is converting strings to numbers. Lua stores most external data as strings — user input, file contents, network messages — and tonumber is the standard bridge from the string world to the numeric world.
Converting Strings
The most common use is parsing strings:
tonumber("42") -- 42
tonumber("3.14") -- 3.14
tonumber(" 100 ") -- 100 (whitespace is ignored)
Lua is strict about what counts as a valid number string. Leading and trailing whitespace is tolerated, but any non-numeric characters — even a single letter mixed into an otherwise numeric string — cause the entire parse to fail. Mixed-content strings like "42abc" are rejected outright because the parser expects a complete, coherent number from start to finish.
This strictness is by design. Returning nil for invalid input lets you write clean guard clauses rather than catching exceptions, as the error-handling section will demonstrate.
tonumber("hello") -- nil
tonumber("42abc") -- nil
tonumber("") -- nil
Using base (radix) parsing
The second argument specifies the number base, which opens up tonumber for parsing binary data, hexadecimal colour codes, octal permission masks, and any custom-base representation between 2 and 36. When you pass a base, the input string is interpreted digit-by-digit in that base — "1010" in base 2 means ten, not one thousand and ten. The base parameter is what makes tonumber far more than a simple string-to-float converter.
Binary (base 2)
tonumber("1010", 2) -- 10
tonumber("1111", 2) -- 15
Hexadecimal (base 16)
Hexadecimal parsing is one of the most common use cases for the base parameter. Colour codes, memory addresses, and binary file formats all use hex notation, and tonumber handles both the standard FF form and the 0x-prefixed form you would write in Lua source code. The 0x prefix is stripped automatically when base 16 is specified.
tonumber("FF", 16) -- 255
tonumber("0xFF", 16) -- 255 (0x prefix also recognized)
tonumber("0xabcd", 16) -- 43981
Hex digits are case-insensitive — tonumber("AbCd", 16) returns 43981 just like tonumber("abcd", 16). Letters A through F (or a through f) represent the decimal values 10 through 15, and the parser treats uppercase and lowercase identically.
Octal parsing needs a bit more care. Many programmers expect a leading zero to signal octal, as it does in C, but Lua does not follow that convention.
Octal (base 8)
tonumber("77", 8) -- 63
tonumber("040", 8) -- 32
Watch out: Lua does not treat a leading zero as octal by default. tonumber("040") returns 40, not 32 — the leading zero is simply ignored in decimal mode. You must pass base 8 explicitly to get octal interpretation. This is a common stumbling block for programmers coming from C or JavaScript.
The base parameter accepts any integer from 2 to 36, which covers every practical numbering system from binary through base-36 alphanumeric encoding.
Other Bases
Base can be any integer from 2 to 36:
tonumber("Z", 36) -- 35
tonumber("100", 3) -- 9
Digits beyond 9 use letters a through z — case-insensitive — for the values 10 through 35. This means base 36 uses all ten digits plus all 26 letters, making it the largest base tonumber supports. Beyond parsing, the practical value of the base parameter becomes clear when you combine it with error handling: invalid input never crashes your program.
Error Handling
tonumber never throws an error — it returns nil for invalid input:
result = tonumber("not a number")
if result == nil then
print("invalid input")
end
The nil-return pattern works well for graceful degradation — you check the result and branch accordingly. But sometimes you want a hard failure when the input is malformed, especially in scripts and data pipelines where bad input should halt processing rather than silently produce incorrect results.
You can combine tonumber with assert to get that fail-fast behaviour. If tonumber returns nil, assert raises an error with your custom message.
value = assert(tonumber("42"), "must be a number") -- 42
value = assert(tonumber("oops"), "must be a number") -- error: must be a number
Practical Examples
The patterns below show tonumber in realistic scenarios — reading from standard input, parsing structured data formats, and validating configuration values. Each example demonstrates a different aspect of the function: the nil-check idiom, the base parameter for hex parsing, and the number-as-guarantee pattern for config tables.
Parsing user input
local input = io.read("*line")
local n = tonumber(input)
if n then
print("Twice that is:", n * 2)
else
print("'" .. input .. "' is not a number")
end
Reading hex colours
The nil-check pattern handles interactive input, but tonumber also shines when parsing structured data. Hexadecimal colour codes are a perfect example — a six-character string like "3A7D44" encodes three colour channels as two hex digits each. You can extract each channel with string.sub and convert it with tonumber and base 16, then use the resulting integers directly in graphics or terminal colour escape sequences.
local hex = "3A7D44"
local r = tonumber(string.sub(hex, 1, 2), 16)
local g = tonumber(string.sub(hex, 3, 4), 16)
local b = tonumber(string.sub(hex, 5, 6), 16)
print(r, g, b) -- 58 125 68
Configuration files
Hex parsing handles structured formats, but the most routine use of tonumber is converting configuration strings into usable numbers. Config files and environment variables always arrive as strings — "800" is text, not an integer — so every numeric setting needs a tonumber call before it can participate in arithmetic. The pattern below wraps the conversion in a table constructor, keeping the parsing logic compact and close to the data it transforms.
config = {
width = tonumber("800"),
height = tonumber("600"),
scale = tonumber("1.5"),
}
if config.width and config.height then
print(config.width * config.height)
end
See Also
- /reference/core-functions/ref-print/ — output values
- /reference/core-functions/ref-tostring/ — convert to string
- /reference/core-functions/ref-type/ — get the type of a value