%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(big_SUITE).
-export([all/0, suite/0, groups/0]).
-export([t_div/1, eq_28/1, eq_32/1, eq_big/1, eq_math/1, big_literals/1,
borders/1, negative/1, big_float_1/1, big_float_2/1,
bxor_2pow/1,
shift_limit_1/1, powmod/1, system_limit/1, toobig/1, otp_6692/1]).
%% Internal exports.
-export([eval/1]).
-export([init/3]).
-export([fac/1, fib/1, pow/2, gcd/2, lcm/2]).
-include_lib("common_test/include/ct.hrl").
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap, {minutes, 3}}].
all() ->
[t_div, eq_28, eq_32, eq_big, eq_math, big_literals,
borders, negative, {group, big_float}, shift_limit_1,
bxor_2pow,
powmod, system_limit, toobig, otp_6692].
groups() ->
[{big_float, [], [big_float_1, big_float_2]}].
%%
%% Syntax of data files:
%% Expr1 = Expr2.
%% ...
%% built in functions are:
%% fac(N).
%% fib(N).
%% pow(X, N) == X ^ N
%% gcd(Q, R)
%% lcm(Q, R)
%%
eq_28(Config) when is_list(Config) ->
TestFile = test_file(Config, "eq_28.dat"),
test(TestFile).
eq_32(Config) when is_list(Config) ->
TestFile = test_file(Config, "eq_32.dat"),
test(TestFile).
eq_big(Config) when is_list(Config) ->
TestFile = test_file(Config, "eq_big.dat"),
test(TestFile).
eq_math(Config) when is_list(Config) ->
TestFile = test_file(Config, "eq_math.dat"),
test(TestFile).
%% Tests border cases between small/big.
borders(Config) when is_list(Config) ->
TestFile = test_file(Config, "borders.dat"),
test(TestFile).
negative(Config) when is_list(Config) ->
TestFile = test_file(Config, "negative.dat"),
test(TestFile).
%% Find test file
test_file(Config, Name) ->
DataDir = proplists:get_value(data_dir, Config),
filename:join(DataDir, Name).
%%
%%
%% Run test on file test_big_seq.erl
%%
%%
test(File) ->
test(File, [node()]).
test(File, Nodes) ->
{ok,Fd} = file:open(File, [read]),
Res = test(File, Fd, Nodes),
file:close(Fd),
case Res of
{0,Cases} -> {comment, integer_to_list(Cases) ++ " cases"};
{_,_} -> ct:fail("failed")
end.
test(File, Fd, Ns) ->
test(File, Fd, Ns, 0, 0, 0).
test(File, Fd, Ns, L, Cases, Err) ->
case io:parse_erl_exprs(Fd, '') of
{eof,_} -> {Err, Cases};
{error, {Line,_Mod,Message}, _} ->
Fmt = erl_parse:format_error(Message),
io:format("~s:~w: error ~s~n", [File, Line+L, Fmt]),
{Err+1, Cases};
{ok, [{match,ThisLine,Expr1,Expr2}], Line} ->
case multi_match(Ns, {op,0,'-',Expr1,Expr2}) of
[] ->
test(File, Fd, Ns, Line+L-1,Cases+1, Err);
[_|_] ->
PP = erl_pp:expr({op,0,'=/=',Expr1,Expr2}),
io:format("~s:~w : error ~s~n", [File,ThisLine+L, PP]),
test(File, Fd, Ns, Line+L-1,Cases+1, Err+1)
end;
{ok, Exprs, Line} ->
PP = erl_pp:exprs(Exprs),
io:format("~s: ~w: equation expected not ~s~n", [File,Line+L,PP]),
test(File, Fd, Ns, Line+L-1,Cases+1, Err+1)
end.
multi_match(Ns, Expr) ->
multi_match(Ns, Expr, []).
multi_match([Node|Ns], Expr, Rs) ->
X = rpc:call(Node, big_SUITE, eval, [Expr]),
if X == 0 -> multi_match(Ns, Expr, Rs);
true -> multi_match(Ns, Expr, [{Node,X}|Rs])
end;
multi_match([], _, Rs) -> Rs.
eval(Expr) ->
LFH = fun(Name, As) -> apply(?MODULE, Name, As) end,
%% Applied arithmetic BIFs.
{value,V,_} = erl_eval:expr(Expr, [], {value,LFH}),
%% Real arithmetic instructions.
V = eval(Expr, LFH),
V.
%% Like a subset of erl_eval:expr/3, but uses real arithmetic instructions instead of
%% applying them (it does make a difference).
eval({op,_,Op,A0}, LFH) ->
A = eval(A0, LFH),
Res = eval_op(Op, A),
erlang:garbage_collect(),
Res;
eval({op,_,Op,A0,B0}, LFH) ->
[A,B] = eval_list([A0,B0], LFH),
Res = eval_op(Op, A, B),
erlang:garbage_collect(),
Res;
eval({integer,_,I}, _) -> I;
eval({call,_,{atom,_,Local},Args0}, LFH) ->
Args = eval_list(Args0, LFH),
LFH(Local, Args).
eval_list([E|Es], LFH) ->
[eval(E, LFH)|eval_list(Es, LFH)];
eval_list([], _) -> [].
eval_op('-', A) -> -A;
eval_op('+', A) -> +A;
eval_op('bnot', A) -> bnot A.
eval_op('-', A, B) -> A - B;
eval_op('+', A, B) -> A + B;
eval_op('*', A, B) -> A * B;
eval_op('div', A, B) -> A div B;
eval_op('rem', A, B) -> A rem B;
eval_op('band', A, B) -> A band B;
eval_op('bor', A, B) -> A bor B;
eval_op('bxor', A, B) -> A bxor B;
eval_op('bsl', A, B) -> A bsl B;
eval_op('bsr', A, B) -> A bsr B.
%% Built in test functions
fac(0) -> 1;
fac(1) -> 1;
fac(N) -> N * fac(N-1).
%%
%% X ^ N
%%
pow(_, 0) -> 1;
pow(X, 1) -> X;
pow(X, N) when (N band 1) == 1 ->
X2 = pow(X, N bsr 1),
X*X2*X2;
pow(X, N) ->
X2 = pow(X, N bsr 1),
X2*X2.
fib(0) -> 1;
fib(1) -> 1;
fib(N) -> fib(N-1) + fib(N-2).
%%
%% Gcd
%%
gcd(Q, 0) -> Q;
gcd(Q, R) -> gcd(R, Q rem R).
%%
%% Least common multiple
%%
lcm(Q, R) ->
Q*R div gcd(Q, R).
%% Test case t_div cut in from R2D test suite.
t_div(Config) when is_list(Config) ->
'try'(fun() -> 98765432101234 div 98765432101235 end, 0),
% Big remainder, small quotient.
'try'(fun() -> 339254531512 div 68719476736 end, 4),
ok.
'try'(Fun, Result) ->
'try'(89, Fun, Result, []).
'try'(0, _, _, _) ->
ok;
'try'(Iter, Fun, Result, Filler) ->
spawn(?MODULE, init, [self(), Fun, list_to_tuple(Filler)]),
receive
{result, Result} ->
'try'(Iter-1, Fun, Result, [0|Filler]);
{result, Other} ->
ct:fail("Expected ~p; got ~p~n", [Result, Other])
end.
init(ReplyTo, Fun, _Filler) ->
ReplyTo ! {result, Fun()}.
%% Tests that big-number literals work correctly.
big_literals(Config) when is_list(Config) ->
%% Note: The literal test cannot be compiler on a pre-R4 Beam emulator,
%% so we compile it now.
DataDir = proplists:get_value(data_dir, Config),
Test = filename:join(DataDir, "literal_test"),
{ok, Mod, Bin} = compile:file(Test, [binary]),
{module, Mod} = code:load_binary(Mod, Mod, Bin),
ok = Mod:t(),
ok.
%% OTP-2436, part 1
big_float_1(Config) when is_list(Config) ->
%% F is a number very close to a maximum float.
F = id(1.7e308),
I = trunc(F),
true = (I == F),
false = (I /= F),
true = (I > F/2),
false = (I =< F/2),
true = (I*2 >= F),
false = (I*2 < F),
true = (I*I > F),
false = (I*I =< F),
true = (F == I),
false = (F /= I),
false = (F/2 > I),
true = (F/2 =< I),
false = (F >= I*2),
true = (F < I*2),
false = (F > I*I),
true = (F =< I*I),
ok.
%% "OTP-2436, part 2
big_float_2(Config) when is_list(Config) ->
F = id(1.7e308),
I = trunc(F),
{'EXIT', _} = (catch 1/(2*I)),
_Ignore = 2/I,
{'EXIT', _} = (catch 4/(2*I)),
ok.
%% OTP-3256
shift_limit_1(Config) when is_list(Config) ->
case catch (id(1) bsl 100000000) of
{'EXIT', {system_limit, _}} ->
ok
end,
ok.
powmod(Config) when is_list(Config) ->
A = 1696192905348584855517250509684275447603964214606878827319923580493120589769459602596313014087329389174229999430092223701630077631205171572331191216670754029016160388576759960413039261647653627052707047,
B = 43581177444506616087519351724629421082877485633442736512567383077022781906420535744195118099822189576169114064491200598595995538299156626345938812352676950427869649947439032133573270227067833308153431095,
C = 52751775381034251994634567029696659541685100826881826508158083211003576763074162948462801435204697796532659535818017760528684167216110865807581759669824808936751316879636014972704885388116861127856231,
42092892863788727404752752803608028634538446791189806757622214958680350350975318060071308251566643822307995215323107194784213893808887471095918905937046217646432382915847269148913963434734284563536888 = powmod(A, B, C),
ok.
powmod(A, 1, C) ->
A rem C;
powmod(A, 2, C) ->
A*A rem C;
powmod(A, B, C) ->
B1 = B div 2,
B2 = B - B1,
P = powmod(A, B1, C),
case B2 of
B1 ->
(P*P) rem C;
_ ->
(P*P*A) rem C
end.
system_limit(Config) when is_list(Config) ->
Maxbig = maxbig(),
{'EXIT',{system_limit,_}} = (catch Maxbig+1),
{'EXIT',{system_limit,_}} = (catch -Maxbig-1),
{'EXIT',{system_limit,_}} = (catch 2*Maxbig),
{'EXIT',{system_limit,_}} = (catch bnot Maxbig),
{'EXIT',{system_limit,_}} = (catch apply(erlang, id('bnot'), [Maxbig])),
{'EXIT',{system_limit,_}} = (catch Maxbig bsl 2),
{'EXIT',{system_limit,_}} = (catch apply(erlang, id('bsl'), [Maxbig,2])),
{'EXIT',{system_limit,_}} = (catch id(1) bsl (1 bsl 45)),
{'EXIT',{system_limit,_}} = (catch id(1) bsl (1 bsl 69)),
%% There should be no system_limit exception when shifting a zero.
0 = id(0) bsl (1 bsl 128),
0 = id(0) bsr -(1 bsl 128),
Erlang = id(erlang),
0 = Erlang:'bsl'(id(0), 1 bsl 128),
0 = Erlang:'bsr'(id(0), -(1 bsl 128)),
ok.
maxbig() ->
%% We assume that the maximum arity is (1 bsl 19) - 1.
Ws = erlang:system_info(wordsize),
(((1 bsl ((16777184 * (Ws div 4))-1)) - 1) bsl 1) + 1.
id(I) -> I.
toobig(Config) when is_list(Config) ->
{'EXIT',{{badmatch,_},_}} = (catch toobig()),
ok.
toobig() ->
A = erlang:term_to_binary(lists:seq(1000000, 2200000)),
ASize = erlang:bit_size(A),
<<ANr:ASize>> = A, % should fail
ANr band ANr.
%% Tests for DIV/REM bug reported in OTP-6692
otp_6692(Config) when is_list(Config)->
loop1(1,1000).
fact(N) ->
fact(N,1).
fact(0,P) -> P;
fact(N,P) -> fact(N-1,P*N).
raised(X,1) ->
X;
raised(X,N) ->
X*raised(X,N-1).
loop1(M,M) ->
ok;
loop1(N,M) ->
loop2(fact(N),raised(7,7),1,8),
loop1(N+1,M).
loop2(_,_,M,M) ->
ok;
loop2(X,Y,N,M) ->
Z = raised(Y,N),
case X rem Z of
Z ->
exit({failed,X,'REM',Z,'=',Z});
0 ->
case (X div Z) * Z of
X ->
ok;
Wrong ->
exit({failed,X,'DIV',Z,'*',Z,'=',Wrong})
end;
_ ->
ok
end,
loop2(X,Y,N+1,M).
%% ERL-450
bxor_2pow(_Config) ->
IL = lists:seq(8*3, 8*16, 4),
JL = lists:seq(0, 64),
[bxor_2pow_1((1 bsl I), (1 bsl J))
|| I <- IL, J <- JL],
ok.
bxor_2pow_1(A, B) ->
for(-1,1, fun(Ad) ->
for(-1,1, fun(Bd) ->
bxor_2pow_2(A+Ad, B+Bd),
bxor_2pow_2(-A+Ad, B+Bd),
bxor_2pow_2(A+Ad, -B+Bd),
bxor_2pow_2(-A+Ad, -B+Bd)
end)
end).
for(From, To, _Fun) when From > To ->
ok;
for(From, To, Fun) ->
Fun(From),
for(From+1, To, Fun).
bxor_2pow_2(A, B) ->
Correct = my_bxor(A, B),
case A bxor B of
Correct -> ok;
Wrong ->
io:format("~.16b bxor ~.16b\n", [A,B]),
io:format("Expected ~.16b\n", [Correct]),
io:format("Got ~.16b\n", [Wrong]),
ct:fail({failed, 'bxor'})
end.
%% Implement bxor without bxor
my_bxor(A, B) ->
my_bxor(A, B, 0, 0).
my_bxor(0, 0, _, Acc) -> Acc;
my_bxor(-1, -1, _, Acc) -> Acc;
my_bxor(-1, 0, N, Acc) -> (-1 bsl N) bor Acc; % sign extension
my_bxor(0, -1, N, Acc) -> (-1 bsl N) bor Acc; % sign extension
my_bxor(A, B, N, Acc0) ->
Acc1 = case (A band 1) =:= (B band 1) of
true -> Acc0;
false -> Acc0 bor (1 bsl N)
end,
my_bxor(A bsr 1, B bsr 1, N+1, Acc1).