Packages

CVSS (Common Vulnerability Scoring System) library for Erlang

Current section

Files

Jump to
cvss src vendored cvss_decimal.erl
Raw

src/vendored/cvss_decimal.erl

%% SPDX-License-Identifier: MIT
%% SPDX-FileCopyrightText: 2018 Yakov Kozlov
%% SPDX-FileCopyrightText: 2018 The decimal library authors <https://github.com/egobrain/decimal/graphs/contributors>
%%
%% Vendored from erlang_decimal v0.6.5
%% https://github.com/egobrain/decimal
-module(cvss_decimal).
-moduledoc false.
%% Converters
-export([
to_decimal/2,
to_decimal/3,
to_binary/1,
to_binary/2
]).
%% Arith
-export([
add/2,
sub/2,
mult/2,
divide/3,
sqrt/2
]).
%% Compare
-export([
fast_cmp/2,
cmp/3
]).
%% Utils
-export([
abs/1,
minus/1,
is_zero/1,
reduce/1,
round/3
]).
-compile(inline).
-inline([divide_/3]).
-type decimal() :: {integer(), integer()}.
-type old_decimal() :: {0|1, non_neg_integer(), integer()}.
-type rounding_algorithm() :: round_floor | round_ceiling |
round_half_up | round_half_down |
round_down.
-type opts() :: #{
precision => non_neg_integer(),
rounding => rounding_algorithm()
}.
-export_type([
decimal/0,
opts/0,
rounding_algorithm/0
]).
%% =============================================================================
%%% API
%% =============================================================================
%% = Converters ================================================================
-spec to_decimal(Value, Opts) -> decimal() when
Value :: integer() | float() | binary() | list() |
decimal() | old_decimal(),
Opts :: opts().
to_decimal({Base, Exp}=Decimal, _Opts) when
is_integer(Base), is_integer(Exp) ->
Decimal;
to_decimal(Int, #{precision := Precision, rounding := Rounding}) when
is_integer(Int) ->
round(Rounding, {Int, 0}, Precision);
to_decimal(Binary, #{precision := Precision, rounding := Rounding}) when
is_binary(Binary) ->
Decimal = cvss_decimal_conv:from_binary(Binary),
round(Rounding, Decimal, Precision);
to_decimal(Float, #{precision := Precision, rounding := Rounding}) when
is_float(Float) ->
Decimal = cvss_decimal_conv:from_float(Float),
round(Rounding, Decimal, Precision);
to_decimal(List, #{precision := Precision, rounding := Rounding}) when
is_list(List) ->
Decimal = cvss_decimal_conv:from_list(List),
round(Rounding, Decimal, Precision);
%% Old decimal format support
to_decimal({Sign, Base0, Exp}, _Opts) when
is_integer(Sign), is_integer(Base0), is_integer(Exp) ->
Base = case Sign of 1 -> -Base0; 0 -> Base0 end,
{Base, Exp}.
-spec to_decimal(Base, Exp, Opts) -> decimal() when
Base :: integer(),
Exp :: integer(),
Opts :: opts().
to_decimal(Base, Exp, _Opts) ->
{Base, Exp}.
-spec to_binary(decimal()) -> binary().
to_binary(Decimal) ->
cvss_decimal_conv:to_binary(Decimal, #{pretty => true}).
-spec to_binary(decimal(), Opts) -> binary() when
Opts :: cvss_decimal_conv:binary_opts().
to_binary(Decimal, Opts) ->
cvss_decimal_conv:to_binary(Decimal, Opts).
%% = Arith =====================================================================
-spec add(decimal(), decimal()) -> decimal().
add({Int1, E1}, {Int2, E2}) ->
Emin = min(E1, E2),
{Int1 * pow_of_ten(E1 - Emin) +
Int2 * pow_of_ten(E2 - Emin),
Emin}.
-spec sub(decimal(), decimal()) -> decimal().
sub(A, B) ->
add(A, minus(B)).
-spec mult(decimal(), decimal()) -> decimal().
mult({Int1, E1}, {Int2, E2}) ->
{Int1*Int2, E1+E2}.
-spec divide(decimal(), decimal(), opts()) -> decimal().
divide({_BaseA, _ExpA}, {0, _ExpB}, _Opts) ->
error(badarith);
divide({0, _ExpA}, {_BaseB, _ExpB}, _Opts) ->
{0, 0};
divide({BaseA, ExpA}, {BaseB, ExpB}, Opts) when BaseB < 0 ->
divide_({-BaseA, ExpA}, {-BaseB, ExpB}, Opts);
divide(A, B, Opts) ->
divide_(A, B, Opts).
divide_({BaseA, ExpA}, {1, ExpB}, #{ precision := Precision, rounding := Rounding }) ->
round(Rounding, {BaseA, ExpA - ExpB}, Precision);
divide_({BaseA, ExpA}, {2, ExpB}, #{ precision := Precision, rounding := Rounding }) when (BaseA band 1) == 0 ->
round(Rounding, {BaseA bsr 1, ExpA - ExpB}, Precision);
divide_({BaseA, ExpA}, {2, ExpB}, #{ precision := Precision, rounding := Rounding }) ->
round(Rounding, {BaseA * 5, ExpA - ExpB - 1}, Precision);
divide_({BaseA, ExpA}, {BaseB, ExpB}, #{ precision := Precision0, rounding := Rounding }) ->
Precision = max(0, -(ExpB - ExpA)) + Precision0 + 1,
BaseRes = BaseA * pow_of_ten(Precision) div BaseB,
round(Rounding, {BaseRes, ExpA - ExpB - Precision}, Precision0).
-spec sqrt(decimal(), opts()) -> decimal() | no_return(). %% @throws error(badarith)
sqrt({M, _E}, _Context) when M < 0 ->
error(badarith);
sqrt({0, _E}, _Context) ->
{0, 0};
sqrt({M, E}=Decimal, #{precision := Precision0} = Context) ->
Precision = Precision0 + 1,
CoefficientDigits = length(integer_to_list(M)),
case E band 1 of
0 ->
Shift = Precision - ((CoefficientDigits + 1) bsr 1),
sqrt(Decimal, Context, Shift, M);
_ ->
Shift = Precision - ((CoefficientDigits bsr 1) + 1),
sqrt(Decimal, Context, Shift, M * 10)
end.
sqrt(Decimal, Context, Shift, M) ->
case Shift >= 0 of
true ->
sqrt(Decimal, Context, Shift, M * pow_of_ten(Shift bsl 1), true);
false ->
Operand = pow_of_ten((- Shift) bsl 1),
sqrt(Decimal, Context, Shift, M div Operand, M rem Operand =:= 0)
end.
sqrt({_, E0}, #{precision := Precision, rounding := Rounding}, Shift, M, Exact) ->
E = E0 bsr 1,
N = sqrt_loop(M, pow_of_ten(Precision + 1)),
Result = case Exact and (N * N =:= M) of
true ->
case Shift >= 0 of
true ->
{N div pow_of_ten(Shift), E};
false ->
{N * pow_of_ten(-Shift), E}
end;
false ->
{N, E - Shift}
end,
round(Rounding, Result, Precision).
sqrt_loop(M, N) ->
Q = M div N,
case N =< Q of
true ->
N;
false ->
sqrt_loop(M, (N + Q) bsr 1)
end.
%% = Compare ===================================================================
-spec cmp(decimal(), decimal(), opts()) -> -1 | 0 | 1.
cmp({0, _}, {0, _}, _Opts) ->
0;
cmp({Int1, _}, {Int2, _}, _Opts) when Int1 >= 0, Int2 =< 0 ->
1;
cmp({Int1, _}, {Int2, _}, _Opts) when Int1 =< 0, Int2 >= 0 ->
-1;
cmp({Int, E}, {Int, E}, _Opts) ->
0;
cmp({Int1, E}, {Int2, E}, _Opts) when Int1 > Int2 ->
1;
cmp({Int1, E}, {Int2, E}, _Opts) when Int1 < Int2 ->
-1;
cmp(A, B, #{ precision := Precision, rounding := Rounding }) ->
{Int1, E1} = round(Rounding, A, Precision),
{Int2, E2} = round(Rounding, B, Precision),
Emin = min(E1, E2),
B1 = Int1*pow_of_ten(E1-Emin),
B2 = Int2*pow_of_ten(E2-Emin),
if B1 < B2 -> -1;
B1 > B2 -> 1;
true -> 0
end.
%% Fast compare without rounding
fast_cmp({0, _}, {0, _}) ->
0;
fast_cmp({Int1, _}, {Int2, _}) when Int1 >= 0, Int2 =< 0 ->
1;
fast_cmp({Int1, _}, {Int2, _}) when Int1 =< 0, Int2 >= 0 ->
-1;
fast_cmp({Int, E}, {Int, E}) ->
0;
fast_cmp({Int1, E}, {Int2, E}) when Int1 > Int2 ->
1;
fast_cmp({Int1, E}, {Int2, E}) when Int1 < Int2 ->
-1;
fast_cmp({Int1, E1}, {Int2, E2}) ->
Emin = min(E1, E2),
B1 = Int1*pow_of_ten(E1-Emin),
B2 = Int2*pow_of_ten(E2-Emin),
if B1 < B2 -> -1;
B1 > B2 -> 1;
true -> 0
end.
%% = Utils =====================================================================
-spec is_zero(decimal()) -> boolean().
is_zero({0, _}) -> true;
is_zero(_) -> false.
-spec minus(decimal()) -> decimal().
minus({Int, E}) ->
{-Int, E}.
-spec abs(decimal()) -> decimal().
abs({Int, E}) ->
{erlang:abs(Int), E}.
-spec reduce(decimal()) -> decimal().
reduce({Int, E}) ->
reduce_(Int, E).
reduce_(0, _E) -> {0, 0};
reduce_(Int, E) ->
case Int rem 10 of
0 -> reduce_(Int div 10, E+1);
_ -> {Int, E}
end.
-spec round(rounding_algorithm(), decimal(), non_neg_integer()) -> decimal().
round(Rounding, {Int, E}=Decimal, Precision) ->
reduce(
case -Precision-E of
Delta when Delta > 0 ->
round_(Rounding, Int, E, Delta);
_ ->
Decimal
end).
round_(round_down, Int, E, Delta) ->
P = pow_of_ten(Delta),
Base = Int div P,
zero_exp_(Base, E+Delta);
round_(Rounding, Int, E, Delta) when
Rounding =:= round_ceiling;
Rounding =:= round_floor ->
P = pow_of_ten(Delta),
Base0 = Int div P,
Diff = Int-Base0*P,
Base =
case Rounding of
round_floor when Diff < 0 ->
Base0 - 1;
round_ceiling when Diff > 0 ->
Base0 + 1;
_ ->
Base0
end,
zero_exp_(Base, E+Delta);
round_(Rounding, Int, E, Delta) ->
P = pow_of_ten(Delta-1),
Data = Int div P,
Base0 = Data div 10,
LastDigit = erlang:abs(Data-(Base0*10)),
Base =
case Rounding of
round_half_up when LastDigit >= 5, Data > 0 ->
Base0 + 1;
round_half_up when LastDigit >= 5, Data < 0 ->
Base0 - 1;
round_half_down when LastDigit > 5, Data > 0 ->
Base0 + 1;
round_half_down when LastDigit > 5, Data < 0 ->
Base0 - 1;
_ ->
Base0
end,
zero_exp_(Base, E+Delta).
zero_exp_(0, _Exp) -> {0,0};
zero_exp_(Base, Exp) -> {Base, Exp}.
%% =============================================================================
%%% Internal functions
%% =============================================================================
-spec pow_of_ten(non_neg_integer()) -> pos_integer().
pow_of_ten(N) ->
if N > 0 -> int_pow(10, N, 1);
true -> 1
end.
int_pow(X, N, R) when N < 2 ->
R * X;
int_pow(X, N, R) ->
int_pow(X * X, N bsr 1, case N band 1 of 1 -> R * X; 0 -> R end).