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:

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.