Skip to content

Commit

Permalink
Update Math.fround implementation to a much smaller one.
Browse files Browse the repository at this point in the history
  • Loading branch information
ljharb committed Apr 11, 2015
1 parent 8342927 commit fa75014
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 145 deletions.
171 changes: 26 additions & 145 deletions es6-shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,144 +382,6 @@
return o;
};

var numberConversion = (function () {
// from https://github.com/inexorabletash/polyfill/blob/master/typedarray.js#L176-L266
// with permission and license, per https://twitter.com/inexorabletash/status/372206509540659200

function roundToEven(n) {
var w = Math.floor(n), f = n - w;
if (f < 0.5) {
return w;
}
if (f > 0.5) {
return w + 1;
}
return w % 2 ? w + 1 : w;
}

function packIEEE754(v, ebits, fbits) {
var bias = (1 << (ebits - 1)) - 1,
s, e, f,
i, bits, str, bytes;

// Compute sign, exponent, fraction
if (v !== v) {
// NaN
// http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping
e = (1 << ebits) - 1;
f = Math.pow(2, fbits - 1);
s = 0;
} else if (v === Infinity || v === -Infinity) {
e = (1 << ebits) - 1;
f = 0;
s = (v < 0) ? 1 : 0;
} else if (v === 0) {
e = 0;
f = 0;
s = (1 / v === -Infinity) ? 1 : 0;
} else {
s = v < 0;
v = Math.abs(v);

if (v >= Math.pow(2, 1 - bias)) {
e = Math.min(Math.floor(Math.log(v) / Math.LN2), 1023);
f = roundToEven(v / Math.pow(2, e) * Math.pow(2, fbits));
if (f / Math.pow(2, fbits) >= 2) {
e = e + 1;
f = 1;
}
if (e > bias) {
// Overflow
e = (1 << ebits) - 1;
f = 0;
} else {
// Normal
e = e + bias;
f = f - Math.pow(2, fbits);
}
} else {
// Subnormal
e = 0;
f = roundToEven(v / Math.pow(2, 1 - bias - fbits));
}
}

// Pack sign, exponent, fraction
bits = [];
for (i = fbits; i; i -= 1) {
bits.push(f % 2 ? 1 : 0);
f = Math.floor(f / 2);
}
for (i = ebits; i; i -= 1) {
bits.push(e % 2 ? 1 : 0);
e = Math.floor(e / 2);
}
bits.push(s ? 1 : 0);
bits.reverse();
str = bits.join('');

// Bits to bytes
bytes = [];
while (str.length) {
bytes.push(parseInt(str.slice(0, 8), 2));
str = str.slice(8);
}
return bytes;
}

function unpackIEEE754(bytes, ebits, fbits) {
// Bytes to bits
var bits = [], i, j, b, str,
bias, s, e, f;

for (i = bytes.length; i; i -= 1) {
b = bytes[i - 1];
for (j = 8; j; j -= 1) {
bits.push(b % 2 ? 1 : 0);
b = b >> 1;
}
}
bits.reverse();
str = bits.join('');

// Unpack sign, exponent, fraction
bias = (1 << (ebits - 1)) - 1;
s = parseInt(str.slice(0, 1), 2) ? -1 : 1;
e = parseInt(str.slice(1, 1 + ebits), 2);
f = parseInt(str.slice(1 + ebits), 2);

// Produce number
if (e === (1 << ebits) - 1) {
return f !== 0 ? NaN : s * Infinity;
} else if (e > 0) {
// Normalized
return s * Math.pow(2, e - bias) * (1 + f / Math.pow(2, fbits));
} else if (f !== 0) {
// Denormalized
return s * Math.pow(2, -(bias - 1)) * (f / Math.pow(2, fbits));
} else {
return s < 0 ? -0 : 0;
}
}

function unpackFloat64(b) { return unpackIEEE754(b, 11, 52); }
function packFloat64(v) { return packIEEE754(v, 11, 52); }
function unpackFloat32(b) { return unpackIEEE754(b, 8, 23); }
function packFloat32(v) { return packIEEE754(v, 8, 23); }

var conversions = {
toFloat32: function (num) { return unpackFloat32(packFloat32(num)); }
};
if (typeof Float32Array !== 'undefined') {
var float32array = new Float32Array(1);
conversions.toFloat32 = function (num) {
float32array[0] = num;
return float32array[0];
};
}
return conversions;
}());

// Firefox 31 reports this function's length as 0
// https://bugzilla.mozilla.org/show_bug.cgi?id=1062484
if (String.fromCodePoint && String.fromCodePoint.length !== 1) {
Expand Down Expand Up @@ -1362,6 +1224,14 @@

var square = function (n) { return n * n; };
var add = function (a, b) { return a + b; };
var inverseEpsilon = 1 / Number.EPSILON;
var roundTiesToEven = function roundTiesToEven(n) {
// Even though this reduces down to `return n`, it takes advantage of built-in rounding.
return (n + inverseEpsilon) - inverseEpsilon;
};
var BINARY_32_EPSILON = Math.pow(2, -23);
var BINARY_32_MAX_VALUE = Math.pow(2, 127) * (2 - BINARY_32_EPSILON);
var BINARY_32_MIN_VALUE = Math.pow(2, -126);

var MathShims = {
acosh: function acosh(value) {
Expand Down Expand Up @@ -1536,11 +1406,22 @@
},

fround: function fround(x) {
if (x === 0 || x === Infinity || x === -Infinity || Number.isNaN(x)) {
return x;
}
var num = Number(x);
return numberConversion.toFloat32(num);
var v = Number(x);
if (v === 0 || v === Infinity || v === -Infinity || numberIsNaN(v)) {
return v;
}
var sign = Math.sign(v);
var abs = Math.abs(v);
if (abs < BINARY_32_MIN_VALUE) {
return sign * roundTiesToEven(abs / BINARY_32_MIN_VALUE / BINARY_32_EPSILON) * BINARY_32_MIN_VALUE * BINARY_32_EPSILON;
}
// Veltkamp's splitting (?)
var a = (1 + BINARY_32_EPSILON / Number.EPSILON) * abs;
var result = a - (a - abs);
if (result > BINARY_32_MAX_VALUE || numberIsNaN(result)) {
return sign * Infinity;
}
return sign * result;
}
};
defineProperties(Math, MathShims);
Expand All @@ -1566,8 +1447,8 @@
// This behavior should be governed by "round to nearest, ties to even mode"
// see https://people.mozilla.org/~jorendorff/es6-draft.html#sec-ecmascript-language-types-number-type
// These are the boundary cases where it breaks.
var smallestPositiveNumberWhereRoundBreaks = 1 / Number.EPSILON + 1;
var largestPositiveNumberWhereRoundBreaks = 2 / Number.EPSILON - 1;
var smallestPositiveNumberWhereRoundBreaks = inverseEpsilon + 1;
var largestPositiveNumberWhereRoundBreaks = 2 * inverseEpsilon - 1;
var roundDoesNotIncreaseIntegers = [smallestPositiveNumberWhereRoundBreaks, largestPositiveNumberWhereRoundBreaks].every(function (num) {
return Math.round(num) === num;
});
Expand Down
4 changes: 4 additions & 0 deletions test/math.js
Original file line number Diff line number Diff line change
Expand Up @@ -760,9 +760,13 @@ describe('Math', function () {

it('works for zeroes and infinities', function () {
expect(isPositiveZero(Math.fround(0))).to.equal(true);
expect(isPositiveZero(Math.fround({ valueOf: function () { return 0; } }))).to.equal(true);
expect(isNegativeZero(Math.fround(-0))).to.equal(true);
expect(isNegativeZero(Math.fround({ valueOf: function () { return -0; } }))).to.equal(true);
expect(Math.fround(Infinity)).to.equal(Infinity);
expect(Math.fround({ valueOf: function () { return Infinity; } })).to.equal(Infinity);
expect(Math.fround(-Infinity)).to.equal(-Infinity);
expect(Math.fround({ valueOf: function () { return -Infinity; } })).to.equal(-Infinity);
});

it('returns infinity for large numbers', function () {
Expand Down

0 comments on commit fa75014

Please sign in to comment.