A directive to solve the JavaScript arithmetic precision issue

Brendan Eich brendan at mozilla.com
Mon Aug 15 14:28:50 PDT 2011


On Aug 15, 2011, at 1:58 PM, David Bruant wrote:

> Le 15/08/2011 21:33, Brendan Eich a écrit :
>> On Aug 15, 2011, at 10:33 AM, David Bruant wrote:
>>> I don't know if it has been proposed already, but what about a
>>> directive? Code could look like:
>>> ------
>>> "use precise arithmetic"; // choose any other name you'd like here
>>> 
>>> var a = 0.1 + 0.2; // 0.3 // FINALLY!
>>> ------
>>> What number format should be used? I have no expertise whatsoever in
>>> that domain, so i'll let people who do talk.
>>> In his talks, Douglas Crockford mentions that the fact that JS has one
>>> number type is a good thing, so, maybe that choosing a unique
>>> replacement should be the best thing.
>>> Alternatively, the directive could be "parametrized" with number formats
>>> ("use number IEEE XYZ").
>> You will not get premature standardization past TC39 on this front. Our agreement when decimal missed ES3.1 in 2008 fall (the Kona meeting) was to work on value types so that new numeric types could be implemented in-language.
> That's an interesting piece of information. If I understand it
> correctly, it would imply that the expression "0.1+0.2" will never be
> equal to 0.3 in a JavaScript program (even with any sort of opt-in
> (@type versionning, directive, pragma...)).

That last "even with" parenthetical does not follow from what I wrote. Clearly one could opt in via

  <script type="application/ecmascript;version=decimal_nirvana_arrived">
    alert(0.1 + 0.2);
  </script>

and have world peace, cats and dogs living together, etc.

Seriously, my point was simply that TC39 will not decide on one new and more precise number type, rewrite the spec to use it (forking the old spec for compatibility with downrev scripts), and throw the "big red switch" by committee fiat.


> The best thing i can think
> of is wrapping my numbers to create values out of them. So,
> "wrap(0.1)+wrap(0.2)" can be equal to 0.3.
> (Tell me if there is a part i misunderstand or forget)

No, you're overreacting still ;-).

Even without a widely-scoped version selector or pragma, we want value types to support usable literals. For decimal, this means the 'm' suffix, after C# (m for "money", I'm told):

    alert(0.1m + 0.2m);

and of course + would work with the decimal value type's appropriate operator methods.


> Consequently, in order to have accurate arithmetic, i can:
> - wrap by hand my numbers (which doesn't scale well if i want to apply
> it to current JS files)
> - use a compiler/transpiler to wrap numbers automatically.
> But it would be hard to tell automatically which numbers should be
> wrapped and which should not. Wrapping all numbers could have annoying
> performance issues.
> - use some combining approach (interactive tool, heuristics...)
> 
> Also, from what i understand, typeof( wrap(0.1)+wrap(0.2) ) could not be
> "number".

That's right, value types could introduce new typeof-types, e.g., "decimal", "complex", "rational".


> A directive may not solve every problem caused by numeric types, but it
> would solve the problem of making current code work with precise
> arithmetic at a small cost (from the developer side).

How? You didn't say anything about the runtime semantics, even if we assume a more precise floating point format (say, IEEE754r decimal).


>> Today, that means module-encapsulated implementations, and (for usability) value types for operator syntax. But value types as you note are not in ES6.
>> 
>> Sketching a directive solves *nothing*. Would Math.sin change to use the new numeric type? If not, why not? If so, how?
> In ES5, Math.sin and most other Math functions are specified as "Returns
> an *implementation-dependent* approximation to the sine of x.", so I do
> not consider this as a concern. Regardless of underlying number
> representation, Math.sin returns a approximation of the sine of the
> argument.

That doesn't say how the one Math.sin function object shared by scripts that |use precise arithmetic| with scripts that use IEEE double decides which representation to use: decimal or double.

Also, the approximation allowed by the spec there does indeed vary among implementations, but the domain of the result is IEEE754 binary double. Not extended 80-bit binary double, not 754r decimal, not any wider format.


> I guess I should return the question: how would Math.sin behave with a
> value proxy?

One idea that we've discussed: reverse-delegate to the argument if it is a value proxy, otherwise convert to double as today.


> This question stands for any in-language representation.
> Since the numeric type is defined in-language, there is not the
> "regardless of underlying numeric representation" trap that i used above
> since the representation is not /under/lying.
> Will in-language values have to define a behavior for each Math
> function?

No, but they might for value types. Math.sin(x) for a value type instance x might delegate to x.reverse_sin() or equivalent.


> What when new Math functions are added?

The reverse delegation could funnel through a single method taking the name of the Math method in question, e.g. Details to be decided, but we definitely talked about this in past value types meetings, and ES4 went so far as to consider multi-methods for operators on new (value-ish) types.


> Will in-language values have to provide a "default IEEE double value" to
> work with native Math functions?

No, that seems neither necessary nor a good idea.


>> Worse, what about calls from within the extent of the "use precise arithmetic" pragma to outside of that scope? Of course we can't have dynamic scope, but what *types* of parameters pass across the barrier? Do precise numbers coerce with loss of precision to IEEE double?
> I agree that this is an issue.
> But a first answer would be that if you really care about precision, you
> will be careful of using only precise arithmetic scripts to not be
> bothered by the dual-mode and issues that could come with it (loss of
> precision).

Programmers will mess this up. All the time.

And it's not just about small step-wise precision differences. Decimal and double are not just imprecisely different, the different radixes result in different rounding even for common "dollars and cents" additions and subtractions. Finally, performance will differ in ways that matter, and which cannot be concealed.


> If you do not care very much about precision, either you do not use the
> directive or being coerced to non-precise numbers is not an issue for
> you, so coercion sounds like a good compromise

Implicit coercion in JS is one of the big remaining warts we can't easily fix in Harmony. Let's not add more.


>> Rather than bikeshed directive syntax, which solves nothing, we need more design and implementation work on value types, specifically new numeric types.
>> 
>> Worse, we may need the "big red switch" on the side of a clique of addressable global objects, so one can opt *everything* into the precise numeric type, including Math, Number, canvas, WebGL, etc. And doing so may break (or at least terribly slow down) canvas, WebGL, etc. So the big red switch may be a pipe dream.
>> 
>> If so, then the problem becomes how to use different numeric types conveniently. Per our TC39 agreement
> Are there notes of the 2008 Kona meeting?
> What were the arguments leading to this agreement?

On es-discuss.

https://mail.mozilla.org/pipermail/es-discuss/2008-November/thread.html#8105

and look for Kona.

/be


More information about the es-discuss mailing list