Unlimited Integers (was: more numeric constants please (especially EPSILON))

# Mark S. Miller (12 years ago)

On Sat, Jul 13, 2013 at 8:46 PM, Brendan Eich <brendan at mozilla.com> wrote:

We should just add

strawman:bignums

to ES7.

First, I wholeheartedly agree. JS is increasingly being used as a target of compilation. When I asking people doing so what they biggest pain point is, the lack of support for integers is often the first thing mentioned. That said, there are the usual open issues we'll need to settle, most of which aren't even raised by this strawman.

Most important is the same as one of the objections raised about decimal: If we treat each individual new primitive type as a one-off special case, we're digging ourselves a deeper abstraction mechanism hole. We first need something like value proxies, so we understand how unprivileged libraries would define new numeric types -- complex, rational, matrix. Once we understand what the bignum API would look like were it defined by an unprivileged library, we can proceed to add it as a primitive anyway. We can even do so before actually accepting a value proxy proposal if necessary, as long as we've settled what the use-API would be.

Is there a corresponding wrapping type? How does this wrapping type relate to Number?

I think the pragma is a bad idea but not a terrible idea. The problem is that we have no choice that a floating point number that happens to have an integral value prints as an integer. Given that, it would be confusing to read simple integer literals into a different type.

Should bignums toString with the i suffix? In general I would say yes. The biggest argument that they should not is that we should be able to use bignums to index into arrays.

Contradicting myself above, if we do support such a pragma, we should also have a JSON.parse option that reads integers into bignums, and one that prints only bignums as integers. This JSON option would also print all floats using only float notation.

Were we not introducing TypedArrays until ES7, would we have typedArray.length be a bignum rather than a floating point number? If so, is there anything we can do in ES6 to leave ourselves this option in ES7?

# Brendan Eich (12 years ago)

Mark S. Miller wrote:

First, I wholeheartedly agree. JS is increasingly being used as a target of compilation. When I asking people doing so what they biggest pain point is, the lack of support for integers is often the first thing mentioned.

int64/uint64 come up faster when compiling from C/C++. I've prototyped these in SpiderMonkey in a patch that I'm still rebasing, but planning to land pretty soon:

bugzilla.mozilla.org/show_bug.cgi?id=749786

js> i = 0L

0L
js> u = 0UL

0UL
js> i = ~i
-1L
js> u = ~u

18446744073709551615UL
js> (i>>>1)

9223372036854775807L
js> (i>>>1)==(u>>1)

true
js> (i>>>1)===(u>>1)

false
js> i === int64(-1)

true

That said, there are the usual open issues we'll need to settle, most of which aren't even raised by this strawman.

I've of course had to address these, and posted about it in the past -- recap below.

Most important is the same as one of the objections raised about decimal: If we treat each individual new primitive type as a one-off special case, we're digging ourselves a deeper abstraction mechanism hole. We first need something like value proxies, so we understand how unprivileged libraries would define new numeric types -- complex, rational, matrix.

Don't forget ratplex ;-).

Seriously, we would like to support composition of rational and complex without having to wrap in order to add operators among the types.

I think "value proxy" is the wrong term, though. These aren't proxies. They must be tight, minimal-overhead objects with value semantics.

Once we understand what the bignum API would look like were it defined by an unprivileged library, we can proceed to add it as a primitive anyway. We can even do so before actually accepting a value proxy proposal if necessary, as long as we've settled what the use-API would be.

That was my approach in exploring int64/uint64. Everything I hacked under the hood is ready for other types: int32/uint32, float32, bignum, complex, ....

For operators, I implemented Christian P. Hansen's suggestion from 2006, ES4 era:

/*
  * Value objects specified by
  *
  *  http://wiki.ecmascript.org/doku.php?id=strawman:value_objects
  *
  * define a subset of frozen objects distinguished by the JSCLASS_VALUE_OBJECT
  * flag and given privileges by this prototype implementation.
  *
  * Value objects include int64 and uint64 instances and support the expected
  * arithmetic operators: | ^ & == < <= << >> >>> + - * / % (boolean test) ~
  * unary - and unary +.
  *
  * != and ! are not overloadable to preserve identities including
  *
  *  X ? A : B <=>  !X ? B : A
  *  !(X && Y) <=>  !X || !Y
  *  X != Y <=>  !(X == Y)
  *
  * Similarly, > and >= are derived from < and <= as follows:
  *
  *  A > B <=>  B < A
  *  A >= B <=>  B <= A
  *
  * We provide <= as well as < rather than derive A <= B from !(B < A) in order
  * to allow the <= overloading to match == semantics.
  *
  * The strict equality operators, === and !==, cannot be overloaded, but they
  * work on frozen-by-definition value objects via a structural recursive strict
  * equality test, rather than by testing same-reference. Same-reference remains
  * a fast-path optimization.
  *
  * Ecma TC39 has tended toward proposing double dispatch to implement binary
  * operators. However, double dispatch has notable drawbacks:
  *
  *  - Left-first asymmetry.
  *  - Exhaustive type enumeration in operator method bodies.
  *  - Consequent loss of compositionality (complex and rational cannot be
  *    composed to make ratplex without modifying source code or wrapping
  *    instances in proxies).
  *
  * So we eschew double dispatch for binary operator overloading in favor of a
  * cacheable variation on multimethod dispatch that was first proposed in 2009
  * by Christian Plesner Hansen:
  *
  *  https://mail.mozilla.org/pipermail/es-discuss/2009-June/009603.html
  *
  * Translating from that mail message:
  *
  * When executing the '+' operator in 'A + B' where A and B refer to value
  * objects, do the following:
  *
  *  1. Get the value of property LOP_PLUS in A, call the result P
  *  2. If P is not a list, throw a TypeError: no '+' operator
  *  3. Get the value of property ROP_PLUS in B, call the result Q
  *  4. If Q is not a list, throw a TypeError: no '+' operator
  *  5. Intersect the lists P and Q, call the result R
  *  6. If R is empty throw, a TypeError: no '+' operator
  *  7. If R has more than one element, throw a TypeError: ambiguity
  *  8. If R[0], call it F, is not a function, throw a TypeError
  *  9. Evaluate F(A, B) and return the result
  *
  * Rather than use JS-observable identifiers to label operator handedness as
  * in Christian's proposal ('this+', '+this'), we use SpecialId variants that
  * cannot be named in-language or observed by proxies.
  *
  * To support operator overloading in-language, we need only provide an API
  * similar to the one Christian proposed:
  *
  *  function addPointAndNumber(a, b) {
  *    return new Point(a.x + b, a.y + b);
  *  }
  *
  *  Function.defineOperator('+', addPointAndNumber, Point, Number);
  *
  *  function addNumberAndPoint(a, b) {
  *    return new Point(a + b.x, a + b.y);
  *  }
  *
  *  Function.defineOperator('+', addNumberAndPoint, Number, Point);
  *
  *  function addPoints(a, b) {
  *    return new Point(a.x + b.x, a.y + b.y);
  *  }
  *
  *  Function.defineOperator('+', addPoints, Point, Point);
  */

I recall you were warm to Christian's suggestion way back then. Let me know if anything above is opaque, I'll answer questions and feed changes into the comment in the patch.

Is there a corresponding wrapping type? How does this wrapping type relate to Number?

No wrapping object type -- those are legacy, to be avoided. See strawman:value_objects. The main thing is value not reference semantics.

I think the pragma is a bad idea but not a terrible idea. The problem is that we have no choice that a floating point number that happens to have an integral value prints as an integer. Given that, it would be confusing to read simple integer literals into a different type.

Right. For int64/uint64 I followed C#'s L and UL suffixes. For bignum, 'i' could be used. You remember the old value types idea we discussed of extensible suffixes.

Should bignums toString with the i suffix? In general I would say yes. The biggest argument that they should not is that we should be able to use bignums to index into arrays.

That's fine, and we'll want {,u}int64 indexes first or sooner (or faster -- again the C++/C Emscripten/Mandreel compilation target language). For ES6 we had a hope of relaxing Array to use integer-domain double (number) index type instead of uint32, and Allen and I discussed this recently. It's really not clear whether real code (not testsuite code) depends on ToUint32. IE had boundary bugs in old JScript for years. Did any portable code reliably depend on ECMA-262 letter-of-law here?

Contradicting myself above, if we do support such a pragma, we should also have a JSON.parse option that reads integers into bignums, and one that prints only bignums as integers. This JSON option would also print all floats using only float notation.

JSON is precision agnostic, so this seems doable. One could imagine wanting a higher precision JSON decoding float type than double, too!

Were we not introducing TypedArrays until ES7, would we have typedArray.length be a bignum rather than a floating point number? If so, is there anything we can do in ES6 to leave ourselves this option in ES7?

That would be unwanted, overkill. All typed arrays want is memory capacity, and 64 bits is more than enough. Even 53 is enough, and that's where ES6 is going, last I talked to Allen. People do want >4MB typed arrays.

# Mark S. Miller (12 years ago)

On Sun, Jul 14, 2013 at 10:39 PM, Brendan Eich <brendan at mozilla.com> wrote:

int64/uint64 come up faster when compiling from C/C++. I've prototyped these in SpiderMonkey in a patch that I'm still rebasing, but planning to land pretty soon:

bugzilla.mozilla.org/show_bug.cgi?id=749786

I don't think we should introduce precision limited integers into JS, ever. The only point would be to do something weird on overflow of the precision limit. The traditional C-like weirdness is wrapping -- the only advantage being that it avoids even needing to check for overflow.

I recall you were warm to Christian's suggestion way back then. Let me know if anything above is opaque, I'll answer questions and feed changes into the comment in the patch.

I recall that I was generally warm but had complex reactions. I no longer recall what they were ;). Agreed that naturally composing complex and rational is a good test case. Will take a look.

That would be unwanted, overkill. All typed arrays want is memory capacity, and 64 bits is more than enough. Even 53 is enough, and that's where ES6 is going, last I talked to Allen. People do want >4MB typed arrays.

When a bignum represents a small integer, there's no reason that it needs to be any slower than a JS floating point number representing a small integer. Most JS implementations already optimize the latter to store the small integer in the "pointer" with a tag indicating that the non-tag bits are the small integer value. Exactly the same trick has been used for bignums in Lisps and Smalltalks for many decades. As long as the numbers represent small integers, I think the only differences would be semantics, not performance.

# Domenic Denicola (12 years ago)

From: Brendan Eich [brendan at mozilla.com]

No wrapping object type -- those are legacy, to be avoided. See strawman:value_objects. The main thing is value not reference semantics.

Hmm, is 0UL.toString() not possible then? What about 0UL + ""?

# Mark S. Miller (12 years ago)

On Sun, Jul 14, 2013 at 10:39 PM, Brendan Eich <brendan at mozilla.com> wrote:

No wrapping object type -- those are legacy, to be avoided. See strawman:value_objects. The main thing is value not reference semantics.

On that page: "It’s forwards-compatible with future mechanisms for user-defined value objects." How can we be confident of this? I would like to be.

# Brendan Eich (12 years ago)

Mark S. Miller wrote:

I don't think we should introduce precision limited integers into JS, ever. The only point would be to do something weird on overflow of the precision limit. The traditional C-like weirdness is wrapping -- the only advantage being that it avoids even needing to check for overflow.

We already have precision limited integers in JS, both via the bitwise-logical and shift operators, and via typed arrays / binary data. We need 64-bit ints for the latter, scalar and SIMD-vector as well as arbitrary length typed array element type.

This ship has sailed, bignums are not equivalent or a superset. These are in the hardware, they need to be in JS for the "memory safe low-road" role it plays for WebGL, asm.js, etc.

When a bignum represents a small integer, there's no reason that it needs to be any slower than a JS floating point number representing a small integer. Most JS implementations already optimize the latter to store the small integer in the "pointer" with a tag indicating that the non-tag bits are the small integer value. Exactly the same trick has been used for bignums in Lisps and Smalltalks for many decades.

Sure, I'm familiar -- but again those older implementations did not face the performance constraints of the "low road" that JS does, nor were they were quite so aggressively optimized.

I'm not saying bignums couldn't be used as doubles are today -- they could.

Rather, JS has number and always will, and the speculations toward int are sunk cost (good sunk cost in a practical sense). Waiting for bignums to come in before typed arrays, just to support types bigger than memory in any foreseeable future, and get the same optimizations as number, does not fly. We won't wait in ES6 -- we will be lucky to get integer-domain double instead of uint32 lengths for typed arrays if not arrays.

As long as the numbers represent small integers, I think the only differences would be semantics, not performance.

Both, in the "short run", in practice. But we aren't even doing bignums in ES6, and we are doing typed arrays / binary data. We need to satisfy >4G memory buffer use cases now, and be future-proof. 53 bits is enough.

# Brendan Eich (12 years ago)

Domenic Denicola wrote:

From: Brendan Eich [brendan at mozilla.com]

No wrapping object type -- those are legacy, to be avoided. See strawman:value_objects. The main thing is value not reference semantics.

Hmm, is 0UL.toString() not possible then? What about 0UL + ""?

Of course those are possible -- int64 and uint64 are value objects, as I shows last message:

js> i = -1L
-1L
js> i === int64(-1)

true
js> u = 0UL

0UL
js> u = ~u

18446744073709551615UL
js> u.toString(16)
"ffffffffffffffff"

There's no mutable Int64 or Uint64 wrapper, that's the point.

# Oliver Hunt (12 years ago)

On Jul 15, 2013, at 8:15 AM, Mark S. Miller <erights at google.com> wrote:

No wrapping object type -- those are legacy, to be avoided. See strawman:value_objects. The main thing is value not reference semantics.

On that page: "It’s forwards-compatible with future mechanisms for user-defined value objects." How can we be confident of this? I would like to be.

That's a concern I have as well -- i'm not 100% sold on user-defined value objects, but i think i'd prefer that we get those done before bolting on [u]int64 and hoping that they're forwards compatible. I don't want to deal with any "we can't do x due to uint64" style problems.

# Brendan Eich (12 years ago)

Oliver Hunt wrote:

That's a concern I have as well -- i'm not 100% sold on user-defined value objects, but i think i'd prefer that we get those done before bolting on [u]int64 and hoping that they're forwards compatible. I don't want to deal with any "we can't do x due to uint64" style problems.

Totally -- that's why all of this is on the ES7 agenda. I'll write a strawman on user-defined value objects.