edgegoat Guides
JavaScript number edge cases to test
A JavaScript number is an IEEE 754 double. That gives you two zeros, a value that is not equal to itself, two infinities, and integers that stop being exact after 2^53.
The bug: losing the sign between -1 and 0
A formatter that prints the integer part and the fraction separately works for -1.5 and 0.5, but not for -0.5: the integer part of -0.5 is -0, and String(-0) is "0". pdf.js had exactly this bug in util.printf, fixed in mozilla/pdf.js#22017.
A naive implementation that fails
function formatFixed(n, digits) {
const int = Math.trunc(n);
const frac = Math.abs(n - int).toFixed(digits).slice(1); // ".50"
return String(int) + frac;
}
formatFixed(-0.5, 2); // "0.50", expected "-0.50"The edgegoat test that catches it
import { expect, test } from 'vitest';
import { numbers } from 'edgegoat';
test.each(numbers({ tags: ['sign', 'negative'] }))('$label keeps its sign', ({ value }) => {
expect(formatFixed(value, 2).startsWith('-')).toBe(value < 0);
});-0.5, -0.0001 and -1e-7: each truncates to -0, which prints as 0. A sign aware version formats Math.abs(n) and prefixes the minus sign when n < 0.Infinity is a real value
Infinity shows up as a "never expires" or "no limit" sentinel. tough-cookie had to treat an infinite max-age as a cookie that never expires (salesforce/tough-cookie#644). Elsewhere it overflows: setTimeout(fn, Infinity) fires after about 1 ms in Node.js, because delays that do not fit in 32 bits are reset to 1, and JSON.stringify(Infinity) is "null".
What numbers() contains
- Zeros and signs:
0,-0,-0.5,-0.0001,-1e-7. - Special values:
NaN,Infinity,-Infinity. - Limits:
MAX_SAFE_INTEGERand its neighbors,MIN_SAFE_INTEGER - 1,MAX_VALUE,MIN_VALUE,EPSILON,2^31,2^32. - Rounding and formatting:
0.1 + 0.2,1.005,0.5,1e21,1e-7.
numericStrings() adds inputs like "", "1e3", "0x1A", "1,5" and "9007199254740993", where Number, parseInt and parseFloat disagree. bigints() covers the int64 and uint64 limits.
FAQ
Neither 0.1 nor 0.2 has an exact binary representation, so their sum is 0.30000000000000004. Compare with a tolerance, or keep money in integer cents.
Number.MAX_SAFE_INTEGER, 2^53 - 1 (9007199254740991). Above it, consecutive integers can no longer all be represented, so 2 ** 53 + 1 === 2 ** 53. Use BigInt or strings for 64 bit IDs.
Use Number.isNaN(x). The global isNaN converts its argument first, so isNaN("abc") is true, and x === NaN is always false.
Number.prototype.toString switches to exponent notation for magnitudes of 1e21 and above and below 1e-6, so String(1e21) is "1e+21" and digit only checks fail.
More guides
- How to test time zones in JavaScript
- How to test daylight saving time in Jest
- Unicode test strings for JavaScript
- Testing dates before 1970 in JavaScript
- Negative zero in JavaScript: why -0 is different from 0
- How to mock Date in JavaScript tests
- Property based testing and curated edge cases in JavaScript