From aa3dae5e11a17f419e3f6dab899542219b756698 Mon Sep 17 00:00:00 2001 From: "karlo.smid" Date: Thu, 2 May 2024 13:02:19 +0200 Subject: [PATCH] feat: check dbl_min_max on Decimal.new method --- lib/decimal.ex | 62 ++++++++++++++++++++++++++++++++++++------ lib/decimal/context.ex | 2 +- test/decimal_test.exs | 42 +++++++++++++++++++++++++++- 3 files changed, 96 insertions(+), 10 deletions(-) diff --git a/lib/decimal.ex b/lib/decimal.ex index dfbb67d..93c930a 100644 --- a/lib/decimal.ex +++ b/lib/decimal.ex @@ -1257,20 +1257,32 @@ defmodule Decimal do iex> Decimal.new("3.14") Decimal.new("3.14") + iex> Decimal.new("1.79769313486231581e308") + ** (Decimal.Error) : number bigger than DBL_MAX: Decimal.new("1.79769313486231581E+308") + + iex> Decimal.new("2.22507385850720139e-308") + ** (Decimal.Error) : number smaller than DBL_MIN: Decimal.new("2.22507385850720139E-308") + """ @spec new(decimal) :: t def new(%Decimal{sign: sign, coef: coef, exp: exp} = num) when sign in [1, -1] and ((is_integer(coef) and coef >= 0) or coef in [:NaN, :inf]) and - is_integer(exp), - do: num + is_integer(exp) do + check_dbl_min_max(num) + end - def new(int) when is_integer(int), - do: %Decimal{sign: if(int < 0, do: -1, else: 1), coef: Kernel.abs(int)} + def new(int) when is_integer(int) do + num = %Decimal{sign: if(int < 0, do: -1, else: 1), coef: Kernel.abs(int)} + check_dbl_min_max(num) + end def new(binary) when is_binary(binary) do case parse(binary) do - {decimal, ""} -> decimal - _ -> raise Error, reason: "number parsing syntax: #{inspect(binary)}" + {decimal, ""} -> + check_dbl_min_max(decimal) + + _ -> + raise Error, reason: "number parsing syntax: #{inspect(binary)}" end end @@ -1290,8 +1302,10 @@ defmodule Decimal do @spec new(sign :: 1 | -1, coef :: non_neg_integer | :NaN | :inf, exp :: integer) :: t def new(sign, coef, exp) when sign in [1, -1] and ((is_integer(coef) and coef >= 0) or coef in [:NaN, :inf]) and - is_integer(exp), - do: %Decimal{sign: sign, coef: coef, exp: exp} + is_integer(exp) do + num = %Decimal{sign: sign, coef: coef, exp: exp} + check_dbl_min_max(num) + end @doc """ Creates a new decimal number from a floating point number. @@ -2014,6 +2028,38 @@ defmodule Decimal do defp fix_float_exp([], result), do: :lists.reverse(result) + defp check_dbl_min_max(%Decimal{coef: :inf} = infinity), do: infinity + + defp check_dbl_min_max(%Decimal{sign: 1} = num) do + cond do + Decimal.gt?(num, dbl_max(1)) -> + raise Error, reason: "number bigger than DBL_MAX: #{inspect(num)}" + + Decimal.gt?(num, zero(1)) and Decimal.lt?(num, dbl_min(1)) -> + raise Error, reason: "number smaller than DBL_MIN: #{inspect(num)}" + + true -> + num + end + end + + defp check_dbl_min_max(num) do + cond do + Decimal.lt?(num, dbl_max(-1)) -> + raise Error, reason: "negative number smaller than DBL_MAX: #{inspect(num)}" + + Decimal.lt?(num, zero(-1)) and Decimal.gt?(num, dbl_min(-1)) -> + raise Error, reason: "negative number bigger than DBL_MIN: #{inspect(num)}" + + true -> + num + end + end + + def dbl_min(sign), do: %Decimal{sign: sign, coef: 22_250_738_585_072_014, exp: -324} + def zero(sign), do: %Decimal{sign: sign, coef: 0, exp: 0} + def dbl_max(sign), do: %Decimal{sign: sign, coef: 17_976_931_348_623_158, exp: 292} + if Version.compare(System.version(), "1.3.0") == :lt do defp integer_to_charlist(string), do: Integer.to_char_list(string) else diff --git a/lib/decimal/context.ex b/lib/decimal/context.ex index 0201eb2..c1c85be 100644 --- a/lib/decimal/context.ex +++ b/lib/decimal/context.ex @@ -84,7 +84,7 @@ defmodule Decimal.Context do Runs function with given context. """ doc_since("1.9.0") - @spec with(t(), (() -> x)) :: x when x: var + @spec with(t(), (-> x)) :: x when x: var def with(%Context{} = context, fun) when is_function(fun, 0) do old = Process.put(@context_key, context) diff --git a/test/decimal_test.exs b/test/decimal_test.exs index 1651bc6..b575e91 100644 --- a/test/decimal_test.exs +++ b/test/decimal_test.exs @@ -248,7 +248,9 @@ defmodule DecimalTest do assert Decimal.compare("Inf", "Inf") == :eq - assert Decimal.compare(~d"5e10000000000", ~d"0") == :gt + assert_raise Decimal.Error, + ": number bigger than DBL_MAX: Decimal.new(\"5E+10000000000\")", + fn -> Decimal.compare(~d"5e10000000000", ~d"0") end assert_raise Error, fn -> Decimal.compare(~d"nan", ~d"0") @@ -906,4 +908,42 @@ defmodule DecimalTest do Decimal.sqrt(Decimal.new(d(3, 1, -1))) end end + + test "max_min_dbl" do + assert Decimal.new("1.7976931348623158e308") == Decimal.dbl_max(1) + assert Decimal.new("-1.7976931348623158e308") == Decimal.dbl_max(-1) + + assert_raise Decimal.Error, + ": number bigger than DBL_MAX: Decimal.new(\"1.79769313486231581E+308\")", + fn -> Decimal.new("1.79769313486231581e308") end + + assert_raise Decimal.Error, + ": negative number smaller than DBL_MAX: Decimal.new(\"-1.79769313486231581E+308\")", + fn -> Decimal.new("-1.79769313486231581e308") end + + assert Decimal.new("1.79769313486231579e308") == Decimal.new("1.79769313486231579E+308") + assert Decimal.new("-1.79769313486231579e308") == Decimal.new("-1.79769313486231579E+308") + + assert Decimal.new("2.2250738585072014e-308") == Decimal.dbl_min(1) + assert Decimal.new("-2.2250738585072014e-308") == Decimal.dbl_min(-1) + + assert_raise Decimal.Error, + ": number smaller than DBL_MIN: Decimal.new(\"2.22507385850720139E-308\")", + fn -> Decimal.new("2.22507385850720139e-308") end + + assert_raise Decimal.Error, + ": negative number bigger than DBL_MIN: Decimal.new(\"-2.22507385850720139E-308\")", + fn -> Decimal.new("-2.22507385850720139e-308") end + + assert Decimal.new("2.22507385850720141e-308") == Decimal.new("2.22507385850720141E-308") + assert Decimal.new("-2.22507385850720141e-308") == Decimal.new("-2.22507385850720141E-308") + + assert_raise Decimal.Error, + ": number bigger than DBL_MAX: Decimal.new(\"9.999999999999999999E+1000000000000000000000017\")", + fn -> Decimal.new("9999999999999999999e999999999999999999999999") end + + assert_raise Decimal.Error, + ": number smaller than DBL_MIN: Decimal.new(\"9.9999999999999E-999999999999999999999986\")", + fn -> Decimal.new("99999999999999e-999999999999999999999999") end + end end