-module(bigdecimal). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/bigdecimal.gleam"). -export([unscaled_value/1, scale/1, zero/0, one/0, absolute_value/1, signum/1, precision/1, ulp/1, negate/1, truncate_to_bigint/1, rescale/3, add/2, sum/1, subtract/2, compare/2, clamp/3, multiply/2, product/1, trim_zeros/1, divide/3, remainder/2, power/2, from_string/1, from_float/1]). -export_type([big_decimal/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -opaque big_decimal() :: {big_decimal, bigi:big_int(), integer()}. -file("src/bigdecimal.gleam", 21). -spec unscaled_value(big_decimal()) -> bigi:big_int(). unscaled_value(Value) -> {big_decimal, Unscaled_value, _} = Value, Unscaled_value. -file("src/bigdecimal.gleam", 26). -spec scale(big_decimal()) -> integer(). scale(Value) -> {big_decimal, _, Scale} = Value, Scale. -file("src/bigdecimal.gleam", 31). -spec zero() -> big_decimal(). zero() -> {big_decimal, bigi_ffi:from(0), 0}. -file("src/bigdecimal.gleam", 35). -spec one() -> big_decimal(). one() -> {big_decimal, bigi_ffi:from(1), 0}. -file("src/bigdecimal.gleam", 53). -spec absolute_value(big_decimal()) -> big_decimal(). absolute_value(Value) -> {big_decimal, erlang:abs(unscaled_value(Value)), scale(Value)}. -file("src/bigdecimal.gleam", 60). ?DOC( " Sign function. Returns +1 if the value is positive,\n" " -1 if the value is negative, 0 if the value is zero.\n" ). -spec signum(big_decimal()) -> integer(). signum(Value) -> _pipe = unscaled_value(Value), _pipe@1 = bigi_ffi:compare(_pipe, bigi_ffi:zero()), gleam@order:to_int(_pipe@1). -file("src/bigdecimal.gleam", 41). ?DOC(" The number of digits in the unscaled value.\n"). -spec precision(big_decimal()) -> integer(). precision(Value) -> String_length = begin _pipe = unscaled_value(Value), _pipe@1 = erlang:integer_to_binary(_pipe), erlang:byte_size(_pipe@1) end, case signum(Value) of -1 -> String_length - 1; _ -> String_length end. -file("src/bigdecimal.gleam", 68). ?DOC(" Returns the unit of least precision of this `BigDecimal`.\n"). -spec ulp(big_decimal()) -> big_decimal(). ulp(Value) -> {big_decimal, bigi_ffi:from(1), scale(Value)}. -file("src/bigdecimal.gleam", 72). -spec negate(big_decimal()) -> big_decimal(). negate(Value) -> {big_decimal, bigi_ffi:negate(unscaled_value(Value)), scale(Value)}. -file("src/bigdecimal.gleam", 210). -spec is_even(bigi:big_int()) -> boolean(). is_even(Value) -> case begin _pipe = bigi_ffi:modulo(Value, bigi_ffi:from(2)), bigi_ffi:to(_pipe) end of {ok, 0} -> true; _ -> false end. -file("src/bigdecimal.gleam", 220). -spec get_magnitude(bigi:big_int(), integer()) -> integer(). get_magnitude(Value, Magnitude) -> Quotient = bigi_ffi:divide(Value, bigi_ffi:from(10)), case bigi_ffi:compare(Quotient, bigi_ffi:zero()) of lt -> get_magnitude(Quotient, Magnitude + 1); gt -> get_magnitude(Quotient, Magnitude + 1); eq -> Magnitude end. -file("src/bigdecimal.gleam", 274). -spec multiply_power_of_ten(bigi:big_int(), integer()) -> bigi:big_int(). multiply_power_of_ten(Value, N) -> case N >= 0 of true -> nil; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"bigdecimal"/utf8>>, function => <<"multiply_power_of_ten"/utf8>>, line => 275, value => _assert_fail, start => 6706, 'end' => 6730, pattern_start => 6717, pattern_end => 6721}) end, _pipe = bigi_ffi:from(10), _pipe@1 = bigi_ffi:power(_pipe, bigi_ffi:from(N)), _pipe@2 = gleam@result:lazy_unwrap(_pipe@1, fun() -> bigi_ffi:from(0) end), bigi_ffi:multiply(_pipe@2, Value). -file("src/bigdecimal.gleam", 84). ?DOC(" Truncate the `BigDecimal` to a `BigInt`.\n"). -spec truncate_to_bigint(big_decimal()) -> bigi:big_int(). truncate_to_bigint(Value) -> _pipe = unscaled_value(Value), bigi_ffi:divide( _pipe, multiply_power_of_ten(bigi_ffi:from(1), scale(Value)) ). -file("src/bigdecimal.gleam", 130). -spec add_zeros(big_decimal(), integer(), integer()) -> big_decimal(). add_zeros(Value, New_scale, Scale_diff) -> _pipe = unscaled_value(Value), _pipe@1 = multiply_power_of_ten(_pipe, Scale_diff), {big_decimal, _pipe@1, New_scale}. -file("src/bigdecimal.gleam", 182). -spec calculate_adjustment_for_half_round( bigi:big_int(), integer(), bigi:big_int(), bigi:big_int(), bigdecimal@rounding:rounding_mode() ) -> bigi:big_int(). calculate_adjustment_for_half_round( Unscaled_value, Sign, Quotient, Power_of_ten, Rounding ) -> Remainder = begin _pipe = bigi_ffi:multiply(Quotient, Power_of_ten), _pipe@1 = bigi_ffi:subtract(_pipe, Unscaled_value), erlang:abs(_pipe@1) end, To_compare = begin _pipe@2 = get_magnitude(Remainder, 0), multiply_power_of_ten(bigi_ffi:from(5), _pipe@2) end, case {bigi_ffi:compare(Remainder, To_compare), Rounding} of {eq, half_even} -> case is_even(Quotient) of true -> bigi_ffi:zero(); false -> bigi_ffi:from(Sign) end; {eq, half_up} -> bigi_ffi:from(Sign); {gt, _} -> bigi_ffi:from(Sign); {_, _} -> bigi_ffi:zero() end. -file("src/bigdecimal.gleam", 156). -spec adjust_unscaled_value_for_rounding( bigi:big_int(), integer(), bigi:big_int(), bigi:big_int(), bigdecimal@rounding:rounding_mode() ) -> bigi:big_int(). adjust_unscaled_value_for_rounding( Unscaled_value, Quotient_sign, Quotient, Divisor, Rounding ) -> Adjustment = case {Rounding, Quotient_sign} of {floor, S} when S < 0 -> bigi_ffi:from(-1); {ceiling, S@1} when S@1 > 0 -> bigi_ffi:from(1); {up, S@2} -> bigi_ffi:from(S@2); {half_up, S@3} when S@3 =/= 0 -> calculate_adjustment_for_half_round( Unscaled_value, S@3, Quotient, Divisor, Rounding ); {half_down, S@3} when S@3 =/= 0 -> calculate_adjustment_for_half_round( Unscaled_value, S@3, Quotient, Divisor, Rounding ); {half_even, S@3} when S@3 =/= 0 -> calculate_adjustment_for_half_round( Unscaled_value, S@3, Quotient, Divisor, Rounding ); {_, _} -> bigi_ffi:zero() end, _pipe = Quotient, bigi_ffi:add(_pipe, Adjustment). -file("src/bigdecimal.gleam", 136). -spec rescale_with_rounding( big_decimal(), integer(), integer(), bigdecimal@rounding:rounding_mode() ) -> big_decimal(). rescale_with_rounding(Value, New_scale, Scale_diff, Rounding) -> Tens = multiply_power_of_ten(bigi_ffi:from(1), Scale_diff), Unscaled = unscaled_value(Value), Quotient = bigi_ffi:divide(Unscaled, Tens), _pipe = adjust_unscaled_value_for_rounding( Unscaled, signum(Value), Quotient, Tens, Rounding ), {big_decimal, _pipe, New_scale}. -file("src/bigdecimal.gleam", 117). -spec rescale(big_decimal(), integer(), bigdecimal@rounding:rounding_mode()) -> big_decimal(). rescale(Value, New_scale, Rounding) -> Scale_diff = scale(Value) - New_scale, case gleam@int:compare(Scale_diff, 0) of eq -> Value; lt -> add_zeros(Value, New_scale, - Scale_diff); gt -> rescale_with_rounding(Value, New_scale, Scale_diff, Rounding) end. -file("src/bigdecimal.gleam", 263). -spec scale_adjusted_add(big_decimal(), big_decimal(), integer()) -> big_decimal(). scale_adjusted_add(To_scale, To_add, Scale_difference) -> _pipe = unscaled_value(To_scale), _pipe@1 = multiply_power_of_ten(_pipe, Scale_difference), _pipe@2 = bigi_ffi:add(_pipe@1, unscaled_value(To_add)), {big_decimal, _pipe@2, scale(To_add)}. -file("src/bigdecimal.gleam", 228). -spec add(big_decimal(), big_decimal()) -> big_decimal(). add(Augend, Addend) -> case gleam@int:subtract(scale(Augend), scale(Addend)) of Scale_difference when Scale_difference < 0 -> scale_adjusted_add(Augend, Addend, - Scale_difference); Scale_difference@1 when Scale_difference@1 > 0 -> scale_adjusted_add(Addend, Augend, Scale_difference@1); _ -> {big_decimal, bigi_ffi:add(unscaled_value(Augend), unscaled_value(Addend)), scale(Augend)} end. -file("src/bigdecimal.gleam", 242). -spec sum(list(big_decimal())) -> big_decimal(). sum(Values) -> gleam@list:fold(Values, zero(), fun add/2). -file("src/bigdecimal.gleam", 249). -spec subtract(big_decimal(), big_decimal()) -> big_decimal(). subtract(Minuend, Subtrahend) -> case gleam@int:subtract(scale(Minuend), scale(Subtrahend)) of Scale_difference when Scale_difference < 0 -> scale_adjusted_add(Minuend, negate(Subtrahend), - Scale_difference); Scale_difference@1 when Scale_difference@1 > 0 -> scale_adjusted_add(negate(Subtrahend), Minuend, Scale_difference@1); _ -> {big_decimal, bigi_ffi:subtract( unscaled_value(Minuend), unscaled_value(Subtrahend) ), scale(Minuend)} end. -file("src/bigdecimal.gleam", 297). -spec scale_adjusted_compare(big_decimal(), big_decimal(), integer()) -> gleam@order:order(). scale_adjusted_compare(To_scale, To_compare, Scale_difference) -> Compare_order@1 = case begin _pipe = gleam@int:absolute_value(Scale_difference), _pipe@1 = bigi_ffi:from(_pipe), _pipe@2 = bigi_ffi:power(bigi_ffi:from(10), _pipe@1), _pipe@3 = gleam@result:map( _pipe@2, fun(_capture) -> bigi_ffi:multiply(_capture, unscaled_value(To_scale)) end ), gleam@result:map( _pipe@3, fun(_capture@1) -> bigi_ffi:compare(_capture@1, unscaled_value(To_compare)) end ) end of {ok, Compare_order} -> Compare_order; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"bigdecimal"/utf8>>, function => <<"scale_adjusted_compare"/utf8>>, line => 303, value => _assert_fail, start => 7568, 'end' => 7825, pattern_start => 7579, pattern_end => 7596}) end, Compare_order@1. -file("src/bigdecimal.gleam", 286). ?DOC(" N.B. If scale is different, trailing zeros are ignored.\n"). -spec compare(big_decimal(), big_decimal()) -> gleam@order:order(). compare(This, That) -> case gleam@int:subtract(scale(This), scale(That)) of Scale_difference when Scale_difference < 0 -> scale_adjusted_compare(This, That, Scale_difference); Scale_difference@1 when Scale_difference@1 > 0 -> _pipe = scale_adjusted_compare(That, This, Scale_difference@1), gleam@order:negate(_pipe); _ -> bigi_ffi:compare(unscaled_value(This), unscaled_value(That)) end. -file("src/bigdecimal.gleam", 92). ?DOC( " N.B. if the input has equal value to either of the extremes\n" " but different precision, the larger precision will be returned\n" ). -spec clamp(big_decimal(), big_decimal(), big_decimal()) -> big_decimal(). clamp(Value, Min, Max) -> case compare(Value, Min) of eq -> case gleam@int:compare(scale(Value), scale(Min)) of eq -> Value; gt -> Value; lt -> Min end; lt -> Min; gt -> case compare(Value, Max) of eq -> case gleam@int:compare(scale(Value), scale(Max)) of eq -> Value; lt -> Value; gt -> Max end; gt -> Max; lt -> Value end end. -file("src/bigdecimal.gleam", 312). -spec multiply(big_decimal(), big_decimal()) -> big_decimal(). multiply(Multiplicand, Multiplier) -> {big_decimal, bigi_ffi:multiply( unscaled_value(Multiplicand), unscaled_value(Multiplier) ), gleam@int:add(scale(Multiplicand), scale(Multiplier))}. -file("src/bigdecimal.gleam", 324). ?DOC(" For an empty list, this will return `one()`.\n"). -spec product(list(big_decimal())) -> big_decimal(). product(Values) -> gleam@list:fold(Values, one(), fun multiply/2). -file("src/bigdecimal.gleam", 377). -spec divide_and_round( bigi:big_int(), bigi:big_int(), integer(), bigdecimal@rounding:rounding_mode() ) -> big_decimal(). divide_and_round(Dividend, Divisor, Scale, Rounding) -> Quotient = bigi_ffi:divide(Dividend, Divisor), Quotient_sign = begin _pipe = bigi_ffi:compare(Quotient, bigi_ffi:zero()), gleam@order:to_int(_pipe) end, Remainder = bigi_ffi:remainder(Dividend, Divisor), case bigi_ffi:compare(Remainder, bigi_ffi:zero()) of eq -> {big_decimal, Quotient, Scale}; lt -> _pipe@1 = adjust_unscaled_value_for_rounding( Dividend, Quotient_sign, Quotient, Divisor, Rounding ), {big_decimal, _pipe@1, Scale}; gt -> _pipe@1 = adjust_unscaled_value_for_rounding( Dividend, Quotient_sign, Quotient, Divisor, Rounding ), {big_decimal, _pipe@1, Scale} end. -file("src/bigdecimal.gleam", 408). ?DOC( " trims trailing zeros until the requested scale\n" " is reached or no more zeros can be trimmed\n" ). -spec trim_zeros_to_match_scale(big_decimal(), integer()) -> big_decimal(). trim_zeros_to_match_scale(Value, Preferred_scale) -> case scale(Value) > Preferred_scale of false -> Value; true -> Non_zero_remainder = begin _pipe = unscaled_value(Value), _pipe@1 = bigi_ffi:remainder(_pipe, bigi_ffi:from(10)), bigi_ffi:compare(_pipe@1, bigi_ffi:zero()) end /= eq, case Non_zero_remainder of true -> Value; false -> trim_zeros_to_match_scale( begin _pipe@2 = unscaled_value(Value), _pipe@3 = bigi_ffi:divide( _pipe@2, bigi_ffi:from(10) ), {big_decimal, _pipe@3, scale(Value) - 1} end, Preferred_scale ) end end. -file("src/bigdecimal.gleam", 78). ?DOC(" Trim trailing zeros from after the decimal point.\n"). -spec trim_zeros(big_decimal()) -> big_decimal(). trim_zeros(Value) -> trim_zeros_to_match_scale(Value, 0). -file("src/bigdecimal.gleam", 348). -spec divide_internal( big_decimal(), big_decimal(), integer(), bigdecimal@rounding:rounding_mode() ) -> big_decimal(). divide_internal(Dividend, Divisor, Preferred_scale, Rounding) -> New_scale = begin _pipe = (10 * precision(Divisor)), _pipe@1 = erlang:float(_pipe), _pipe@2 = gleam@float:divide(_pipe@1, 3.0), _pipe@3 = gleam@result:lazy_unwrap(_pipe@2, fun() -> +0.0 end), _pipe@4 = math:ceil(_pipe@3), _pipe@5 = erlang:round(_pipe@4), gleam@int:add(_pipe@5, precision(Dividend)) end, _pipe@10 = case (scale(Divisor) - scale(Dividend)) + New_scale of X when X > 0 -> _pipe@6 = unscaled_value(Dividend), _pipe@7 = multiply_power_of_ten(_pipe@6, X), divide_and_round( _pipe@7, unscaled_value(Divisor), New_scale, Rounding ); X@1 -> _pipe@8 = unscaled_value(Divisor), _pipe@9 = multiply_power_of_ten(_pipe@8, - X@1), divide_and_round( unscaled_value(Dividend), _pipe@9, New_scale, Rounding ) end, trim_zeros_to_match_scale(_pipe@10, Preferred_scale). -file("src/bigdecimal.gleam", 335). ?DOC( " Divide the dividend by the divisor.\n" " The result has a preferred scale of `scale(dividend) - scale(divisor)`,\n" " but might have scale up to `precision(dividend) + ceil(10 * precision(divisor) / 3)`.\n" ). -spec divide(big_decimal(), big_decimal(), bigdecimal@rounding:rounding_mode()) -> big_decimal(). divide(Dividend, Divisor, Rounding) -> New_scale = scale(Dividend) - scale(Divisor), case {signum(Dividend), signum(Divisor)} of {_, 0} -> zero(); {0, _} -> {big_decimal, bigi_ffi:from(0), New_scale}; {_, _} -> divide_internal(Dividend, Divisor, New_scale, Rounding) end. -file("src/bigdecimal.gleam", 441). ?DOC( " Computes the **truncating remainder** of two `BigDecimal` values.\n" "\n" " The sign of the result always matches the sign of `value`\n" " (the dividend), and the remainder may be negative.\n" "\n" " Follows the standard Gleam divide-by-zero rule of 0 when the divisor is 0.\n" "\n" " Note: This is **not** a modulo operation.\n" ). -spec remainder(big_decimal(), big_decimal()) -> big_decimal(). remainder(Value, Divisor) -> Common_scale = gleam@int:max(scale(Value), scale(Divisor)), _pipe@2 = bigi_ffi:remainder( begin _pipe = rescale(Value, Common_scale, floor), unscaled_value(_pipe) end, begin _pipe@1 = rescale(Divisor, Common_scale, floor), unscaled_value(_pipe@1) end ), {big_decimal, _pipe@2, Common_scale}. -file("src/bigdecimal.gleam", 455). ?DOC( " Returns an error if the exponent is negative.\n" " (Inherited behaviour from `bigi`)\n" ). -spec power(big_decimal(), integer()) -> {ok, big_decimal()} | {error, nil}. power(Value, Exponent) -> _pipe = unscaled_value(Value), _pipe@1 = bigi_ffi:power(_pipe, bigi_ffi:from(Exponent)), gleam@result:map( _pipe@1, fun(_capture) -> {big_decimal, _capture, gleam@int:multiply(scale(Value), Exponent)} end ). -file("src/bigdecimal.gleam", 495). -spec parse_unscaled(binary(), integer()) -> {ok, big_decimal()} | {error, nil}. parse_unscaled(Value, Scale) -> _pipe = bigi_ffi:from_string(Value), gleam@result:map(_pipe, fun(_capture) -> {big_decimal, _capture, Scale} end). -file("src/bigdecimal.gleam", 486). -spec parse_decimal(binary(), integer()) -> {ok, big_decimal()} | {error, nil}. parse_decimal(Value, Initial_scale) -> case begin _pipe = Value, gleam@string:split_once(_pipe, <<"."/utf8>>) end of {ok, {Before, After}} -> parse_unscaled( <>, erlang:byte_size(After) + Initial_scale ); {error, _} -> parse_unscaled(Value, Initial_scale) end. -file("src/bigdecimal.gleam", 474). -spec parse_exponential(binary()) -> {ok, big_decimal()} | {error, nil}. parse_exponential(Value) -> Value@1 = begin _pipe = Value, _pipe@1 = gleam@string:trim(_pipe), string:lowercase(_pipe@1) end, case begin _pipe@2 = Value@1, gleam@string:split_once(_pipe@2, <<"e"/utf8>>) end of {ok, {Number, Exponent}} -> _pipe@3 = gleam_stdlib:parse_int(Exponent), _pipe@4 = gleam@result:map(_pipe@3, fun gleam@int:negate/1), gleam@result:'try'( _pipe@4, fun(_capture) -> parse_decimal(Number, _capture) end ); {error, _} -> parse_decimal(Value@1, 0) end. -file("src/bigdecimal.gleam", 470). -spec from_string(binary()) -> {ok, big_decimal()} | {error, nil}. from_string(Value) -> parse_exponential(Value). -file("src/bigdecimal.gleam", 461). -spec from_float(float()) -> big_decimal(). from_float(Value) -> Bigd@1 = case begin _pipe = gleam_stdlib:float_to_string(Value), from_string(_pipe) end of {ok, Bigd} -> Bigd; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"bigdecimal"/utf8>>, function => <<"from_float"/utf8>>, line => 463, value => _assert_fail, start => 12043, 'end' => 12110, pattern_start => 12054, pattern_end => 12062}) end, Bigd@1.