ericmj / decimal

Arbitrary precision decimal arithmetic

Home Page:https://hexdocs.pm/decimal/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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