luaguides

math.abs

math.abs(x)

math.abs returns the absolute value of a number. The Lua 5.4 manual defines it as “the maximum value between x and -x”, which for any non-NaN input gives you the non-negative magnitude.

It preserves the integer or float subtype of the input, so math.abs(1.0) returns 1.0 (a float), not 1 (an integer). It is also one of the few math functions in Lua that handles math.mininteger correctly without overflow, because the C implementation uses unsigned arithmetic to dodge the sign problem.

Signature

math.abs(x)

Parameters

math.abs takes a single required argument.

ParameterTypeDefaultDescription
xnumber (integer or float)requiredThe value whose absolute value is computed. Numeric strings are auto-coerced to numbers; any other type raises a bad argument error.

Return value

Returns a number with the same subtype as x. An integer input produces an integer result, and a float input produces a float result. The value is always non-negative.

Two edge cases are worth memorising:

  • math.abs(math.mininteger) returns math.mininteger itself. The smallest representable integer has no positive counterpart in two’s-complement, so the function cannot flip its sign. A naive n < 0 and -n or n would overflow here. math.abs does not, because the C source uses unsigned arithmetic for the negation.
  • math.abs(0/0) returns NaN. NaN has no defined order, so the “maximum of x and -x” definition is technically undefined for it. C’s fabs returns NaN, and that is what you get in practice on every mainstream Lua build.

Negative zero collapses to positive zero: math.abs(-0.0) returns 0.0. If you ever need to know whether the input was a negative zero, you have to inspect it before calling abs.

The result is always the same type as the input. That is a deliberate design choice in Lua’s math library and matches the spec’s (integer/float) annotation. It rarely matters in arithmetic, but it does affect code that branches on math.type, such as serializers, table pretty-printers, or anything that distinguishes int from float at the protocol layer.

Examples

Integers and floats

print(math.abs(-17))                       -- 17
print(math.abs(17))                        -- 17
print(math.abs(-3.14))                     -- 3.14
print(math.abs(0))                         -- 0
print(math.type(math.abs(-3.14)))          -- "float"
print(math.type(math.abs(-17)))            -- "integer"

The last two lines show the subtype-preservation rule. The float stays a float and the integer stays an integer, even when their values are numerically equal. math.type is the cleanest way to assert that, since Lua does not distinguish the two subtypes with type(x) alone.

Manhattan distance

math.abs is the natural building block for axis-aligned distance:

local function manhattan(x1, y1, x2, y2)
    return math.abs(x1 - x2) + math.abs(y1 - y2)
end

print(manhattan(0, 0, 3, 4))   -- 7

That is the L1 / Manhattan distance. For Euclidean distance you would reach for math.sqrt of the sum of squares. Both forms appear often in grid-based games, A-star pathfinding heuristics, and pixel-art collision detection where the cost of a sqrt is wasted on integer coordinates.

Clamping to a non-negative range

The naive (math.abs(x) + x) / 2 “fold negatives to zero” idiom is broken at the boundaries, so do not use it. Use math.max or an explicit conditional:

local function clamp_non_negative(value)
    if value < 0 then return 0 end
    return value
end

-- Equivalent and shorter:
local function clamp_non_negative(value)
    return math.max(0, value)
end

The (math.abs(x) + x) / 2 form returns value / 2 for small negatives, not 0, and behaves worse near math.mininteger. Reach for math.max instead. The explicit conditional is also faster on a hot path because it avoids both the multiplication and the second math.max evaluation.

String coercion and error handling

Numeric strings are silently coerced:

print(math.abs("-3.5"))   -- 3.5
print(math.abs("42"))     -- 42

Anything else errors out. If you are reading untrusted input from environment variables, config files, or CLI arguments, validate the value with tonumber first or wrap the call in pcall to catch the bad argument error gracefully:

local ok, result = pcall(math.abs, "abc")
print(ok, result)         -- false   bad argument #1 to 'abs' (number expected, got string)

The pcall form is useful when you cannot validate up front. Pair it with an explicit type(x) == "number" check when the input is dynamic but you want a clean domain-specific error message rather than the generic “bad argument” failure you get from the C function.

Integer range safety

math.abs is the safe way to compute absolute value at the integer boundary, where a naive negation would silently overflow. The example below shows the boundary behaviour in practice:

print(math.abs(math.mininteger))       -- -9223372036854775808  (math.mininteger itself)
print(math.abs(math.mininteger + 1))   --  9223372036854775807  (math.maxinteger)

The C source explicitly uses unsigned subtraction (0u - (lua_Unsigned)n) to compute the negation, which is the only safe way to handle math.mininteger in two’s-complement arithmetic. If you ever needed to write math.abs in pure Lua, you would have to do the same dance — the high bit is the sign bit, and the negation of the most negative integer cannot be represented in the same width.

Comparing magnitudes with a tolerance

math.abs is the idiomatic way to test “are these two numbers close enough?” without paying for a square root:

local function nearly_equal(a, b, tolerance)
    return math.abs(a - b) <= tolerance
end

print(nearly_equal(1.0000001, 1.0000002, 1e-6))   -- true
print(nearly_equal(1.0, 1.1, 1e-6))              -- false

This pattern is everywhere: physics integration, floating-point regressions, animation curves, and unit tests for numeric code. Pick a tolerance that matches the units of a and b1e-6 is reasonable for normalised floats, but ridiculous for distances measured in meters and far too loose for double-precision values around 1e15.

Behavior notes

math.abs preserves the integer or float subtype of its input. This is intentional and matches the rest of the math library. It rarely matters for arithmetic, but it can matter when an external API checks math.type(x) == "integer". Some serializers and JSON encoders use that check to decide whether a number needs a decimal point, and a silent subtype change can produce invalid output for protocols that distinguish int from float at the wire level.

Numeric strings are coerced silently. math.abs("10") works and returns the integer 10. This is convenient but easy to miss when reading from external sources such as environment variables or CLI arguments. Use tonumber when you want to be explicit about the coercion, since it returns nil for invalid input instead of raising an error.

NaN passes through. Because NaN is not less than, greater than, or equal to itself, the manual’s “maximum of x and -x” definition does not constrain the NaN case. C’s fabs produces NaN, and that is what you get. Comparing the result of math.abs to anything returns false, including comparison to itself — keep that in mind when writing debug assertions such as assert(math.abs(x) == math.abs(x)), which will fire on every NaN input.

Negative zero collapses. math.abs(-0.0) returns 0.0. If you ever need to know whether the input was a negative zero, inspect it before calling abs. In Lua 5.3 and later you can test 1/x == -math.huge to detect a negative zero without losing the sign bit, since division by -0.0 yields -Inf under IEEE 754 while division by +0.0 yields +Inf.

No sign-extraction helper ships with the standard library. Lua has no math.sign and no math.copysign. The usual idioms are (x > 0) - (x < 0) for a -1/0/+1 result, or x / math.abs(x) for a +1/-1 multiplier (which divides by zero on x = 0 and is undefined on NaN). Reach for one of these only when you genuinely need sign information; for “is this nonzero?” prefer x ~= 0.

The Lua 5.1 manual said “Returns the absolute value of x”. The 5.4 manual switched to “Returns the maximum value between x and -x” and added the explicit (integer/float) annotation. The behavior is identical across versions, so the change is a clarification rather than a fix. Quoting the 5.1 wording is fine in older codebases, but the 5.4 wording is what you will see in current documentation.

See also

  • tonumber(): explicit numeric coercion, useful before passing untrusted input to math.abs.
  • type(): guard with type(x) == "number" before calling math.abs on dynamic input.
  • pcall(): wrap math.abs when the input might be a non-numeric string.