ES Decimal status
Sam Ruby
rubys at intertwingly.net
Fri Sep 12 12:00:47 PDT 2008
Preface:
Poor Pratap. He's folded in updates from me on the various
operators several times now. It has gone from Type(Result(n))
is Decimal, to Form(Result(n)) is Decimal, and now reads
Result(n) instanceof Decimal. I believe that the current
consensus is that the original formulation was the correct one
for equality operators, and nearly so for the remaining
operators. For arithmetic operators, the correct formulation is
closer to Type(GetValue(Result(n)) is Decimal, though in most
cases that simply means that the “if” check moves after the
existing steps that call GetValue, as it will be those results
that are checked.
Before proceeding with what hopefully is a final update, I want
to verify my assumptions on what the consensus is. Let me know
if you have any issues you would like me to add to the list.
Description of ES 3.1m decimal support
number and decimal are distinct primitive data types. The
former is based on IEEE 754 binary 64 floating point, the latter
is based on IEEE 754r decimal 128 floating point. Each
primitive data type is associated with a wrapper type, namely
Number and Decimal.
The wrapper classes, when invoked as a function, can be used to
convert between the two data types. Conversion from decimal to
number may involve the loss of precision, and may result in +/-
infinity if the exponent is outside of the range supported by
binary64. Conversion from number to decimal is precise and will
round trip.
Mixed mode operations, e.g. the addition of a decimal and a
number, proceed by first precisely converting the number to a
decimal, and then by performing a decimal addition operation.
The notable exception to this is strict equality which will
always return false if the types of the expressions involve
differ.
Binary Bitwise operators currently defined in terms of producing
32 bit integer results (e.g. shift), will continue to be defined
in terms of producing 32 bit integer results, i.e. will produce
a value of type number. Such numbers will always convert
exactly to decimal.
Additionally, there will be a number of named methods provided
on the Decimal class. Many of these methods will accept a math
context as a final argument. This will be a simple object,
which will be examined for a single property named “round”.
This will be compared against a number of predefined values, and
will be used to determine the rounding mode of the operation.
All named decimal methods will convert the default value of the
arguments directly to decimal, without first converting them to
a number. For example '1.1' would be treated as 1.1m when
passed as an argument to decimal.
While 128 bit decimal trigonometric functions could be proposed
at some point in the future, the existing Math libraries are to
be left untouched, and will continue to return numbers. This
means that the following Decimal equivalents will be required:
abs, min, max.
While extending base classes is generally frowned upon by many
in the ES community, Decimal should be extendable in precisely
the same ways as Number is in ES3.
In a nutshell, that's pretty much all there is too Decimal
support.
An implementation you can reference
As opposed to a “reference implementation” :-)
Often times loose prose such as this doesn't capture nuances as
well as code or test cases can. I have published a branch of
SpiderMonkey with the functionality described above, and those
that care to can review the source. This includes a number of
test cases, and I've published the results for quick reference.
I've also got a live web service which you can use to evaluate
simple expressions; for many, this might be easier than
downloading and building this code yourself.
http://code.intertwingly.net/public/hg/js-decimal/
http://intertwingly.net/stories/2008/09/12/estest.html
http://mail.mozilla.org/pipermail/es-discuss/2008-August/007261.html
Note the recent changes in the typeof, instanceOf, ==, and ===
tables.
I am particularly interested in any test cases people might like
to see added. Particularly ones that deal with object-ness or
prototype chains.
I have still yet to hear back from from the SquirrelFish team.
After Redmond, I'm considering adding Decimal support to V8.
Open questions
* Decimal literals. I believe that they would be a big win at
almost a negligible cost. But I fully recognize that no
backwards incompatible syntax change has been introduced into
the language to date.
* Selection of which named methods to include. Clearly functions
like quantum and compareTotal are needed. Not so clear are
functions that duplicate infix and prefix operators, such as add
and plus. I've heard arguments on both sides, and don't have a
strong opinion on this subject myself. After we verify
consensus on the broader approach described in this email, I can
present a list of potential candidate methods, grouped into
categories. We should be able to quickly sort through this
list. This effort could be done either on the mailing list or
in the F2F meeting in Redmond.
* Whether the named methods are to be static or instance methods.
I've heard arguments both ways, and could go either way on this.
Frankly, the infix operators capture the 80% use case. Instance
methods feel more OO, and some (like toString and valueOf) are
required anyway. Static methods may be more consistent with
Math.abs, and are asserted to be more suited to code generators
and optimization, though I must admit that I never could quite
follow this argument.
* Should there be a “use decimal”? To me, this feels like
something that could be added later. The fact that all 15 digit
integers convert exactly, as well as a few common fractions such
as .5 and .25, greatly reduces the need. The strategy of doing
precise conversions also will tend to highlight when mixed
operations occur, and will do so in a non-fatal way.
Approaches not selected:
* Decimal as a library only. Reason: usability concerns and
evolution of the language concerns. More specifically, the
definition of the behaviors of operators like === and + need to
be specified. Throwing exceptions would not merely be developer
unfriendly, it would likely be perceived as causing existing
libraries to break. And whatever was standardized would make
latter support for such operators to be a breaking change – at
the very least it would require an opt-in.
* Attempting to round binary 64 values to the nearest decimal 128
value. Such approaches are fragile (e.g., 1.2-1.1) and tends to
hide rather than reveal errors.
* Decimal being either a “subclass” or “form” of number. Turned
out to be too confusing, potentially breaking, and in general
larger in scope than simply providing a separate type and
wrapper class.
* Type(3m) being “object”. Reason: the only false values for
objects should be null, and it is highly desirable that 0.0m be
considered false.
* Methods naming based on Java's BigDecimal class. This was my
original approach as it was initially felt that IEEE 754 was too
low of level. This turned out not to be the case, and there are
some conflicts (e.g. valueOf is a static method with a single
argument on BigDecimal).
* Having a separate context class or storing application state in
Decimal class. The former is unnecessary namespace pollution in
a language with a simple syntax for object literals, and the
latter is against the policy of this working group.
- Sam Ruby
More information about the Es-discuss
mailing list