math.floor
math.floor(x) Overview
math.floor returns the largest integer less than or equal to its argument. In Lua 5.4, when the result fits in the integer range, the function returns a true integer subtype. Float values come back only when the result is too large for the integer range, which is the change from Lua 5.1 and 5.2 (where math.floor always returned a float) and the reason every downstream integer check has a subtype story behind it.
math is auto-loaded, so no require is needed.
Signature
math.floor(x)
Parameters
x — number (integer or float). The value to round down. A numeric string is accepted through Lua’s standard arithmetic coercion, so math.floor("3.7") returns 3.
Return value
Returns the largest integral value ≤ x. In Lua 5.4 the result is an integer subtype when it fits, otherwise a float. math.ceil and math.modf follow the same rule; the manual groups them together as the “rounding functions.”
Behavior
Rounding direction catches people on negative numbers. math.floor always rounds toward negative infinity, so math.floor(3.7) returns 3 and math.floor(-3.2) returns -4 (not -3).
JavaScript’s Math.floor rounds the same way. C-style integer truncation does not; it rounds toward zero and would give -3 for -3.2. The direct expression for truncation toward zero in Lua is x >= 0 and math.floor(x) or math.ceil(x).
Paste the snippet below into a Lua 5.4 REPL and read the four outputs in order. They cover the inputs that come up most when porting code from C or JavaScript:
print(math.floor(3.7)) --> 3
print(math.floor(3.0)) --> 3
print(math.floor(-3.2)) --> -4
print(math.floor(-3.0)) --> -3
Negative inputs are where C and Lua disagree most visibly. C would give -3; Lua gives -4. math.type reports the result as "integer" in Lua 5.4, which is why math.floor(x) == x works as the integer check across the standard library.
A few additional rules worth knowing:
- Already-integer inputs come back as integers.
math.floor(5)returns5withmath.typereporting"integer", not5.0.math.flooris the normal way to coerce a known-integral float into the integer subtype. - String coercion applies.
math.floor(" 7.99 ")returns7. A string with trailing junk like"3.9x"throws, because the coercion fails. math.hugeis preserved.math.floor(math.huge)returnsmath.huge(a float). The same applies to-math.huge.- NaN propagates.
math.floor(0/0)returns NaN. Comparisons against NaN are false, sox ~= xis the standard check downstream.
math.floor is also a building block for round-to-nearest. The expression math.floor(x + 0.5) rounds positives to the nearest integer, but it is wrong for negatives; for half-away-from-zero rounding, pair it with math.ceil:
local function round(x)
if x >= 0 then
return math.floor(x + 0.5)
else
return math.ceil(x - 0.5)
end
end
For decomposing a number into integer and fractional parts, math.modf returns both pieces in a single call. It avoids the subtraction-roundtrip a math.floor-based approach would need, which matters when x is large enough for floating-point error to leak into the result.
Examples
Each block below is self-contained. Paste into a Lua 5.4 REPL and read the printed output line by line.
Basic rounding across signs
Run this first to fix the rounding direction in your head. The four inputs are the cases that come up most when porting from C or JavaScript, and the math.type call at the end confirms the result is "integer" and not "float". The subtype matters because the rest of the standard library treats them differently.
print(math.floor(3.7)) --> 3
print(math.floor(3.0)) --> 3
print(math.floor(-3.2)) --> -4
print(math.floor(-3.0)) --> -3
-- Confirm the integer subtype in Lua 5.4
print(math.type(math.floor(3.7))) --> integer
String coercion and integer normalization
math.floor accepts numeric strings because Lua’s arithmetic operators go through the same coercion path. A float that you know is integral can be promoted to a true integer subtype with a single math.floor call, which is what you need when downstream code does math.type(x) == "integer".
-- Numeric strings are accepted through Lua's coercion rules
print(math.floor("7.99")) --> 7
-- Idiom: normalize a float that you know is integral to a true integer
local f = 42.0
print(math.type(f)) --> float
print(math.type(math.floor(f))) --> integer
print(math.floor(f) == 42) --> true
Bounds and special values
Infinities and NaN break naive guards. math.huge and -math.huge round to themselves and stay floats; NaN propagates through unchanged, so x ~= x is the standard check downstream of any arithmetic operation.
print(math.floor(math.huge)) --> inf
print(math.floor(-math.huge)) --> -inf
local nan = 0/0
print(nan == nan) --> false
print(math.floor(nan) == nan) --> true
Common mistakes
- Expecting floor to round toward zero.
math.floor(-3.2)is-4, not-3. For truncation toward zero, use a conditional or pairmath.ceilfor negatives. - Using
math.floor(x + 0.5)for round-half-away-from-zero. It works for positives, but-2.5becomes-2instead of-3. Pair it withmath.ceilfor negatives, as shown above. - Wrapping
//results inmath.floor. The//operator already floors the division, so the wrapper is a no-op and may strip an integer subtype you wanted to keep. - Targeting Lua 5.1 or 5.2 by accident. In those versions
math.flooralways returned a float, so any code that usesmath.type(result) == "integer"only works on 5.3+.