Part of Learn Lua with NeoVim

Learn Lua in Neovim: Numbers and Arithmetic — Operators & Receipt Calculator | Episode 3

Sandy LaneSandy Lane

Video: Learn Lua in Neovim: Numbers and Arithmetic — Operators & Receipt Calculator | Episode 3 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Lua Numbers and Arithmetic: Operators and a Receipt Calculator

+ - * / for the basics, // for floor division, % for modulo, ^ for exponent. Standard precedence rules. Build a small receipt calculator with subtotal, tax, and total.

Lua's arithmetic is conventional — same operators you'd write on paper, plus a few extras. Today we cover all of them and use them to build a tiny receipt calculator.

The receipt calculator

-- Receipt calculator
local item1 = 12.99
local item2 = 8.50
local item3 = 24.99

local subtotal = item1 + item2 + item3
print("Subtotal: " .. subtotal)

local tax_rate = 0.08
local tax = subtotal * tax_rate
print("Tax: " .. tax)

local total = subtotal + tax
print("Total: " .. total)

Three items, sum them with +, multiply by tax rate, add tax to subtotal. Standard everyday arithmetic.

Output:

Subtotal: 46.48
Tax: 3.7184
Total: 50.1984

For real money you'd want to round to 2 decimal places — string.format("%.2f", total) produces "50.20". We'll cover formatting in episode 4.

All the operators

print("7 // 2 = " .. 7 // 2)    -- floor division: 3
print("7 % 2 = " .. 7 % 2)      -- modulo (remainder): 1
print("2 ^ 10 = " .. 2 ^ 10)    -- exponent: 1024.0

Six arithmetic operators in Lua:

Operator Name Example Result
+ addition 5 + 3 8
- subtraction 5 - 3 2
* multiplication 5 * 3 15
/ division (always float) 5 / 2 2.5
// floor division 5 // 2 2
% modulo (remainder) 7 % 2 1
^ exponent 2 ^ 10 1024.0

/ always produces a float, even when the result is whole — 4 / 2 is 2.0, not 2. For integer-only division, use //.

^ is exponent (power-of), not XOR. Lua doesn't have a built-in XOR — use bit32.bxor() or the ~ operator in newer versions.

Precedence and parentheses

print("10 - 3 * 2 = " .. 10 - 3 * 2)        -- 4
print("(10 - 3) * 2 = " .. (10 - 3) * 2)    -- 14

Standard math precedence:

  1. ^ (right-associative — 2 ^ 3 ^ 2 is 2 ^ 9)
  2. unary - (negation)
  3. * / // %
  4. + -

Parentheses force order. When in doubt, add them — readability beats cleverness.

.. has lower precedence than arithmetic

print("Total: " .. 10 + 5)  -- works: parses as ("Total: " .. 15)

Concatenation runs after arithmetic, so the math finishes first and the result gets concatenated. But:

print("Total: " .. 10 .. " items")  -- works: "Total: 10 items"

Multiple .. concatenate left-to-right (or right-to-left — Lua doesn't actually care because concatenation is associative).

When mixing .. and +:

print("sum is " .. 5 + 3)   -- "sum is 8"
print("sum is " .. (5 + 3)) -- same, more obvious

Parentheses for clarity.

Negative numbers and unary minus

local debt = -50
local positive = -debt   -- 50

- works as both subtraction and negation. -x is "negate x"; 5 - x is "subtract x from 5." Lua figures out which from context.

Integer overflow

print(math.maxinteger)      -- 9223372036854775807
print(math.maxinteger + 1)  -- -9223372036854775808 (wraps)

Lua's integers are 64-bit signed, so they overflow at ~9.2 quintillion. For most arithmetic you'll never hit this, but for cryptographic or scientific work, use the bigint library or convert to float.

Number-to-string conversion

local x = 42
print(x .. "")           -- "42" (concatenation triggers conversion)
print(tostring(x))       -- "42" (explicit)
print(string.format("%d", x))  -- "42" (formatted)

Lua's auto-conversion is convenient but loses control over formatting. For currency, scientific notation, or padding, always use string.format.

Common stumbles

Using ^ for XOR. It's exponent. 2 ^ 8 is 256, not 10.

Forgetting / produces floats. 5 / 2 is 2.5, not 2. Use // for integer division.

Missing parentheses around negation in concatenation. "value: " .. -5 works; -5 is parsed as a unary expression.

Modulo with negative operands. Lua's % always matches the sign of the divisor: -7 % 3 is 2, not -1 (as in C). For "true remainder" use math.fmod.

Integer vs float surprise. 1 == 1.0 is true (they compare equal across subtypes), but tostring(1) == tostring(1.0) is false because string forms differ ("1" vs "1.0"). Beware when comparing string representations.

What's next

Episode 4: strings. Concatenation, length with #, string.upper/string.lower, multi-line strings with [[ ]], and a name-badge generator.

Recap

Six arithmetic operators: + - * / plus // (floor div), % (modulo), ^ (exponent). / always returns a float; // for integer division. Standard precedence; use parentheses for clarity. Numbers auto-convert to strings on concatenation. Use string.format("%.2f", x) for real money formatting.

Next episode: strings.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.