ISO 60599 is precisely what it says on the cover: a standard for FLOATING-POINT arithmetic.
In fact it is a standard that covers both binary and decimal floating-point.
Decimal floats have all the quirks of binary floats and add some of their own.
There are standards for programming languages with binary and decimal fixed-point
arithmetic, notably COBOL, PL/I, SQL, and Ada. Several computers have had
instruction-set-level support for decimal arithmetic, including the VAX computers
of fond memory and the IBM System/360 series, whose latest incarnation, z/Series,
is still with us.
We can classify fixed-point systems in several ways. One way is
- bounds and scale are part of the type (COBOL, PL/I, SQL, Ada).
The size is known at compile time so variables can be stack-allocated or
statically allocated like bounded integers. Arithmetic operations are
loop-free. The scale adjustments needed for addition, subtraction, and
comparison are known at compile time.
- bounds are not part of the type but the scale is (this can be done easily
in any language with dependent types, like Agda and in fact Haskell and C++
are just powerful enough to do it). Values have to be heap-allocated, and
operations are not loop-free, but scale adjustments can be done at
compile time.
- scale is an independent part of the value. That is, a number is (n / 10**s)
with no restrictions on n or s so that multiple (n,s) pairs represent
numerically equal but distinguishable values.
- scale is a derived part of the value. That is, a number is still (n / 10**s)
but this is constrained in one way.
Alternative II: n is 0 and s is 0 or n is not divisible by 10.
Alternative I: s is 0 or (n is not zero, s > 0, and n is not divisible by 10).
Alternative I allows a fixed-point value that is integral to be that integer.
I came into this area by way of Smalltalk. I wanted to implement the ANSI Smalltalk
standard, and that includes a ScaledDecinal class. The standard appeals to the LIA
standard for the semantics, which is unfortunate, because no part of the LIA standard
has ever had anything to say about it, and the result is that different Smalltalk
systems do different things.
- One Smalltalk bases its decimal arithmetic on IBM/360 hardware: numbers have up to
31 decimal digits, a sign, and a scale.
- Two Smalltalk systems say “a ScaledDecimal number is really a rational number,
any rational number, even one like 22/7, and the scale has no influence on how
calculations are done but only on how values are printed”, which still has me shaking
my head in disbelief that anyone ever thought that was a good idea.
- My Smalltalk follows the independent-part-of-the-value model, which seems forced by
the ANSI interface, with the consequence that 0.00s2 and 0.000s3 are numerically equal
but do not behave the same way.
As for the Elixir Decimal you linked to,
- having THREE numbers in the representation instead of two seems wasteful;
worse than that, it creates a +0 vs -0 distinction, in the representation.
- the example
D.div(1, 3)
Decimal.new(“0.3333333333333333333333333333”)
came as a very nasty shock. There is simply no way to justify this.
If x and y are decimal numbers, then x/y is a rational number, NOT a fixed point
number, and if there is any coherent rule for how many decimal places the
quotient should have, nobody seems to have found one.
- Decimal has something called a “context” which is stored in the
process dictionary, meaning that its arithmetic is state-dependent ,
which is NOT something you want to have to include in your reasoning.
This is because Decimal is yet another FLOATING-POINT arithmetic.
Note that I am not saying that Decimal isn’t useful or that ISO 60559 isn’t useful.
What I’m saying is that they are floating-point systems and as such messy and
tricky to reason about.
Of course it depends on what calculations you need to reason about.
Addition, subtraction, negation, absolute value, sign, multiplication,
any of several variants of quotient and remainder to a specified precision,
even square and cube roots to a specified precision, and comparison,
these all fit exact scaled decimal arithmetic very well, especially using
alternative I. If you want trig functions, hyperbolic functions, Bessel functions,
gamma, beta, exponentials and logarithms etc, then you must be satisfied with
inexact approximations and decimal floats may be a good choice.
If you want financial calculations, where thanks to compound interest
many functions have exponential or logarithmic aspects, a hybrid
approach where calculations are either exact or have a degree of
approximation explicit in the function call, may be the right
approach.
As for currency exchange rates, it is always the case that you can
find a positive integer for each currency such that
Nx units of currency Cx convert to Ny units of currency Cy.
There is no reason to involve floating-point numbers.
(Why yes, my Smalltalk does have currency conversion support.
How ever did you guess?)