diff --git a/lib/decimal.ex b/lib/decimal.ex index dfbb67d..69e33a9 100644 --- a/lib/decimal.ex +++ b/lib/decimal.ex @@ -79,6 +79,9 @@ defmodule Decimal do | :rounded | :inexact + @type compare_result :: + :lt | :gt | :eq + @typedoc """ Rounding algorithm. @@ -302,6 +305,43 @@ defmodule Decimal do sub(decimal(num1), decimal(num2)) end + @doc """ + Compares two numbers numerically using a threshold. If the first number added + to the threshold is greater than the second number, and the first number + subtracted by the threshold is smaller than the second number, then the two + numbers are considered equal. + + ## Examples + + iex> Decimal.compare("1.1", 1, "0.2") + :eq + + iex> Decimal.compare("1.2", 1, "0.1") + :gt + + iex> Decimal.compare("1.0", "1.2", "0.1") + :lt + """ + @spec compare(decimal :: decimal(), decimal :: decimal(), threshold :: decimal()) :: + compare_result() + + def compare(_, _, %Decimal{sign: -1}), do: raise(Error, reason: "threshold cannot be negative") + + def compare(%Decimal{} = n1, %Decimal{} = n2, %Decimal{} = threshold) do + add_threshold = n1 |> Decimal.add(threshold) + sub_threshold = n1 |> Decimal.sub(threshold) + case1 = compare(add_threshold, n2) + case2 = compare(sub_threshold, n2) + + cond do + (case1 == :gt or case1 == :eq) and (case2 == :lt or case2 == :eq) -> :eq + case1 == :gt -> :gt + case2 == :lt -> :lt + end + end + + def compare(n1, n2, threshold), do: compare(decimal(n1), decimal(n2), decimal(threshold)) + @doc """ Compares two numbers numerically. If the first number is greater than the second `:gt` is returned, if less than `:lt` is returned, if both numbers are equal @@ -318,7 +358,7 @@ defmodule Decimal do :gt """ - @spec compare(decimal, decimal) :: :lt | :gt | :eq + @spec compare(decimal, decimal) :: compare_result() def compare(%Decimal{coef: :inf, sign: sign}, %Decimal{coef: :inf, sign: sign}), do: :eq @@ -446,6 +486,29 @@ defmodule Decimal do def eq?(_num1, %Decimal{coef: :NaN}), do: false def eq?(num1, num2), do: compare(num1, num2) == :eq + @doc """ + It compares the equality of two numbers. If the second number is within + the range of first - threshold and first + threshold, it returns true; + otherwise, it returns false. + + ## Examples + + iex> Decimal.eq?("1.0", 1, "0") + true + + iex> Decimal.eq?("1.2", 1, "0.1") + false + + iex> Decimal.eq?("1.2", 1, "0.2") + true + + iex> Decimal.eq?(1, -1, "0.0") + false + + """ + @spec eq?(decimal :: decimal(), decimal :: decimal(), thresrold :: decimal()) :: boolean() + def eq?(num1, num2, thresrold), do: compare(num1, num2, thresrold) == :eq + @doc """ Compares two numbers numerically and returns `true` if the first argument is greater than the second, otherwise `false`. If one the operands is a diff --git a/test/decimal_test.exs b/test/decimal_test.exs index 1651bc6..87d9a04 100644 --- a/test/decimal_test.exs +++ b/test/decimal_test.exs @@ -259,6 +259,24 @@ defmodule DecimalTest do end end + test "compare/3" do + assert Decimal.compare(~d"420.5", ~d"42e1", "0.5") == :eq + assert Decimal.compare(~d"420.5", ~d"42e1", "0.2") == :gt + + assert_raise Error, fn -> + Decimal.compare(~d"420.5", ~d"42e1", "-0.2") + end + + assert Decimal.compare(~d"1", ~d"0", "0") == :gt + + assert Decimal.compare(~d"-inf", ~d"inf", "100") == :lt + assert Decimal.compare(~d"inf", ~d"-inf", "0") == :gt + assert Decimal.compare(~d"0", ~d"inf", "1000000") == :lt + + assert Decimal.compare(~d"0.123", ~d"0", "0") == :gt + assert Decimal.compare(~d"0.123", ~d"0", "0.2") == :eq + end + test "equal?/2" do assert Decimal.equal?(~d"420", ~d"42e1") refute Decimal.equal?(~d"1", ~d"0") @@ -277,6 +295,16 @@ defmodule DecimalTest do refute Decimal.eq?(~d"1", ~d"nan") end + test "eq/3?" do + assert Decimal.eq?(~d"420", ~d"42e1", ~d"0") + assert Decimal.eq?(~d"1", ~d"0", ~d"1") + refute Decimal.eq?(~d"1", ~d"0", ~d"0") + + assert_raise Error, fn -> + Decimal.eq?(~d"nan", ~d"1", ~d"1") + end + end + test "gt?/2" do refute Decimal.gt?(~d"420", ~d"42e1") assert Decimal.gt?(~d"1", ~d"0")