ericmj / decimal

Arbitrary precision decimal arithmetic

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

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to check if a decimal is zero, regardless of its precision?

GildedHonour opened this issue · comments

I have a function "round()" which rounds decimals to a certain precision. I want to check whether or not a decimal, regardless of a precision, is equal to zero. But only 2 decimals are zeros with equal precision, they're equal:


iex(6)> z1 = Decimal.new(0)       
#Decimal<0>

iex(7)> z2 = Decimal.new(0) |> Decimal.round(5)
#Decimal<0.00000>

iex(8)> z1 == z2
false

iex(9)> z3 = Decimal.new(0)                    
#Decimal<0>

iex(10)> z3 == z1
true

Although in this case z2 is zero, in my real project it comes from outside and it's not known whether or not it really happens to be a zero or a positive number with some precision. Therefore, Decimal.round(z2, 0) won't work correctly:

iex(14)> z22 = Decimal.new("0.05") |> Decimal.round(5)
#Decimal<0.05000>

iex(15)> Decimal.round(z22, 0)
#Decimal<0>

iex(16)> z2 == z22 # !! but they aren't
true

Use Decimal.compare(d1, d2) == :eq.

If you are on Decimal 1.x the function is called cmp/2.

Actually, == operator works fine too.

iex(22)> z22 = Decimal.new("0.05") |> Decimal.round(0)
#Decimal<0>


iex(29)> z222 = Decimal.round(z22, 0)              
#Decimal<0>

z2 == z222
false

I'm confused, what have I just asked and shown? :)

See this:


zero = Decimal.new(0)


iex(51)> Decimal.compare(zero, (Decimal.new("0.000") |> Decimal.round(0))) == :eq
false # why ???

iex(52)> Decimal.compare(zero, (Decimal.new("0.000") |> Decimal.round(5))) == :eq
false # why ????

iex(53)> Decimal.compare(Decimal.new(0), (Decimal.new("0.00001") |> Decimal.round(0))) == :eq
false # ok

sorry for confusion, on Decimal 1.x, use cmp/2 to compare. On Decimal 2.x we'll have compare/2. (on Decimal 1.x, compare/2 returns Decimal<-1> | Decimal<0> | Decimal<1>, and to instead return :lt | :eq | :gt, we're making a breaking change, hence Decimal 2.x)

iex(74)> Decimal.compare(zero, (Decimal.new("0.000") |> Decimal.round(0)))
#Decimal<0>     # 0 means equal?

iex(75)> Decimal.compare(zero, (Decimal.new("0.000") |> Decimal.round(5)))
#Decimal<0>      # 0 means equal?

iex(80)> Decimal.compare(zero, (Decimal.new("0.00001") |> Decimal.round(0)))          
#Decimal<0>  # but, this is wrong; probably

#Decimal<0> # 0 means equal?

yup

iex(80)> Decimal.compare(zero, (Decimal.new("0.00001") |> Decimal.round(0)))
#Decimal<0> # but, this is wrong; probably

it's correct and that's because:

iex> Decimal.new("0.00001") |> Decimal.round(0)
#Decimal<0>

You shouldn't use == to compare structs since it's usually incorrect. == does a structural comparison which will cause the decimals 1x10^2 to not compare equal to 10x10^1 even though they are equal numerically.

Elixir recently standardized on using the MyStruct.compare/2 function to compare structs semantically. Decimal is migrating to this standard in v2 with the Decimal.compare/2 function, but on decimal v1 you should still use Decimal.cmp/2 to compare decimal numbers.

With the new API there'll be an extra step to compare a result of compare() with Decimal 0, -1 or 1?

The new API will return :gt | :eq | :lt, so you don't have to compare with 0, -1, 1. See https://github.com/ericmj/decimal/blob/master/lib/decimal.ex#L289.