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.