-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Details of specifying Decimal numbers and algorithms #122
Comments
I'd suggest representing Decimal128 values and algorithms analogously to how we represent Number and BigInt values. FormatA Decimal128 value would be one of the following:
Some definitions:
CohortThe cohort of a Decimal128 value is the value without the q. Specifically:
The use of cohorts is pervasive in simplifying specifications of spec algorithm steps, the bulk of which do not depend on q. Exponent and SignificandEvery finite nonzero Decimal128 value «v, q»𝔻 has an exponent and a significand. The exponent is the unique integer e and the significand is the unique real number s that satisfy v = s × 10e and 1 ≤ |s| < 10. Normalized and Denormalized ValuesThe exponent e of a finite nonzero Decimal128 value will always be in the range 6144 ≥ e ≥ -6176.
This corresponds to the IEEE notion of normalized and denormalized values. Denormalized values have progressively limited resolution as they approach zero. Truncated ExponentRounding behavior of denormalized Decimal128 values diverges from that of normalized Decimal128 values. For the purposes of describing rounding algorithms it's useful to define the notion of a truncated exponent te of a finite Decimal128 value.
Given this, we can define the scaled significand ss of a finite Decimal128 value as:
ss will always be an integer with an absolute value less than 1034. The main importance of a scaled significand ss is in rounding behavior, for which IEEE 754 arithmetic algorithms make different choices depending on whether ss is odd or even. The notion of scaled significands should not be exposed to users. Edits
|
Some tentative items from the discussion on Tuesday, 16 April 2024. Please add comments if I missed anything. Overall
Specification Techniques
Terminology
Quanta
Naming
Formats
Conversions
|
Here's my summary of the discussion on Wednesday, 17 April 2024. Please add comments if I missed anything. Overall
Rounding Modes and Implementability
This disparity leads to questions of both nomenclature and implementability:
User Operations on Mantissa and ExponentSome users will want to compose a Decimal out of a mantissa and exponent and decompose a Decimal back into a mantissa and exponent. That's desirable in various applications and we should make sure that how to do this correctly is readily apparent. Some options:
Explicit ConversionsDecimal ↔ StringDecimal → String
String → Decimal
Decimal ↔ NumberWe discussed those yesterday and settled on the choices:
Decimal ↔ BigInt
Conclusion
|
* Define a Makefile for basic editorial work * Add a number of suggested definitions Relates to #122.
* Fix how we refer to exceptions. + For `Decimal128.prototype.round`, use `mark` to mark an incomplete algorithm. + Use `*this* value` rather than `**this** value`. Part of #122
(This issue got unintentionally closed; reopening it.) |
…ion, and remainder (#143) * Add `toFixed`, `toPrecision`, and `toExponential` * Define AO to massage a potential Decimal128 value to an actual one * Say that Decimal128 values aren't ECMAScript language values * Inline AO used only in the constructor * Fix references to undefined variables and mark an in-progres spot * Flesh out `round` method * Fix specification for exponent in `divide` * Add checks for rounding mode * Flesh out constructor * Add brand checks * Add links * Add reference for rounding * Add more IEEE 754 references * Use "Otherwise" * Add note about how we convert from Number relates to #122
EnsureDecimal128Value is the main nexus of issues in the June 2024 spec. Here's a sketch of how I'd recommend fixing things. We should probably come up with better names for some of these sets and functions, but the general idea is:
Code that calls these should work with cohorts and quantums separately. Arithmetic and logic should call the cohort method and do arithmetic cases with that value. A recurring problem in the current spec is the incorrect querying of q in Decimal128 values «v, q». The only places where q should be used are:
Any other use of q is a bug. This includes trying to use q in the default toString or toExponential. Edit: Added third parameter to cover inexact cases to PickQuantum. |
I recommend providing the following three user methods for working with exponents and significands. I based this design on recommendations in the IEEE754 standard as well as interoperability considerations. In particular, I designed these so that one can feed the output of one method into another without having to check for special cases such as NaNs, infinities, or zeroes. The nomenclature is just a strawman. We might find better names for these. ExponentThe exponent user method returns a Number that represents the base-10 exponent of a Decimal128 value d. It has the following cases:
This logic is based on the IEEE754 logB method. QuantumExponentThe quantumExponent user method takes a Decimal128 value d and returns its quantum q as a Number if available. It has the following cases:
Why not call this quantum? I would, but the IEEE spec uses the word quantum to mean 10d.quantumExponent. One can easily generate that using the scale method, as illustrated later. ScaleThe scale (maybe we should call it scale10?) user method scales a Decimal128 value's exponent by a given Number n, effectively multiplying it by 10n but without the danger of intermediate overflows. It takes a Decimal128 value d and a Number value n, which must be an integeral Number or one of {NaN𝔽, +∞𝔽, -∞𝔽}. It has the following cases:
ApplicationsWith these three methods we can readily do all of the common operations on exponents and significands. The scale method lets us construct a Decimal128 value out of a significand and an exponent. For breaking Decimal128 values into parts we have a lot of useful choices:
|
…m rounding, as well as API additions (#159) * Get started using the suggested `PickQuantum` AO As suggested in #122 * Fix and simplify .add, .subtract and .multiply There were a bunch of errors with handling of zeroes and infinities. This commit fixes those, and reduces the number of branches to make the algorithm shorter and simpler to follow. * Only define `cohort(x)` for finite numbers While we also defined the cohort of +inf/-inf/NaN, those values were never passed to `cohort()` and thus all the algorithms (correctly) assume that the result of `cohort` is either a zero or a finite mathematical value. While this is true, it is difficult to read them because the type definition of `cohort` says that it can also return `+inf/-inf/NaN`. This updates the type definition to match usage, rather than unnecessarily accepting more inputs. There is one case in .add and friends where we are actually passing +inf/-inf/NaN to cohort(), but that is just a bug since we then do not handle the case in which it returns +inf/-inf/NaN. * Define rounding and use it, along with pick-quantum, throughout * Ensure .abs returns a non-negative value * Fix `isFinite` * Remove references to "rational number" (Of course, an alternative would be to define the term.) * Handle second argument NaN in .compare * round: Do `RequireInternalSlot` before validating arguments * Unconditionally throw in `valueOf` Don't even ensure that we have the right internal slot. * Set variable rather than return early We need to ensure that `new Number(...)` returns a new object, rather than a primitive. * Ensure variable is defined * Use the term "real number" * Use `abs(...)` rather than `|...|` * Use assertion rather than note * Add <sub>𝔻</sub> after pairs * Fix return type of ToIntlMathematicalValue We don't return a decimal. * Handle returning 0 from remainder * Qualify argument of rounding (shouldn't be zero) * Use proper IDs * When rounding, enumerate 34 significant digits and consult the 35th * Show where we overflow to infinity in rounding * Handle denormalized values in rounding * Add IDs for normalized/denormalized * Handle underflow with increasingly coarse-grained approximations * Handle the possibility that rounding returns 0 * Fix busted <sub>D</sub> and calls to `PickQuantum` * In scale10, allow NaN and infinity as argument Throw only if we are given a non-integer finite argument. * Return a +/-0 from RoundToDecimal128Domain * Fix quantum of division * Define a separate AO for rounding positive numbers * Embed utility rounding AO inside the other one Also, fix a positive infinity sneaking out. --------- Co-authored-by: Nicolò Ribaudo <[email protected]> Co-authored-by: Nicolò Ribaudo <[email protected]>
Regarding the proposed notation for decimals, I suggest using something like |
Earlier I mentioned the spec function RoundDecimal128. Here's how I recommend implementing it. RoundDecimal128(v, roundingMode) that takes v ∈ ℝ and one of the rounding modes. It rounds v to one of the Decimal128 cohorts per the rounding mode and produces a result that is a member of the set FiniteDecimalCohorts ∪ {+∞𝔻, -∞𝔻} — note that there's no way for rounding to produce a NaN𝔻. Don't pass ±0𝔻 as the first parameter to this function; instead, handle those cases explicitly in the calling code because IEEE treats negative zeros specially. On the other hand, the mathematical value 0 is perfectly fine as the first parameter of this function and outputs the cohort +0𝔻.
The spec function ApplyRoundingModeToPositive(m, roundingMode) takes a positive number m ∈ ℝ and one of the rounding modes and returns an integer:
|
This is how I'd recommend implementing the spec function PickQuantum mentioned earlier. The spec function PickQuantum(d, v, qPreferred) takes d ∈ DecimalCohorts, v ∈ ℝ, and qPreferred ∈ ℤ and produces a Decimal128 value in cohort d. If d is an exact Decimal128 cohort representation of v, PickQuantum picks the quantum as close as possible to qPreferred; if d is not an exact Decimal128 cohort representation of v, PickQuantum picks the lowest possible quantum.
PickQuantum is often (but not always) used together with RoundDecimal128, so it's also worthwhile to define a helper spec function that combines the two: The spec function RoundAndPickQuantum(v, roundingMode, qPreferred) takes v ∈ ℝ, one of the rounding modes, and qPreferred ∈ ℤ and produces a Decimal128 value as follows:
|
This has been implemented as suggested. Thank you! |
Can we set up a video meeting to discuss how to formalize Decimal numbers and the algorithms involving those in the spec? I'd like to ensure that the spec is correct, clear, unambiguous, and easy to read and, if desired, am willing to provide extensive help to achieve that. I'm available next week Tue-Thu.
The text was updated successfully, but these errors were encountered: