Math.imul()
Math.imul(a, b) Math.imul() multiplies two values the way C does on a 32-bit int: each operand is coerced to a 32-bit unsigned integer, the full 64-bit product is taken, the low 32 bits are kept, and that result is reinterpreted as a signed 32-bit integer. The method was added in ES2015 to support the asm.js integer-math effort. The related Math.clz32 counts leading zeros; Math.fround narrows a number to a float32; Math.hypot returns the square root of a sum of squares.
Syntax and parameters
Math.imul(a, b)
| Parameter | Type | Coercion | Description |
|---|---|---|---|
a | number (or value coercible to a number) | ToUint32 | First multiplicand. Fractional values are truncated, negatives wrap modulo 2^32. |
b | number (or value coercible to a number) | ToUint32 | Second multiplicand. Same coercion rules as a. |
The method always returns a JavaScript number, but the value is constrained to the int32 range [-2^31, 2^31 - 1]. Math.imul is a static method, so call it directly on Math. It is not a constructor.
How it works
The spec algorithm is short and worth knowing. The ECMAScript definition performs four steps:
- Coerce
ato a 32-bit unsigned integer withToUint32(a). - Coerce
bto a 32-bit unsigned integer withToUint32(b). - Compute the product modulo 2^32.
- If the result is at least 2^31, subtract 2^32 to fold it into the signed range. Otherwise return it as-is.
That last step is what produces values like -1 from 0xffffffff. The literal 0xffffffff looks like “thirty-two ones,” but inside Math.imul it is the uint32 representation of -1, and multiplying by 5 gives -5:
Math.imul(0xffffffff, 5); // -5
Math.imul(0xfffffffe, 5); // -10
Overflow wraps the same way it does in C, and the sign bit carries through whatever the product is. 0x7fffffff is 2^31 - 1, the largest positive int32; doubling it gives -2 because step 4 of the algorithm folds any unsigned result at or above 2^31 back into the negative half of the range. Multiplying 0x80000000 (the largest unsigned 32-bit value) by 2 produces a low 32 bits of zero:
Math.imul(0x7fffffff, 2); // -2
Math.imul(0x80000000, 2); // 0
Examples
The basic case looks the same as ordinary multiplication when both inputs fit comfortably inside the int32 range, with no overflow and no surprises. For values that stay well below 2^31, the only real difference from * is performance — Math.imul is usually a touch slower, not faster:
Math.imul(2, 4); // 8
Math.imul(3, 4); // 12
Math.imul(-2, -2); // 4
Non-number inputs go through ToUint32 first. Strings get parsed as numbers — "3" becomes 3. Booleans coerce to 0 (false) or 1 (true). null coerces to +0. undefined becomes NaN, which ToUint32 then maps to 0. Each value is narrowed to a 32-bit unsigned integer before multiplication happens:
Math.imul("3", "4"); // 12
Math.imul(true, 7); // 7
Math.imul(null, 9); // 0
Math.imul(1.9, 2); // 2
Math.imul(undefined, 9); // 0
BigInt is rejected because ToUint32 does not accept BigInt inputs — the conversion throws before multiplication runs, so no int32 product is ever computed. The TypeError is raised by the implicit numeric coercion, not by Math.imul itself; that is why the message names BigInt, not imul:
Math.imul(1n, 2n);
// TypeError: Cannot mix BigInt and other types
The most common real-world use is a non-cryptographic hash step such as FNV-1a, which mixes each byte into a 32-bit accumulator by xor-ing and then multiplying by a fixed prime:
function fnv1aStep(hash, byte) {
return Math.imul(hash ^ byte, 0x01000193);
}
// FNV-1a offset basis XOR'd with the first byte, then multiplied by the FNV prime
fnv1aStep(0x811c9dc5, 0x68);
Common mistakes
Truncation happens on each operand, not on the result. Math.imul(1.9, 2) returns 2, not 3.8. If you want decimal multiplication with no overflow concerns, use *.
The 0xffffffff trap. Code that uses 0xffffffff as a bitmask elsewhere may quietly produce negatives once it flows through Math.imul. Multiply by 5 and you get -5, not 21474836465.
It is not a faster multiply. MDN is explicit that feeding it ordinary floating-point numbers can make it slower than *. The win shows up when the inputs are already integer-typed inside a hot loop, in asm.js, or in code emitted by Emscripten. Treat it as “correct int32 semantics,” not “fast multiplication.”
The return type is still number. You do not get an int, a BigInt, or a 64-bit result. The value is a JS number whose magnitude is at most 2^31 - 1.
It is not modular multiplication. Math.imul(a, b) is the low 32 bits of the product, not a * b mod m. For modular arithmetic over a prime, write the modulo explicitly.
When to use it
Reach for Math.imul when you need the low 32 bits of a product to match C, Emscripten, or a hash/CRC implementation. Hash functions rely on it for the multiply-by-prime step. FNV-1a is the classic case; xxhash and MurmurHash do similar work with their own magic primes. Fixed-point arithmetic with a power-of-two scale also benefits, because the 32-bit wrap keeps the rounding predictable.
Native Math.imul is the only way to get the low 32 bits correct for inputs whose product exceeds what a JS double can represent. The common polyfill uses * and %, so the IEEE-754 multiplication rounds before the modulo, then takes the wrong value:
// (2^31 - 1)^2 mod 2^32 = 1 — the correct int32 product
Math.imul(0x7fffffff, 0x7fffffff); // 1
// Same operands, polyfill path — the JS double rounds the product
// to 4611686014132420600, and that rounded value is a multiple of 2^32
((0x7fffffff * 0x7fffffff) % 0x100000000) | 0; // 0
For modular arithmetic under a large prime, treat it as a building block, not a complete answer — apply the modulo yourself and fold the result into the positive range:
// Modular multiplication under 1_000_000_007
const M = 1000000007;
const modMul = (a, b) => (Math.imul(a, b) % M + M) % M;
modMul(12345, 67890); // 838102050
Skip it for general-purpose decimal math, where plain * is shorter, faster, and more honest about precision.
Browser support
Math.imul is supported in every evergreen browser: Chrome 28+, Firefox 20+, Safari 7+, Edge 12+, Opera 16+, and the corresponding mobile browsers. The only modern holdout is Internet Explorer 11, which never shipped it. No polyfill is needed for any current target.
See also
- Math.clz32() — count leading zeros of the 32-bit representation, the natural pair to
imul. - Math.fround() — the float-narrowing sibling added in the same ES2015 batch.
- Math.trunc() — drop the fractional part without rounding, for the broader integer-conversion family.