diff --git a/lib/decimal.ex b/lib/decimal.ex index b405079..004c205 100644 --- a/lib/decimal.ex +++ b/lib/decimal.ex @@ -2463,7 +2463,11 @@ defmodule Decimal do defp increment?(:down, _, _, _, _), do: false - defp increment?(:up, _, _, _, _), do: true + # Round-up is away from zero, but per the General Decimal Arithmetic spec + # an all-zero discarded part leaves the value unchanged (an exact value is + # never rounded up). Check the discarded digits like :ceiling/:floor do, + # rather than incrementing unconditionally. + defp increment?(:up, _, _, remain, sticky?), do: any_nonzero?(remain, sticky?) defp increment?(:ceiling, sign, _, remain, sticky?), do: sign == 1 and any_nonzero?(remain, sticky?) diff --git a/test/decimal/context_test.exs b/test/decimal/context_test.exs index 4e5a894..a42f42d 100644 --- a/test/decimal/context_test.exs +++ b/test/decimal/context_test.exs @@ -82,6 +82,17 @@ defmodule Decimal.ContextTest do assert Decimal.add(~d"0", ~d"-102") == d(-1, 11, 1) assert Decimal.add(~d"0", ~d"1.1") == d(1, 11, -1) end) + + # :up rounds away from zero only when a discarded digit is nonzero. An + # exact value whose dropped digits are all zero must be left unchanged + # (matches the General Decimal Arithmetic spec and Python's decimal). + Context.with(%Context{precision: 1, rounding: :up}, fn -> + assert Decimal.add(~d"0", ~d"9.0") == d(1, 9, 0) + assert Decimal.add(~d"0", ~d"-9.0") == d(-1, 9, 0) + assert Decimal.mult(~d"9.00", ~d"1") == d(1, 9, 0) + # a nonzero discarded digit still rounds up + assert Decimal.add(~d"0", ~d"3.1") == d(1, 4, 0) + end) end test "with_context/2: large exponent gap addition" do