Skip to content

Instantly share code, notes, and snippets.

@shicks
Last active January 31, 2022 19:01
Show Gist options
  • Save shicks/7a97ec6b3f10212e60a89a7f6d2d097d to your computer and use it in GitHub Desktop.
Save shicks/7a97ec6b3f10212e60a89a7f6d2d097d to your computer and use it in GitHub Desktop.
Universal polyfill for Math.fround, does not depend on Float32Array.
// 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);
};
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));
});
@shicks
Copy link
Author

shicks commented Jan 31, 2022

Thanks for finding that - I added a comment to the top of the gist to hopefully direct anyone to your comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment