luaguides

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) returns 5 with math.type reporting "integer", not 5.0. math.floor is the normal way to coerce a known-integral float into the integer subtype.
  • String coercion applies. math.floor(" 7.99 ") returns 7. A string with trailing junk like "3.9x" throws, because the coercion fails.
  • math.huge is preserved. math.floor(math.huge) returns math.huge (a float). The same applies to -math.huge.
  • NaN propagates. math.floor(0/0) returns NaN. Comparisons against NaN are false, so x ~= x is 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 pair math.ceil for negatives.
  • Using math.floor(x + 0.5) for round-half-away-from-zero. It works for positives, but -2.5 becomes -2 instead of -3. Pair it with math.ceil for negatives, as shown above.
  • Wrapping // results in math.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.floor always returned a float, so any code that uses math.type(result) == "integer" only works on 5.3+.

See also