more numeric constants please (especially EPSILON)

# Roger Andrews (13 years ago)

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.

# Jeff Walden (11 years ago)

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?

# Brendan Eich (11 years ago)

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."

# Allen Wirfs-Brock (11 years ago)

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.

# Brendan Eich (11 years ago)

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.

# Jorge Chamorro (11 years ago)

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
# Mark S. Miller (11 years ago)

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

# Brendan Eich (11 years ago)

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!

# Jorge Chamorro (11 years ago)

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

# Brendan Eich (11 years ago)

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.

# Mark S. Miller (11 years ago)

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.

# Mark S. Miller (11 years ago)
# Jeff Walden (11 years ago)

On 07/09/2013 04:14 PM, Brendan Eich wrote:

See en.wikipedia.org/wiki/Machine_epsilon.

Hmm, my memory of the meaning of epsilon was obviously horribly wrong. :-) This is obviously sane then.

# Jorge Chamorro (11 years ago)

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?

# Allen Wirfs-Brock (11 years ago)

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

# Jorge Chamorro (11 years ago)

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
# Jeff Walden (11 years ago)

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.

# Tab Atkins Jr. (11 years ago)

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 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.

Mark's Nat() function does throw if the input isn't an exactly-representable number.

# Jeff Walden (11 years ago)

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.

# Mark S. Miller (11 years ago)

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?

# Jeff Walden (11 years ago)

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. :-\

# Brendan Eich (11 years ago)

We should just add

strawman:bignums

to ES7.