0Pricing
Lua Academy · Lesson

Type Coercion and Conversion

Learn automatic coercion between strings and numbers, and explicit tonumber/tostring.

Type Coercion and Conversion is a free Lua Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Lua Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What Is Type Coercion?

Type coercion is the automatic conversion of a value from one type to another when the context requires it. Lua performs coercion in two specific directions: string-to-number in arithmetic contexts, and number-to-string in concatenation contexts.

Unlike JavaScript, Lua coercion is narrow and predictable. It does not coerce booleans to numbers or tables to strings automatically.

-- String to number (arithmetic context)
print("10" + 5)     -- 15  (coerced)
print("3.14" * 2)   -- 6.28

-- Number to string (concatenation context)
print(10 .. "px")   -- 10px
print(3.14 .. "!")  -- 3.14!

-- No boolean coercion
-- print(true + 1)  -- ERROR!

String-to-Number Coercion Rules

When a string appears in an arithmetic expression, Lua tries to convert it to a number. If the string doesn't represent a valid number, a runtime error is raised. Valid numeric strings include integers ("42"), floats ("3.14"), hex ("0xff"), and scientific notation ("1e3").

Strings with leading/trailing whitespace are accepted — Lua trims them during coercion.

print("42" + 0)      -- 42    (integer)
print("3.14" + 0)    -- 3.14  (float)
print("0xff" + 0)    -- 255   (hex)
print("  10  " + 0)  -- 10    (whitespace ok)
print("1e3" + 0)     -- 1000.0

-- This will error:
-- print("hello" + 0)  -- attempt to perform arithmetic on a string value

Number-to-String Coercion

When you concatenate a number with .., Lua automatically converts the number to a string. Integers become their decimal representation; floats may gain decimal places. For precise control over the format, always use tostring() or string.format() rather than relying on implicit coercion.

print(10 .. "!")       -- 10!
print(3.14 .. "!")     -- 3.14!
print(1/3 .. "!")      -- 0.33333333333333!

-- Explicit is better for precision
print(string.format("%.2f", 1/3))  -- 0.33
print(tostring(10))                -- 10
print(tostring(10.0))              -- 10.0

tonumber(): Explicit Conversion

tonumber(v) explicitly converts a value to a number. It returns the converted number on success, or nil if conversion fails — it never raises an error. An optional base (2–36) can be specified for integer parsing, enabling binary, octal, and hex conversions.

Always check the return value of tonumber() for nil when processing user input.

print(tonumber("42"))      -- 42
print(tonumber("3.14"))    -- 3.14
print(tonumber("0xFF"))    -- 255
print(tonumber("hello"))   -- nil (no error!)
print(tonumber(true))      -- nil

-- Base conversion
print(tonumber("ff", 16))  -- 255
print(tonumber("11", 2))   -- 3   (binary)
print(tonumber("77", 8))   -- 63  (octal)

tostring(): Explicit Conversion

tostring(v) converts any value to a string. For numbers it produces a decimal or scientific notation string. For booleans it produces "true" or "false". For nil it returns "nil". For tables and functions it returns an address like "table: 0x..." unless a __tostring metamethod is defined.

print(tostring(42))         -- 42
print(tostring(3.14))       -- 3.14
print(tostring(true))       -- true
print(tostring(false))      -- false
print(tostring(nil))        -- nil
print(tostring({}))         -- table: 0x...

-- Useful for safe concatenation
local val = nil
local msg = "Value: " .. tostring(val)
print(msg)   -- Value: nil

math.tointeger()

math.tointeger(x) (Lua 5.3+) converts a float to an integer only if the value has an exact integer representation. If the conversion would lose precision, it returns nil. This is safer than math.floor() which always succeeds but may silently truncate.

Use this when you need strict integer validation, for example when interfacing with APIs that require exact integers.

print(math.tointeger(5.0))   -- 5   (exact)
print(math.tointeger(5.5))   -- nil (not exact)
print(math.tointeger(2^53))  -- 9007199254740992

-- Contrast with math.floor
print(math.floor(5.9))       -- 5   (truncates)
print(math.floor(5.0))       -- 5

-- Check before use
local n = math.tointeger(val) or error("need integer")

Coercion in Comparisons

Lua does not coerce types for equality comparisons. 1 == "1" is always false because the operands have different types. However, relational operators (<, >, etc.) do trigger coercion between strings and numbers — but only if both operands are of the same type. Mixing types in ordering comparisons raises an error.

print(1 == "1")    -- false (no coercion)
print(1 == 1.0)    -- true  (int/float equal)

-- Ordering: same type only
print("abc" < "abd")  -- true (string compare)
print(1 < 2)          -- true (number compare)

-- This ERRORS: mixing string and number
-- print(1 < "2")  -- attempt to compare number with string

Practical: Parsing User Input

A very common pattern in Lua programs is reading a string from user input (e.g., via io.read()) and converting it to a number. Always use tonumber() and check for nil to handle invalid input gracefully instead of crashing.

-- Safe input parsing pattern
local function parseNumber(input)
  local n = tonumber(input)
  if n == nil then
    return nil, "Invalid number: " .. tostring(input)
  end
  return n, nil
end

local val, err = parseNumber("42")
print(val, err)   -- 42  nil

local val2, err2 = parseNumber("abc")
print(val2, err2) -- nil  Invalid number: abc

Integer/Float Precision Pitfalls

Floats have limited precision. Operations that mix integer and float may produce unexpected results due to IEEE 754 representation. The maximum exact integer representable as a double is 2^53 = 9,007,199,254,740,992. Integers beyond this lose precision when stored as float.

In Lua 5.3+, native 64-bit integers support exact values up to 2^63-1, much larger. Always use integer arithmetic when exactness matters.

-- Float precision limit
print(2^53)            -- 9.007...e+15
print(2^53 + 1 == 2^53) -- true (precision lost!)

-- Integer stays exact
local big = math.maxinteger
print(big)             -- 9223372036854775807
print(math.type(big))  -- integer

-- Mixing promotes to float
print(math.type(1 + 1.0))  -- float

String Format Conversions

string.format() is the most powerful tool for number-to-string conversion when you need control over precision, padding, and notation. It follows C's printf format specifiers. Use it for display, file output, and anywhere the default tostring() representation isn't adequate.

print(string.format("%d", 255))      -- 255
print(string.format("%x", 255))      -- ff
print(string.format("%05d", 42))     -- 00042
print(string.format("%.2f", 3.14159)) -- 3.14
print(string.format("%e", 12345.0))  -- 1.234500e+04
print(string.format("%s=%d", "x", 10)) -- x=10

Quick Check

What does tonumber("hello") return in Lua?

Recap: Type Coercion and Conversion

What you've learned:

  • Lua auto-coerces strings to numbers in arithmetic and numbers to strings in concatenation
  • tonumber() returns nil on failure — always check!
  • tostring() safely converts any value to a string
  • Equality (==) never coerces — different types are never equal
  • Use string.format() for precise number formatting
  • Float precision limit is 2^53; use native integers for large exact values

Next: control flow with if/elseif/else and loops.

Frequently asked questions

Is the “Type Coercion and Conversion” lesson free?

Yes — the full text of “Type Coercion and Conversion” is free to read here on the web, and the Lua Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Lua Academy course, upgrade to CoddyKit PRO.

What will I learn in “Type Coercion and Conversion”?

Learn automatic coercion between strings and numbers, and explicit tonumber/tostring. You practise Lua Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Lua Academy?

No prior experience is required. Lua Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Type Coercion and Conversion” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Lua Academy lesson?

Yes. Every Lua Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Lua Data Types Overview
  2. Declaring Variables in Lua
  3. Arithmetic and Relational Operators
  4. Type Coercion and Conversion
← Back to Lua Academy