How Lodash isInteger Handles Floating-Point Precision
Lodash’s _.isInteger method verifies whether a given
value is an integer by determining if the value is a finite number equal
to its truncated integer form. Because JavaScript represents all numbers
using the IEEE 754 standard for double-precision floating points,
_.isInteger does not evaluate mathematical integers in the
abstract, but rather the binary floating-point representation of the
value. Consequently, its evaluation at precision boundaries is strictly
dictated by the 53-bit significand limit of JavaScript numbers, leading
to specific behaviors around safe integer thresholds, fractional
rounding underflow, and large-magnitude numbers.
The Underlying Evaluation Logic
At its core, Lodash defines _.isInteger by checking
whether the input type is a number and whether it matches its integer
conversion:
function isInteger(value) {
return typeof value === 'number' && value == toInteger(value);
}In modern environments, this behaves virtually identically to
ECMAScript’s native Number.isInteger(value). The
calculation checks that the value is not NaN, not infinite,
and that Math.floor(Math.abs(value)) === Math.abs(value)
(or value % 1 === 0).
Because this check happens purely in the floating-point domain, the precision limits of standard 64-bit floats determine the outcome before the code can even distinguish fractional components.
The 53-Bit
Significand and MAX_SAFE_INTEGER
Standard IEEE 754 doubles allocate 53 bits (including the implicit
leading bit) to the significand (fraction). This guarantees exact
integer precision up to \(2^{53} - 1\)
(9,007,199,254,740,991), accessible in JavaScript as
Number.MAX_SAFE_INTEGER.
When evaluating numeric precision boundaries:
- Within Safe Bounds: For values between
-Number.MAX_SAFE_INTEGERandNumber.MAX_SAFE_INTEGER, every mathematical integer has a unique, exact binary representation. Here,_.isIntegerbehaves with exact mathematical accuracy. - Beyond Safe Bounds: Once numbers exceed \(2^{53}\), the exponent scale forces the least significant bits to represent increments greater than \(1\). Between \(2^{53}\) and \(2^{54}\), only even integers can be represented. Beyond \(2^{54}\), the representable numbers skip by multiples of 4, 8, 16, and so on.
Because any floating-point number with an exponent of 52 or greater
has no fractional bits allocated in the IEEE 754 format,
_.isInteger evaluates all representable numbers in this
range as true:
_.isInteger(9007199254740992); // true (2^53)
_.isInteger(9007199254740993); // true, because it rounds to 9007199254740992 in memory
_.isInteger(1e25); // true_.isInteger does not reject numbers that exceed safe
precision limits. It simply confirms that the current floating-point
representation does not contain a fractional remainder.
Fractional Precision and Machine Epsilon
The boundary behavior also affects fractional values near zero and
near whole numbers. In IEEE 754, the gap between 1 and the next larger
floating-point number is Number.EPSILON (\(2^{-52} \approx 2.22 \times
10^{-16}\)).
If a fractional offset is smaller than the precision threshold at a
specific magnitude, the JavaScript runtime rounds the value to the
nearest representable float before _.isInteger receives
it:
_.isInteger(1 + 1e-16); // true (rounds down to exactly 1.0)
_.isInteger(1 + 1e-15); // false (fraction is preserved in the significand)At this boundary, _.isInteger does not fail due to its
own logic; rather, the runtime has already discarded the fractional
precision.
_.isInteger vs.
_.isSafeInteger
Because _.isInteger accepts any floating-point
representation with a remainder of zero, Lodash provides
_.isSafeInteger to explicitly guard against IEEE 754
precision degradation.
_.isSafeInteger adds a boundary constraint:
function isSafeInteger(value) {
return isInteger(value) && value >= -9007199254740991 && value <= 9007199254740991;
}When writing systems that require absolute integer precision—such as
handling database IDs, cryptographic nonces, or 64-bit integer
values—_.isInteger alone is insufficient because it treats
floats with lost precision as valid integers. For strict IEEE 754
precision boundary checks, _.isSafeInteger must be used
instead.