Last active
January 31, 2022 19:01
-
-
Save shicks/7a97ec6b3f10212e60a89a7f6d2d097d to your computer and use it in GitHub Desktop.
Universal polyfill for Math.fround, does not depend on Float32Array.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// NOTE: This implementation is incorrect (it doesn't break ties to even). See comments below for discussion. | |
Math.fround = Math.fround || function(arg) { | |
arg = Number(arg); | |
// Return early for ±0 and NaN. | |
if (!arg) return arg; | |
var sign = arg < 0 ? -1 : 1; | |
if (sign < 0) arg = -arg; | |
// Compute the exponent (8 bits, signed). | |
var exp = Math.floor(Math.log(arg) / Math.LN2); | |
var powexp = Math.pow(2, Math.max(-126, Math.min(exp, 127))); | |
// Handle subnormals: leading digit is zero if exponent bits are all zero. | |
var leading = exp < -127 ? 0 : 1; | |
// Compute 23 bits of mantissa, inverted to round toward zero. | |
var mantissa = Math.round((leading - arg / powexp) * 0x800000); | |
if (exp > 0 && mantissa <= -0x800000) return sign * Infinity; | |
return sign * powexp * (leading - mantissa / 0x800000); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
it('should handle terminating numbers', function() { [5/32762] | |
// These cases all have terminating binary representations (i.e. integers | |
// or rationals with powers of two in the denominator). | |
assertPositiveZero(Math.fround(0)); | |
assertNegativeZero(Math.fround(-0)); | |
assertEquals(1, Math.fround(1)); | |
assertEquals(-1, Math.fround(-1)); | |
assertEquals(0.5, Math.fround(0.5)); | |
assertEquals(0.25, Math.fround(0.25)); | |
assertEquals(-0.75, Math.fround(-0.75)); | |
assertEquals(3, Math.fround(3)); | |
assertEquals(-20.375, Math.fround(-20.375)); | |
assertEquals(101.3125, Math.fround(101.3125)); | |
assertEquals(1 << 22, Math.fround(1 << 22)); | |
}); | |
it('should handle large numbers', function() { | |
assertEquals(1 << 30, Math.fround(1 << 30)); | |
assertEquals(2 ** 127, Math.fround(2 ** 127)); | |
assertEquals(-(2 ** 127), Math.fround(-(2 ** 127))); | |
assertEquals(1.875 * (2 ** 127), Math.fround(1.875 * (2 ** 127))); | |
assertEquals(-1.9375 * (2 ** 127), Math.fround(-1.9375 * (2 ** 127))); | |
assertEquals('a', Infinity, Math.fround(2 ** 128)); | |
assertEquals(-Infinity, Math.fround(-(2 ** 128))); | |
const maxFloat = 3.4028234663852886e38; | |
assertEquals(maxFloat, Math.fround(3.4028235e38)); | |
assertEquals(Infinity, Math.fround(3.4028236e38)); | |
assertEquals('b', Infinity, Math.fround(Infinity)); | |
assertEquals(-maxFloat, Math.fround(-3.4028235e38)); | |
assertEquals(-Infinity, Math.fround(-3.4028236e38)); | |
assertEquals(-Infinity, Math.fround(-Infinity)); | |
}); | |
it('should handle small numbers', function() { | |
// Smallest normal float32 | |
assertEquals(1.015625 * 2 ** -126, Math.fround(1.015625 * 2 ** -126)); | |
assertEquals(-1.015625 * 2 ** -126, Math.fround(-1.015625 * 2 ** -126)); | |
// Subnormal numbers | |
assertEquals(1.015625 * 2 ** -127, Math.fround(1.015625 * 2 ** -127)); | |
assertEquals(1.875 * 2 ** -128, Math.fround(1.875 * 2 ** -128)); | |
// Numbers exactly between two floats round toward zero | |
const minFloat = 2 ** -149; | |
assertEquals(12 * minFloat, Math.fround(12.5 * minFloat)); | |
assertEquals(13 * minFloat, Math.fround(12.5 * (1 + EPSILON) * minFloat)); | |
assertEquals(-12 * minFloat, Math.fround(-12.5 * minFloat)); | |
assertEquals(-13 * minFloat, Math.fround(-12.5 * (1 + EPSILON) * minFloat)); | |
// Smallest non-zero float32 | |
assertEquals(minFloat, Math.fround(minFloat)); | |
assertEquals(-minFloat, Math.fround(-minFloat)); | |
assertEquals(minFloat, Math.fround((1 + EPSILON) / 2 * minFloat)); | |
assertEquals(-minFloat, Math.fround(-(1 + EPSILON) / 2 * minFloat)); | |
assertPositiveZero(Math.fround(minFloat / 2)); | |
assertNegativeZero(Math.fround(-minFloat / 2)); | |
// Edge cases around mantissa === -0x800000 | |
assertEquals(2 ** -125, Math.fround(2 ** -125)); | |
assertEquals(2 ** -124, Math.fround(2 ** -124)); | |
}); | |
it('should handle non-numbers', function() { | |
assertEquals(1, Math.fround(noCheck('1'))); | |
assertExactlyNaN(Math.fround(noCheck([1, 2]))); | |
assertExactlyNaN(Math.fround(noCheck('a'))); | |
assertExactlyNaN(Math.fround(NaN)); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for finding that - I added a comment to the top of the gist to hopefully direct anyone to your comment.