%%% -*- erlang-indent-level: 2 -*-
%%%-------------------------------------------------------------------
%%% Author: Kostis Sagonas
%%%
%%% Contains tests that manipulate floating point numbers.
%%%-------------------------------------------------------------------
-module(basic_floats).
-export([test/0]).
-export([test_fmt_double_fpe_leak/0]). % suppress the unused warning
test() ->
ok = test_arith_ops(),
ok = test_fp_ebb(),
ok = test_fp_phi(),
ok = test_big_bad_float(),
ok = test_catch_bad_fp_arith(),
ok = test_catch_fp_conv(),
ok = test_fp_with_fp_exceptions(),
%% ok = test_fmt_double_fpe_leak(), % this requires printing
ok = test_icode_type_crash(),
ok = test_icode_type_crash_2(),
ok.
%%--------------------------------------------------------------------
test_arith_ops() ->
E = 2.5617,
5.703200000000001 = add(E),
0.5798000000000001 = sub(E),
8.047580550000001 = mult(E),
-6.023e23 = negate(6.023e23),
ok.
add(X) ->
3.1415 + X.
sub(X) ->
3.1415 - X.
mult(X) ->
3.1415 * X.
%% tests the translation of the fnegate BEAM instruction.
negate(X) ->
- (X + 0.0).
%%--------------------------------------------------------------------
%% Test the construction of overlapping extended basic blocks where
%% BEAM has constructed one and hipe_icode_fp constructs the other.
%%--------------------------------------------------------------------
test_fp_ebb() ->
1.0 = foo(2 * math:pi()),
1.0 = bar(2 * math:pi()),
ok.
foo(X) ->
X / (2 * math:pi()).
bar(X) ->
F = float_two(),
case F < 3.0 of
true -> (X * F) / ((2 * F) * math:pi());
false -> weird
end.
float_two() ->
2.0.
%%--------------------------------------------------------------------
test_fp_phi() ->
10 = fp_phi(10, 100),
undefined = fp_phi(1.1e302, 0.000000001),
ok.
fp_phi(A, B) ->
case catch A / B of
{'EXIT', _Reason} -> undefined;
_ -> round(100 * (A / B))
end.
%%--------------------------------------------------------------------
-define(BS, "93904329458954829589425849258998492384932849328493284932849328493284932389248329432932483294832949245827588578423578435783475834758375837580745807304258924584295924588459834958349589348589345934859384958349583945893458934859438593485995348594385943859438593458934589345938594385934859483958348934589435894859485943859438594594385938459438595034950439504395043950495043593485943758.0").
test_big_bad_float() ->
ok = try f2l(?BS) catch error:badarg -> ok end,
ok = case catch f2l(?BS) of {'EXIT', {badarg, _}} -> ok end,
ok.
f2l(F) ->
float_to_list(list_to_float(F)).
%%--------------------------------------------------------------------
%% Tests catching of floating point bad arithmetic.
test_catch_bad_fp_arith() ->
5.7 = f(2.56),
{'EXIT', {badarith, _}} = bad_arith(9.9),
ok.
f(F) when is_float(F) -> F + 3.14.
bad_arith(F) when is_float(F) ->
catch F * 1.70000e+308.
%%--------------------------------------------------------------------
%% Tests proper catching of exceptions due to illegal convertion of
%% bignums to floating point numbers.
test_catch_fp_conv() ->
F = 1.7e308, %% F is a number very close to a maximum float.
ok = big_arith(F),
ok = big_const_float(F),
ok.
big_arith(F) ->
I = trunc(F),
{'EXIT', {badarith, _}} = big_int_arith(I),
ok.
big_int_arith(I) when is_integer(I) ->
catch(3.0 + 2*I).
big_const_float(F) ->
I = trunc(F),
badarith = try (1/(2*I)) catch error:Err -> Err end,
_ = 2/I,
{'EXIT', _} = (catch 4/(2*I)),
ok.
%%--------------------------------------------------------------------
%% Forces floating point exceptions and tests that subsequent, legal,
%% operations are calculated correctly.
test_fp_with_fp_exceptions() ->
0.0 = math:log(1.0),
badarith = try math:log(float_minus_one()) catch error:E1 -> E1 end,
0.0 = math:log(1.0),
badarith = try math:log(float_zero()) catch error:E2 -> E2 end,
0.0 = math:log(1.0),
%% An old-fashioned exception here just so as to test this case also
{'EXIT', _} = (catch fp_mult(3.23e133, 3.57e257)),
0.0 = math:log(1.0),
badarith = try fp_div(5.0, 0.0) catch error:E3 -> E3 end,
0.0 = math:log(1.0),
ok.
fp_mult(X, Y) -> X * Y.
fp_div(X, Y) -> X / Y.
%% The following two function definitions appear here just to shut
%% off 'expression will fail with a badarg' warnings from the compiler
float_zero() -> 0.0.
float_minus_one() -> -1.0.
%%--------------------------------------------------------------------
%% Test that erl_printf_format.c:fmt_double() does not leak pending FP
%% exceptions to subsequent code. This used to break x87 FP code on
%% 32-bit x86. Based on a problem report from Richard Carlsson.
test_fmt_double_fpe_leak() ->
test_fmt_double_fpe_leak(float_zero(), int_two()),
ok.
%% We need the specific sequence of erlang:display/1 on a float that
%% triggers faulting ops in fmt_double() followed by a simple FP BIF.
%% We also need to repeat this at least three times.
test_fmt_double_fpe_leak(X, Y) ->
erlang:display(X), _ = math:log10(Y),
erlang:display(X), _ = math:log10(Y),
erlang:display(X), _ = math:log10(Y),
erlang:display(X), _ = math:log10(Y),
erlang:display(X),
math:log10(Y).
int_two() -> 2.
%%--------------------------------------------------------------------
%% Contains code which confuses the icode_type analysis and results
%% in a compiler crash. Stipped down from code sent by Paul Guyot.
%% Compiles alright with the option 'no_icode_type' but that defeats
%% the purpose of the test.
test_icode_type_crash() ->
Fun = f(1, 2, 3),
42.0 = Fun(),
ok.
f(A, B, C) ->
fun () ->
X = case A of
0 -> 1 / B;
_ -> A / C
end,
Y = case B of
a -> 1.0;
b -> 2.0;
_ -> 6.0
end,
Z = case C of
c -> 0.1 * X;
_ -> 7.0
end,
Y * Z
end.
%%--------------------------------------------------------------------
%% Contains another case that crashed hipe_icode_fp. This sample was
%% sent by Mattias Jansson (25 Nov 2015). It compiled alright with the
%% option 'no_icode_type' but that defeats the purpose of the test.
%% Unfortunately, the execution of this code goes into an infinite
%% loop, even if the map in the second argument of eat_what/2 gets the
%% appropriate key-value pairs. Still, it is retained as a test
%% because it exposed a different crash than test_icode_type_crash/0.
test_icode_type_crash_2() ->
{'EXIT', {function_clause, _}} = (catch eat()),
ok.
eat() ->
eat_what(1.0, #{}).
eat_what(Factor, #{rat_type := LT} = Rat) ->
#{cheese := Cheese} = Rat,
UnitCheese = Cheese / 2,
RetA = case eat() of
{full, RetA1} ->
CheeseB2 = min(RetA1, UnitCheese) * Factor,
case eat() of
full -> {win, RetA1};
hungry -> {partial, RetA1 - CheeseB2}
end;
AOther -> AOther
end,
RetB = case eat() of
{full, RetB1} ->
CheeseA2 = min(RetB1, UnitCheese) * Factor,
rat:init(single, LT, CheeseA2),
case eat() of
full -> {full, RetB1};
hungry -> {hungry, RetB1 - CheeseA2}
end
end,
{RetA, RetB}.