lack of auto-normalization produces unexpected results
adrianomitre opened this issue · comments
Problem Statement
In general, numbers should be considered equal regardless of multiple possible exponential format representations. In particular, all zeroes (of same sign) should be equal. This holds in math, Ruby, Python, and floating-point units (despite being binary, not decimal), but not in Elixir's Decimal.
Elixir
# assuming you have added `{:decimal, "2.0.0-rc.0"}` to `mix.exs`
$ elixir -v
Erlang/OTP 23 [erts-11.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]
Elixir 1.10.4 (compiled with Erlang/OTP 23)
$ iex -S mix
Erlang/OTP 23 [erts-11.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]
Interactive Elixir (1.10.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Decimal.parse("0.0e+123") == Decimal.parse("0")
false
Ruby
$ ruby -v
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]
$ irb --simple-prompt -r bigdecimal
>> BigDecimal("0.0e+123") == BigDecimal("0")
=> true
Python
$ python3 -- version
Python 3.8.2
$ python3
Python 3.8.2 (default, Apr 27 2020, 15:53:34)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from decimal import *
>>> Decimal("0.0e+123") == Decimal("0")
True
IEEE Standard for Floating-Point Arithmetic (IEEE 754)
Some numbers may have several possible exponential format representations. For instance, if b = 10, and p = 7, then −12.345 can be represented by −12345×10−3, −123450×10−4, and −1234500×10−5. However, for most operations, such as arithmetic operations, the result (value) does not depend on the representation of the inputs.
Source: IEEE 754 - Formats - Representation and encoding in memory - Wikipedia
Proposed Solution
Normalize at the end of every operation (at the very least at the end of "constructors" such as parse and from_float).
PS: I am aware of the existence of eq? and equal? and they are not a solution to the problem of not following the principle of least astonishment (POLA).
This library preserves precision of numbers in all operations where it is possible, which is important in many applications of mathematics. You can see an example of this in the examples for the addition operation in the specification we follow [1] add(’12’, ’7.00’) ==> ’19.00’
. If we were to automatically normalize we can no longer preserve precision.
What you are highlighting is that we cannot override the built-in operators in Elixir. Convention in Elixir is to implement a compare/2
function [2] (and if you like eq?/2
, gt?/2
, lt?/2
, etc). Decimal follows this Elixir convention.
[1] http://speleotrove.com/decimal/daops.html#refaddsub
[2] https://hexdocs.pm/elixir/Enum.html#sort/2-sorting-structs