splitting Number into s-m-e, radix too (cf. DFP/Decimal)
Not gonna change number (the primitive type) to IEEE 754r DFP (not sure you were getting at that in closing). Just FTR.
These Number.prototype getters can be implemented today in top browsers (thanks to typed arrays) and in ES6 (binary data embraces and extends typed arrays). See dherman/float.js.
Technically you don't even need typed arrays to do it; it's just more work to implement a "pure" library that extracts the bit strings. With typed arrays it was utterly trivial (though you have to be slightly careful to avoid writing unportable code.)
FWIW.
Dealing with denormals is a pain.
js> Math.log(Number.MAX_VALUE)
709.782712893384 js> Math.log(Number.MAX_VALUE)/Math.log(2)
1024 js> Math.log(Number.MIN_VALUE)/Math.log(2) -1074 js> Math.log(Number.MIN_VALUE2)/Math.log(2) -1073 js> Math.log(Number.MIN_VALUE3)/Math.log(2) -1072.415037499279 js> Math.log(Number.MIN_VALUE*3)/Math.log(2)+1074
1.5849625007210761 js> Math.pow(2,Math.log(Number.MIN_VALUE*3)/Math.log(2)+1074)
2.9999999999998335
Also it's nice to see those NaN bits and ensure they're all set! Canonical quiet NaN is required.
Currently my code to extract the sign (s), significand (m), exponent (e), radix (b) from Number is as follows. This is slow, is there a better way to get these desirable values?
++++code here++++
IEEE.split = function (x) { x = +x; // guard against non-numerics
var s, m, e, abs;
// handle NaN, infinities, zeroes
if (isNaN( x )) { return {s: 0, m: Number.NaN, e: Number.NaN, b: 2}; } if (1/x == 0) { s = (x < 0)? 1 : 0; return {s: s, m: Number.POSITIVE_INFINITY, e: Number.POSITIVE_INFINITY, b: 2}; } if (x == 0) { s = (1/x < 0)? 1 : 0; return {s: s, m: 0, e: Number.NEGATIVE_INFINITY, b: 2}; }
// compute the sign
if (x >= 0) { s = 0, abs = x; } else { s = 1, abs = -x; }
// calculate the approx exponent; // then fix the exponent estimate by computing the significand
e = Math.floor( Math.log( abs ) / Math.LN2 );
m = abs / Math.pow( 2, e ); if (m >= 2) e += 1, m /= 2; if (m < 1) e -= 1, m *= 2;
return {s: s, m: m, e: e, b:2}; };
From: "David Herman" <dherman at mozilla.com>
Sent: Wednesday, March 21, 2012 7:20 AM To: "Brendan Eich" <brendan at mozilla.org>
Cc: "Roger Andrews" <roger.andrews at mail104.co.uk>; <es-discuss at mozilla.org>
Subject: Re: splitting Number into s-m-e, radix too (cf. DFP/Decimal)
The radix for Number is always 2. Providing this value simply future-proofs against Decimal (should that ever happen). Then the programmer could say: (x.radix == 10)? do_decimal_thing(x) : do_binary_thing(x) instead of: (typeof x == "decimal")? do_decimal_thing(x) : do_binary_thing(x)
I believe this would be nice if there is controversy over what "typeof mydecimal" would be. Also it works for Objects which are numbers, when typeof returns "object".
In my code I have Number.prototype.radix = 2; Number.prototype.numericType = "binary64"; which have proved rather useful.
Language purists may be offended by this engineer's nuts'n'bolts approach.
From: "Brendan Eich" <brendan at mozilla.org>
Sent: Wednesday, March 21, 2012 7:00 AM To: "Roger Andrews" <roger.andrews at mail104.co.uk>
Cc: <es-discuss at mozilla.org>
Subject: Re: splitting Number into s-m-e, radix too (cf. DFP/Decimal)
Furthermore, to do the 'logB' and 'scaleB' operations I want to say: x.exponent // logB(x) x.exponent += n // scaleB(x,n)
Instead of the slow code which follows (sorry about the length):
++++code here++++
IEEE.logB = function (x) { x = +x; // guard against non-numerics
var m, e; var abs = (x >= 0)? x : -x; // don't care about -0!
// calculate the approx exponent; // then fix the exponent estimate by computing the significand // (also works for pow() overflow/underflow!)
e = Math.floor( Math.log( abs ) / Math.LN2 );
m = abs / Math.pow( 2, e ); // don't care about NaNs! if (m >= 2) e += 1; if (m < 1) e -= 1;
return e; };
IEEE.scaleB = function (x,n) { x = +x, n = +n; // guard against non-numerics
// handle NaN, infinities, zeroes
if (isNaN( x/x )) return x;
// multiply x by 2^n, first taking care for large |n|
if (isFinite( n )) { while (n > 1023 && 1/x != 0) { x *= Number.MAX_POWTWO; n -= 1023; } while (n < -1022 && x != 0) { x *= Number.MIN_NORMAL; n += 1022; } } return x * Math.pow( 2, n ); };
Numerical Analysts don't really want the actual bit strings of s-m-e. They want the values.
The ancient C functions frexp/ldexp are great for this (see also modf(3)).
frexp/ldexp were created before 1980 -- before IEEE754 was even a glint in William Kahan's eye. So they are not quite the same as logB/scaleB and split/join-type functions. frexp(3) delivers a significand in the interval [0.5,1) not [1,2) union 0, and it is a bit weird with the sign. ldexp(3) is almost perfect as scaleB.
From: "David Herman" <dherman at mozilla.com>
Floating-point numbers are formed of a sign (s), significand (m), exponent (e), and radix (b): (-1)^s * m * b^e where 0<=m<b.
Often we want to access the s-m-e values directly, especially the significand. Notably when doing comparison for approximate equality by "masking out" the rounding error in the low order bits; or some multiprecise work. In ES5 this is painful.
The sign, significand, exponent, radix are intrinsic properties of a Number (like the length of a String) - so how about exposing them as properties of Number? Say the sign, significand, and exponent could be read-write and the radix read-only.
Advantages:
Also programmers could distinguish a binary value from a decimal without using typeof, using the 'radix' property instead. This might future-proof against the possible coming of a Decimal type without breaking existing code. Does it help there?