A summary of changes to ECMAScript decimal support, based on input from the committee in the last week or so:
Operations (e.g., addition, subtraction, multiplication, ...) between 64-bit binary floating point and 128-bit decimal floating point quantities will proceed by first converting the 64-bit binary floating point number to 128-bit decimal floating point to the maximum required precision and then proceeding with the operation. Previously the proposal was to convert the other way, resulting in a 64-bit binary result.
“strict” equality operations (===, !==) will no longer return false when comparing two decimal values with different precisions (e.g., 1.10m and 1.1m)
That concern goes both ways, doesn’t it? If typeof(1.1m) returns 'object' and people treat it like an object, stuff might break. If it returns 'decimal' and people treat it like an object (because it typically matches a too greedy else clause), stuff might break. Either way, you’re in danger of breaking existing code, so returning 'decimal' would be the best long-term decision imo.
An instance of Decimal is an object. For example, one can add properties and access them later. One can even use instanceof to determine if an object is an instance of the Decimal type.
I will confess that I have a mild preference for typeof 1m to be "decimal", but not to the point where I would want to create any cause for concern about backwards compatibility.
Decimal is an object type, analogous to Number in its spelling and constructor with prototype, but similar to RegExp in that it has literal syntax.
This is in contrast to 3.14, which is a primitive number (according to typeof) or double (ES4’s typename) type without constructor, which is automatically wrapped in Number “clothing” when used as an object, so that it appears to delegate to Number.prototype.
We don’t expect to add decimal (the primitive type) in ES-Harmony or ES3.1.
But should Decimal instances be mutable? Object clothing carries intrinsic cost. If Decimal instances could not be extended with ad-hoc properties, implementations could optimize better with simpler code.
More important, memoizing would be hard in the face of mutation, but easy if mutation were ruled out, with 1.1m === 1.1m falling out as a fast-path consequence (1.10m === 1.1m too, but that would take more work, similar to -0 === 0).
Asbjørn wrote “If typeof(1.1m) returns ‘object’ and people treat it like an object, stuff might break. What stuff? Stuff that expects all objects to be extensible, it seems to me. I’m not sure what other stuff there could be that might break. There will be a mutable Decimal.prototype.
With ES3.1’s Object.preventExtensions API, one cannot assume typeof x == "object” && x != null => x.adhoc = 42 will succeed. So stuff might break anyway, in ES3.1.
I admit it’s hard to reason about which typeof result is less likely to break existing code, but I lean toward “object”. I’d like objects to become capable, in some future edition, of being non-extensible, having operator multimethods, etc. Users might add Complex, Quaternion, Color, etc., numeric object subtypes.
Extending typeof for new would-be numeric types that are implemented as objects seems fruitless yet risky.
In general, we’ve found it hard to change typeof (in ES4 or ES3.1, e.g. so typeof null == “null") without breaking compatibility, so I’m also leaning toward "object” on this more general basis. It seems better to leave the co-domain of typeof exactly as it was in ES1-3.
But the committee has not figured this one out and come to agreement yet, so more arguments here could be helpful. Thanks,
If Decimal instances could not be extended with ad-hoc properties, implementations could optimize better with simpler code.
I’m not aware of any such places in Spidermonkey where such optimizations would be helpful. All Decimal operations take in one or two arguments, and produce a new decimal value. Such operations would simply ignore the ad-hoc properties. There may be some complication when passing decimal values to host provided methods: as the existence of such properties may preclude mapping decimal values to host primitive equivalents.
1.10m === 1.1m too, but that would take more work, similar to -0 === 0
Neither require more work. “strict” equality becomes a misnomer, but ultimately such operations map to Decimal.compare. This may seem confusing to people who believe that typeof(x)=="object" implies “strict” equality on such objects implies object identity.
I’m not sure what other stuff there could be that might break
This is a true edge case, but is !!0.0m true or false?
The only value in ES3 where typeof(x) is “object” and !!x is false is null. Is this something that is important to preserve? Or should !!0.0m be true?
I’m not aware of any such places in Spidermonkey where such optimizations would be helpful.
We could avoid making JSObjects for decimal literals and intermediates, using pseudo-booleans to refer to unboxed 128-bit decimals on the GC-heap. A primitive decimal type under the hood, so to speak.
Neither [1.1m === 1.1m or 1.10m === 1.1m] require more work.
If we memoize — intern all 1.1m to the same object, then the fast path is a pointer comparison — reference identity. Only if the (tagged, even) jsvals are not equal according to C or C++ would we need to call Decimal.compare.
I’m not talking about the connotations of “strict equality”, I’m talking again about implementation efficiency.
This is a true edge case, but is !!0.0m true or false?
typeof x == “object && !x => x == null is potentially important, you’re right. An argument for typeof 1.1m == "decimal”. Which could break other code that exhausts typeof results and throws or otherwise fails on a new result string such as “decimal”.
I don’t think anything in the spec should preclude memoizing, but I’m not (yet) convinced that at an implementation level it is worth pursuing. “Under the hood” with SpiderMonkey, I’ve tried to model the “primitiveness” of Decimal against what is done for Binary64 quantities; but I may have missed something.
Clearly ES has a long life, hopefully Decimal is not the only “potentially memoizable, potentially internally primitive” data type. Do we want to forever “seal” the range of typeof? On the other hand, !!(new Number(0)) is false, so perhaps !!(new Decimal(0)) should be too; even if (new Decimal(0)) should memoize to the same object as 0.0m.