more numeric constants please (especially EPSILON)
I'm only commenting on the proposals that seem to be in the current draft, because I'm reviewing a patch that adds only those particular constants. :-) Just to be clear why I'm saying nothing about the other constants, neither to praise nor to disparage.
On 03/09/2012 08:00 PM, Roger Andrews wrote:
Number.EPSILON == 2^-52
The difference between 1 and the smallest value >1 that is representable as a floating-point number.
Why pick this particular epsilon? Why not, say, 2^-1074 instead, as the difference between 0 and the next largest number? Seeing only the name I'd have guessed 2^-1074.
Number.MAX_INTEGER == 2^53 - 1
The maximum integer value that can be stored in a number without losing precision. (OK, so technically 2^53 can be stored, but that's an anomaly.)
Why discount the anomaly? Looking at SpiderMonkey's source code, we have mxr.mozilla.org/mozilla-central/search?string=<< 53 as vaguely representative of most of the places using a number like this, I think -- could be others not using the "<< 53"
string, but that's probably a fair sample. Ignore the RNG_DSCALE one, that's a red herring. But all the others use 2^53 as the pertinent value. (The dom/bindings/PrimitiveConversions.h hits using 2^53 -1 is a bug, I'm told, due to recent spec changes.) So if this constant is to exist, and I think it's a fair constant to add, why would it not be 2^53?
Jeff Walden wrote:
Why pick this particular epsilon? Why not, say, 2^-1074 instead, as the difference between 0 and the next largest number? Seeing only the name I'd have guessed 2^-1074.
See en.wikipedia.org/wiki/Machine_epsilon.
Why discount the anomaly? Looking at SpiderMonkey's source code, we have mxr.mozilla.org/mozilla-central/search?string=<< 53 as vaguely representative of most of the places using a number like this, I think -- could be others not using the
"<< 53"
string, but that's probably a fair sample. Ignore the RNG_DSCALE one, that's a red herring. But all the others use 2^53 as the pertinent value. (The dom/bindings/PrimitiveConversions.h hits using 2^53 -1 is a bug, I'm told, due to recent spec changes.) So if this constant is to exist, and I think it's a fair constant to add, why would it not be 2^53?
I think you have a point! From en.wikipedia.org/wiki/Double-precision_floating-point_format,
"Between 2^52 =4,503,599,627,370,496 and 2^53 =9,007,199,254,740,992 the representable numbers are exactly the integers."
On Jul 9, 2013, at 4:14 PM, Brendan Eich wrote:
Jeff Walden wrote:
...
Number.MAX_INTEGER == 2^53 - 1
The maximum integer value that can be stored in a number without losing precision. (OK, so technically 2^53 can be stored, but that's an anomaly.)Why discount the anomaly? Looking at SpiderMonkey's source code, we have mxr.mozilla.org/mozilla-central/search?string=<< 53 as vaguely representative of most of the places using a number like this, I think -- could be others not using the
"<< 53"
string, but that's probably a fair sample. Ignore the RNG_DSCALE one, that's a red herring. But all the others use 2^53 as the pertinent value. (The dom/bindings/PrimitiveConversions.h hits using 2^53 -1 is a bug, I'm told, due to recent spec changes.) So if this constant is to exist, and I think it's a fair constant to add, why would it not be 2^53?I think you have a point! From en.wikipedia.org/wiki/Double-precision_floating-point_format,
"Between 2^52 =4,503,599,627,370,496 and 2^53 =9,007,199,254,740,992 the representable numbers are exactly the integers."
Isn't the anomaly (and the issue) that 2^53 (9,007,199,254,740,992) is both the upper-end of the range of integers that can be exactly represented in IEEE float64, it is is also the representation of the smallest positive integer (2^53+1) that cannot be exactly represented.
In other words, if you see the IEEE float 64 encoding of 9,007,199,254,740,992 you don't know if it is an exact representation of 2^53 or an approximate representation of 2^53+1.
2^53-1 is the max integer value whose encoding is not also an approximation of some other integer value.
Allen Wirfs-Brock wrote:
Isn't the anomaly (and the issue) that 2^53 (9,007,199,254,740,992) is both the upper-end of the range of integers that can be exactly represented in IEEE float64, it is is also the representation of the smallest positive integer (2^53+1) that cannot be exactly represented.
In other words, if you see the IEEE float 64 encoding of 9,007,199,254,740,992 you don't know if it is an exact representation of 2^53 or an approximate representation of 2^53+1.
2^53-1 is the max integer value whose encoding is not also an approximation of some other integer value.
You're right, that's the reason. It helps make sense of some of the code Jeff's mxr.mozilla.org link shows. We really do have a fencepost at 2^53, not a maximum precise integer value.
On 10/07/2013, at 02:34, Allen Wirfs-Brock wrote:
In other words, if you see the IEEE float 64 encoding of 9,007,199,254,740,992 you don't know if it is an exact representation of 2^53 or an approximate representation of 2^53+1.
2^53-1 is the max integer value whose encoding is not also an approximation of some other integer value.
Or, in other words, the IEEE-754 doubles 9,007,199,254,740,992 and 9,007,199,254,740,993 are equal:
9007199254740992 === 9007199254740993
true
FWIW, we include 2^53 as in the "contiguous range of exactly representable natural numbers".
code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses/startSES.js#492
Mark S. Miller wrote:
FWIW, we include 2**53 as in the "contiguous range of exactly representable natural numbers".
code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses/startSES.js#492
It's exactly representable, but its representation is not exact. If that makes sense!
On 10/07/2013, at 03:23, Brendan Eich wrote:
Mark S. Miller wrote:
FWIW, we include 2^53 as in the "contiguous range of exactly representable natural numbers".
code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses/startSES.js#492
It's exactly representable, but its representation is not exact. If that makes sense!
2^53 is exactly representable, but it gets the exact same representation as 2^53 + 1
Jorge Chamorro wrote:
2^53 is exactly representable, but it gets the exact same representation as 2^53 + 1
Yes, you said that last time, and Allen said it before in the message to which you replied :-P.
I initially didn't think this mattered, but it is an excellent and important point. Look at the use I make of Nat in Dr.SES in Figure 1 of research.google.com/pubs/pub40673.html:
var makeMint = () => {
var m = WeakMap();
var makePurse = () => mint(0);
var mint = balance => {
var purse = def({
getBalance: () => balance,
makePurse: makePurse,
deposit: (amount, srcP) =>
Q(srcP).then(src => {
Nat(balance + amount);
m.get(src)(Nat(amount));
balance += amount;
})
});
var decr = amount => { balance = Nat(balance - amount); };
m.set(purse, decr);
return purse;
};
return mint;
};
Because Nat includes 2^53, this code actually fails to enforce conservation of currency!! I've repeatedly claimed this conservation property about this code and code like it for a long time now, to many audiences and in several papers. There have been several exercises proving some properties of this code correct and laying the groundwork for proving conservation of currency. However, none have previously spotted this hole.
On 07/09/2013 04:14 PM, Brendan Eich wrote:
Hmm, my memory of the meaning of epsilon was obviously horribly wrong. :-) This is obviously sane then.
On 10/07/2013, at 03:49, Mark S. Miller wrote:
Because Nat includes 2^53, this code actually fails to enforce conservation of currency!! I've repeatedly claimed this conservation property about this code and code like it for a long time now, to many audiences and in several papers. There have been several exercises proving some properties of this code correct and laying the groundwork for proving conservation of currency. However, none have previously spotted this hole.
Right, if balance+amount ever result in 2^53+1, the code would rather "see" it (and save it!) as 2^53.
Sort of a new kind of off by one error... for the wikipedia?
On Jul 9, 2013, at 6:09 PM, Mark S. Miller wrote:
FWIW, we include 2^53 as in the "contiguous range of exactly representable natural numbers".
code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses/startSES.js#492
What you want is: the continuous range of exactly and unambiguously representable natural numbers
On 10/07/2013, at 03:45, Brendan Eich wrote:
Jorge Chamorro wrote:
On 10/07/2013, at 03:23, Brendan Eich wrote:
Mark S. Miller wrote:
FWIW, we include 2^53 as in the "contiguous range of exactly representable natural numbers".
code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses/startSES.js#492 It's exactly representable, but its representation is not exact. If that makes sense!
2^53 is exactly representable, but it gets the exact same representation as 2^53 + 1
Yes, you said that last time, and Allen said it before in the message to which you replied :-P.
He, yes, I'm amazed, there's lots of fun on the edge:
a = Math.pow(2,53)
9007199254740992
a === a+1
true
a === a+2-1
true
And my favorite:
(a+1-1) === (a-1+1)
false
On 07/09/2013 06:49 PM, Mark S. Miller wrote:
Because Nat includes 2^53, this code actually fails to enforce conservation of currency!!
The problem isn't that Nat includes 2^53. It's that you're performing an operation that may compute an inexact value, then you're treating that inexact value as if it were exact. You should be testing before performing any operation that might compute an inexact value. Or, you should be rejecting values which might be rounded from an inexact value. Which would mean your MAX_NAT test should instead be
if (allegedNum >= MAX_NAT) { throw new RangeError('too big'); }
But really, Nat seems like the wrong concept to me. Even if you correct it as above, it's only correctly usable if it is applied after every floating point operation. If you have |a + b|, you can correctly apply a corrected Nat to that. But if you have a + b + c
or a + b - c
or any more floating-point operations than a single operation, Nat can't be correctly applied. Corrected Nat as-is gives a false sense of security, by implying that you can apply it to a calculation and it'll do the right thing, when really it'll only do so if the value you're passing in is the result of no more than a single computation.
On Fri, Jul 12, 2013 at 4:07 PM, Jeff Walden <jwalden+es at mit.edu> wrote:
The problem isn't that Nat includes 2^53. It's that you're performing an operation that may compute an inexact value, then you're treating that inexact value as if it were exact. You should be testing before performing any operation that might compute an inexact value. Or, you should be rejecting values which might be rounded from an inexact value. Which would mean your MAX_NAT test should instead be
if (allegedNum >= MAX_NAT) { throw new RangeError('too big'); }
But really, Nat seems like the wrong concept to me. Even if you correct it as above, it's only correctly usable if it is applied after every floating point operation. If you have
a + b
, you can correctly apply a corrected Nat to that. But if you havea + b + c
ora + b - c
or any more floating-point operations than a single operation, Nat can't be correctly applied. Corrected Nat as-is gives a false sense of security, by implying that you can apply it to a calculation and it'll do the right thing, when really it'll only do so if the value you're passing in is the result of no more than a single computation.
Mark's Nat() function does throw if the input isn't an exactly-representable number.
On 07/12/2013 04:09 PM, Tab Atkins Jr. wrote:
Mark's Nat() function does throw if the input isn't an exactly-representable number.
Yes. I'm arguing that's not helpful when you can compute an exactly-representable number, that is the result of an inexact calculation, like Math.pow(2, 53) + 1 - 4
. The JS result of that calculation is an exactly-representable number. But the mathematical result of that computation is not the same number. Nat treats the number passed to it as if it were a calculation's exact result, when it may not be.
I understand that I need to do it after each individual operation. I agree that the API is hazard prone, in that other programmers might not realize the caution needed to use it safely. I would like a better API -- both less likely to be used unsafely and no harder (or not much harder) to use safely. Suggestions?
On 07/12/2013 04:53 PM, Mark S. Miller wrote:
I would like a better API -- both less likely to be used unsafely and no harder (or not much harder) to use safely. Suggestions?
In C++ you'd want MS's SafeInt, or WTF's CheckedInt, with operator overloading and all that jazz. Without operator overloading the best I can think of are functions for every operation, that have to be used, and if you use the raw operators you take your life into your own hands. Definitely not as easy as just doing the math the "normal"-looking way. I don't see a super-nice way to do this. :-\
When getting rough with floating-point numbers, several constants come in handy, especially the "machine epsilon". (Note there is some confusion in definition of "machine epsilon" and "unit roundoff" - the definition below is the one we want and the one used in the C library.)
Proposals:
Number.EPSILON == 2^-52
The difference between 1 and the smallest value >1 that is representable as a floating-point number.Number.MIN_NORMAL == 2^-1022
The minimum positive value that can be stored as an IEEE normal number.Number.MAX_INTEGER == 2^53 - 1
The maximum integer value that can be stored in a number without losing precision. (OK, so technically 2^53 can be stored, but that's an anomaly.)Number.MAX_POWTWO == 2^1023
The maximum power-of-two that can be stored in a number.