We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Decimal `==` is not value equality
almirsarajcic
Decimal.new("1.0") and Decimal.new("1.00") represent the same number, but Elixir’s == says they are different:
a = Decimal.new("1.0")
b = Decimal.new("1.00")
c = Decimal.new("1.0")
a == b
# => false
a == c
# => true
Decimal.equal?(a, b)
# => true
Decimal.compare(a, b)
# => :eq
a in [b]
# => false
Enum.uniq([a, b])
# => [Decimal.new("1.0"), Decimal.new("1.00")]
a == c is true because c was built from the exact same string as a, so the two structs are field-for-field identical. a == b is false even though a and b are mathematically equal, because they carry different precision.
Why
Decimal is a plain struct:
%Decimal{sign: 1, coef: 10, exp: -1}
== in Elixir doesn’t know anything about decimal arithmetic. It falls back to Erlang term comparison, which compares the struct like any other map: by its fields. Decimal.new("1.0") stores coef: 10, exp: -1 (1.0 = 10 × 10⁻¹), while Decimal.new("1.00") stores coef: 100, exp: -2 (1.00 = 100 × 10⁻²). Same value, different coefficient and exponent, so the terms don’t match.
This is exactly why in, Enum.member?/2, and Enum.uniq/1 all misbehave — they use == under the hood. a in [b] is false, and Enum.uniq([a, b]) keeps both entries instead of collapsing them to one. The same trap applies to using a Decimal as a bare map key: %{a => :x, b => :y} keeps two entries, not one, because map key matching also uses term equality.
Enum.sort/1 doesn’t crash, but it doesn’t tie-break on numeric value either — it orders by the same struct-field comparison, so equal-valued decimals with different precision can still end up in a specific, coefficient-driven order rather than being genuinely tied.
Use the right function
Decimal.equal?/2for equality that respects numeric value, not representation.Decimal.compare/2for ordering — returns:lt,:eq, or:gt.Decimal.lt?/2,Decimal.gt?/2,Decimal.lte?/2,Decimal.gte?/2for direct comparisons.
None of these care whether the operands came from "1.0" or "1.00" — they compare the actual numbers.
This matters most for money and other exact-decimal fields loaded from a database or parsed from user input, where trailing zeros routinely differ between two values that should be treated as equal.
copied to clipboard