Learn Lua in Neovim: Numbers and Arithmetic — Operators & Receipt Calculator | Episode 3
Video: Learn Lua in Neovim: Numbers and Arithmetic — Operators & Receipt Calculator | Episode 3 by Taught by Celeste AI - AI Coding Coach
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:
^(right-associative —2 ^ 3 ^ 2is2 ^ 9)- unary
-(negation) *///%+-
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.