# for review: big ints again

Bert Thompson aet at hydra.cs.mu.oz.au
Wed Apr 8 18:18:59 AEST 1998

```People,

Apologies if this is the second time you receive this. (News didn't
handle the 8-bit characters in the previous posting.)

Anyways, here's the post-review integer.m and a test module, bigint.m.
Let me know if you have feedback.

Cheers,
Bert
----------------------------------------
Estimated hours taken: 10

Implementation of an arbitrary precision integer type and
operations on it.

library/integer.m
test/general/bigint.m

%-----------------------------------------------------------------------------%
% Copyright (C) 1997-1998 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%
%
% file: integer.m
% authors: aet Mar 1998.
%
% Implements an arbitrary precision integer type and basic
% operations on it. (An arbitrary precision integer may have
% any number of digits, unlike an int, which is limited to the
% precision of the machine's int type, which is typically 32 bits.)
%
% Note that all operators behave as the equivalent operators on ints do.
% This includes the division operators: / // rem div mod.
%
% Possible improvements:
%
%	1) allow negative digits (-base+1 .. base-1) in lists of
%	  digits and normalise only when printing. This would
%	  probably simplify the division algorithm, also.
%
%	2) alternatively, instead of using base=10000, use *all* the
%	  bits in an int and make use of the properties of machine
%	  arithmetic. Base 10000 doesn't use even half the bits
%	  in an int, which is inefficient. (Base 2^14 would be
%	  a little better but would require a slightly more
%	  complex case conversion on reading and printing.)
%
%	3) Use an O(n^(3/2)) algorithm for multiplying large
%	  integers, rather than the current O(n^2) method.
%	  There's an obvious divide-and-conquer technique,
%	  Karatsuba multiplication.
%
%	4) We could overload operators so that we can have mixed operations
%	  on ints and integers. For example, "integer(1)+3". This
%	  would obviate most calls of integer().
%
%	5) Use double-ended lists rather than simple lists. This
%	  would improve the efficiency of the division algorithm,
%	  which reverse lists.
%
%	6) Add bit operations (XOR, AND, OR, etc). We would treat
%	  the integers as having a 2's complement bit representation.
%	  This is easier to do if we use base 2^14 as mentioned above.
%
%	7) The implementation of `div' is slower than it need be.
%
%	8) Fourier methods such as Schoenhage-Strassen and
%	  multiplication via modular arithmetic are left as
%	  exercises to the reader. 8^)
%
%
%	Of the above, 1) would have the best bang-for-buck, 5) would
%	benefit division and remainder operations quite a lot, and 3)
%	would benefit large multiplications (thousands of digits)
%	and is straightforward to implement.

:- module integer.

:- interface.

:- import_module string.

:- type integer.

:- pred integer:'<'(integer, integer).
:- mode integer:'<'(in, in) is semidet.

:- pred integer:'>'(integer, integer).
:- mode integer:'>'(in, in) is semidet.

:- pred integer:'=<'(integer, integer).
:- mode integer:'=<'(in, in) is semidet.

:- pred integer:'>='(integer, integer).
:- mode integer:'>='(in, in) is semidet.

:- pred integer:'='(integer, integer).
:- mode integer:'='(in, in) is semidet.

:- func integer(int) = integer.

:- func integer:to_string(integer) = string.

:- func integer:from_string(string) = integer.
:- mode integer:from_string(in) = out is semidet.

:- func integer:'+'(integer) = integer.

:- func integer:'-'(integer) = integer.

:- func integer:'+'(integer, integer) = integer.

:- func integer:'-'(integer, integer) = integer.

:- func integer:'*'(integer, integer) = integer.

:- func integer:'/'(integer, integer) = integer.

:- func integer:'//'(integer, integer) = integer.

:- func integer:'div'(integer, integer) = integer.

:- func integer:'rem'(integer, integer) = integer.

:- func integer:'mod'(integer, integer) = integer.

:- func integer:abs(integer) = integer.

:- pred integer:pow(integer, integer, integer).
:- mode integer:pow(in, in, out) is det.

% :- func integer:float(integer) = float.

:- implementation.

:- import_module require, list, char, std_util, int.

:- type sign == int.	% -1, 0, +1
:- type digit == int.	% base 10000 digit

:- type comparison
--->	lessthan
;	equal
;	greaterthan.

% Note: the list of digits is stored in reverse order.
% That is, little end first.
:- type integer
--->	i(sign, list(digit)).

% We choose base=10000 since 10000^2+10000 < maxint.
% XXX: We should check this.
:- func base = int.
base = 10000.

:- func log10base = int.
log10base = 4.

integer:'<'(X1, X2) :-
big_cmp(X1, X2) = lessthan.

integer:'>'(X1, X2) :-
big_cmp(X1, X2) = greaterthan.

integer:'=<'(X1, X2) :-
big_cmp(X1, X2) = C,
( C = lessthan ; C = equal).

integer:'>='(X1, X2) :-
big_cmp(X1, X2) = C,
( C = greaterthan ; C = equal).

integer:'='(X1, X2) :-
big_cmp(X1, X2) = equal.

:- func one = integer.
one = integer(1).

:- func zero = integer.
zero = integer(0).

integer:'+'(X1) =
X1.

integer:'-'(N) =
big_neg(N).

integer:'+'(X1, X2) =
big_plus(X1, X2).

integer:'-'(X1, X2) =
big_plus(X1, big_neg(X2)).

integer:'*'(X1, X2) =
big_mul(X1, X2).

integer:'div'(X1, X2) =
big_div(X1, X2).

integer:'/'(X1, X2) =
big_quot(X1, X2).

integer:'//'(X1, X2) =
big_quot(X1, X2).

integer:'rem'(X1, X2) =
big_rem(X1, X2).

integer:'mod'(X1, X2) =
big_mod(X1, X2).

integer:abs(N) = Abs :-
( N < integer(0) ->
Abs = -N
;
Abs = N
).

:- func big_neg(integer) = integer.
big_neg(i(S, Ds)) =
i(-S, Ds).

:- func big_mul(integer, integer) = integer.
big_mul(i(S1, Ds1), i(S2, Ds2)) = i(S, Ds) :-
S = S1 * S2,
Ds = pos_mul(Ds1, Ds2).

:- func big_quot(integer, integer) = integer.
big_quot(X1, X2) = Q :-
big_quot_rem(X1, X2, Q, _R).

:- func big_rem(integer, integer) = integer.
big_rem(X1, X2) = R :-
big_quot_rem(X1, X2, _Q, R).

:- func big_div(integer, integer) = integer.
big_div(X, Y) = Div :-
big_quot_rem(X, Y, Trunc, Rem),
(
( X >= zero, Y >= zero
; X < zero, Y < zero
; Rem = zero
)
->
Div = Trunc
;
Div = Trunc - one
).

% XXX: This is dog-slow.
:- func big_mod(integer, integer) = integer.
big_mod(X, Y) = X - (X div Y) * Y.

% Compare two integers.
:- func big_cmp(integer, integer) = comparison.
big_cmp(i(S1, D1), i(S2, D2)) =
( S1 < S2 ->
lessthan
; S1 > S2 ->
greaterthan
; (S1=0, S2=0) ->
equal
; S1=1 ->
pos_cmp(D1, D2)
;
pos_cmp(D2, D1)
).

:- func pos_cmp(list(digit), list(digit)) = comparison.
pos_cmp(Xs, Ys) = pos_cmp_2(Xs1, Ys1) :-
Xs1 = norm(Xs),
Ys1 = norm(Ys).

:- func pos_cmp_2(list(digit), list(digit)) = comparison.
pos_cmp_2([], []) = equal.
pos_cmp_2([_X|_Xs], []) = greaterthan.
pos_cmp_2([], [_Y|_Ys]) = lessthan.
pos_cmp_2([X|Xs], [Y|Ys]) = Cmp :-
Res = pos_cmp_2(Xs, Ys),
( (Res = lessthan ; Res = greaterthan) ->
Cmp = Res
; X = Y ->
Cmp = equal
; X < Y ->
Cmp = lessthan
;
Cmp = greaterthan
).

:- func big_plus(integer, integer) = integer.
big_plus(i(S1, Ds1), i(S2, Ds2)) = Sum :-
( S1 = S2 ->
Sum = i(S1, pos_plus(Ds1, Ds2))
; S1 = 1 ->
C = pos_cmp(Ds1, Ds2),
( C = lessthan ->
Sum = i(-1, pos_sub(Ds2, Ds1))
; C = greaterthan ->
Sum = i(1, pos_sub(Ds1, Ds2))
;
Sum = zero
)
;
C = pos_cmp(Ds1, Ds2),
(
C = lessthan ->
Sum = i(1, pos_sub(Ds2, Ds1))
; C = greaterthan ->
Sum = i(-1, pos_sub(Ds1, Ds2))
;
Sum = zero
)
).

integer:from_string(S) = Big :-
string__to_char_list(S, Cs),
string_to_integer(Cs) = Big.

:- func string_to_integer(list(char)) = integer.
:- mode string_to_integer(in) = out is semidet.
string_to_integer(CCs) = Result :-
( CCs = [],
fail
; CCs = [C|Cs],
% Note:
%	- '-' must be in parentheses.
%	- There can be only one minus sign in a valid string.
( C = ('-') ->
Result = i(Sign, Digs),
Digs = string_to_integer_acc(Cs, []),
pos_cmp(Digs, []) = Cmp,
(Cmp = equal ->
Sign = 0
;
Sign = -1
)
; char__is_digit(C) ->
Result = i(Sign, Digs),
Digs = string_to_integer_acc(CCs, []),
pos_cmp(Digs, []) = Cmp,
(Cmp = equal ->
Sign = 0
;
Sign = 1
)
;
fail
)
).

:- func string_to_integer_acc(list(char), list(digit)) = list(digit).
:- mode string_to_integer_acc(in, in) = out is semidet.
string_to_integer_acc([], Acc) = Acc.
string_to_integer_acc([C|Cs], Acc) = Result :-
( char__is_digit(C) ->
char__to_int(C, D1),
char__to_int('0', Z),
Dig = pos_int_to_digits(D1 - Z),
NewAcc = pos_plus(Dig, mul_by_digit(10, Acc)),
Result = string_to_integer_acc(Cs, NewAcc)
;
fail
).

integer(N) =
int_to_integer(N).

:- func int_to_integer(int) = integer.

:- func signum(int) = int.
signum(N) = SN :-
(N < 0 ->
SN = -1
; N = 0 ->
SN = 0
;
SN = 1
).

:- func pos_int_to_digits(int) = list(digit).
pos_int_to_digits(D) = Result :-
( D = 0 ->
Result = []
;
Result = [ S1 | pos_int_to_digits(C1) ],
chop(D, S1, C1)
).

% Multiply a list of digits by the base.
:- func mul_base(list(digit)) = list(digit).
mul_base(Xs) =
[0|Xs].

:- func mul_by_digit(digit, list(digit)) = list(digit).
mul_by_digit(D, Xs) = Norm :-
Norm = norm(DXs),
DXs = mul_by_digit_2(D, Xs).

:- func mul_by_digit_2(digit, list(digit)) = list(digit).
mul_by_digit_2(_D, []) = [].
mul_by_digit_2(D, [X|Xs]) = [ D*X | mul_by_digit_2(D, Xs) ].

% Normalise a list of ints so that each element of the list
% is a base 10000 digit and there are no extraneous zeros
% at the big end. (Note: the big end (most significant
% digit) is at the end of the list.)
:- func norm(list(int)) = list(digit).
norm(Xs) =
nuke_zeros(norm_2(Xs, 0)).

:- func nuke_zeros(list(digit)) = list(digit).
nuke_zeros(Xs) = Zs :-
list__reverse(Xs, RXs),
RZs = drop_while(equals_zero, RXs),
list__reverse(RZs, Zs).

:- func norm_2(list(int), digit) = list(digit).
norm_2([], C) = Xs :-
( C = 0 ->
Xs = []
;
Xs = [C]
).
norm_2([X|Xs], C) = [S1 | norm_2(Xs, C1)] :-
XC = X + C,
chop(XC, S1, C1).

% Chop an integer into the first two digits of its
% base 10000 representation.
:- pred chop(int, digit, digit).
:- mode chop(in, out, out) is det.
chop(N, Dig, Carry) :-
Dig = N mod base,
Carry = N div base.

:- pred equals_zero(int).
:- mode equals_zero(in) is semidet.
equals_zero(X) :-
X = 0.

:- func drop_while(pred(T), list(T)) = list(T).
:- mode drop_while(pred(in) is semidet, in) = out is det.
drop_while(_F, []) = [].
drop_while(F, [X|Xs]) =
( F(X) ->
drop_while(F, Xs)
;
[X|Xs]
).

:- func pos_plus(list(digit), list(digit)) = list(digit).
pos_plus(Xs, Ys) = Norm :-
Norm = norm(Sums),

:- func pos_sub(list(digit), list(digit)) = list(digit).
pos_sub(Xs, Ys) = Norm :-
Norm = norm(Diffs),
Diffs = diff_pairs(Xs, Ys).

:- func add_pairs(list(int), list(int)) = list(int).
( XXs = [],
XYs = YYs
; YYs = [], XXs = [_|_],
XYs = XXs
; XXs = [X|Xs], YYs = [Y|Ys],
XYs = [ X+Y | add_pairs(Xs, Ys) ]
).

:- func diff_pairs(list(int), list(int)) = list(int).
diff_pairs(XXs, YYs) = XYs :-
( XXs = [],
list__map(int_negate, YYs, XYs)
; YYs = [], XXs = [_|_],
XYs = XXs
; XXs = [X|Xs], YYs = [Y|Ys],
XYs = [ X-Y | diff_pairs(Xs, Ys) ]
).

:- pred int_negate(int, int).
:- mode int_negate(in, out) is det.
int_negate(M, NegM) :-
NegM = -M.

:- func pos_mul(list(digit), list(digit)) = list(digit).
pos_mul([], _Ys) = [].
pos_mul([X|Xs], Ys) = Sum :-
mul_by_digit(X, Ys) = XYs,
pos_mul(Xs, Ys) = XsYs,
mul_base(XsYs) = TenXsYs,
Sum = pos_plus(XYs, TenXsYs).

integer:to_string(N) = S :-
integer_to_string_2(N) = S.

:- func integer_to_string_2(integer) = string.
integer_to_string_2(i(S, Ds)) = Str :-
string__append(Sgn, digits_to_string(Ds), Str),
( S = (-1) ->
Sgn = "-"
;
Sgn = ""
).

:- func digits_to_string(list(digit)) = string.
digits_to_string(DDs) = Str :-
list__reverse(DDs, Rev),
( Rev = [],
Str = "0"
; Rev = [R|Rs],
string__int_to_string(R, S),
list__map(digit_to_string, Rs, Ss),
string__append_list([S|Ss], Str)
).

:- pred digit_to_string(digit, string).
:- mode digit_to_string(in, out) is det.
digit_to_string(D, S) :-
string__int_to_string(D, S1),
Width = log10base,

:- pred big_quot_rem(integer, integer, integer, integer).
:- mode big_quot_rem(in, in, out, out) is det.
big_quot_rem(N1, N2, Qt, Rm) :-
( N2 = zero ->
error("big_quot_rem: division by zero")
; N1 = zero ->
Qt = zero,
Rm = N2
;
N1 = i(S1, D1),
N2 = i(S2, D2),
Qt = i(SQ, Q),
Rm = i(SR, R),
( pos_is_zero(R) ->
SR = 0
;
SR = S1
),
( pos_is_zero(Q) ->
SQ = 0
;
SQ = S1 * S2
),
Q = norm(QRR),
R = norm(RRR),
list__reverse(RR, RRR),
list__reverse(QR, QRR),
list__reverse(D1, D1R),
list__reverse(D2, D2R),
quot_rem_rev([], D1R, D2R, QR, RR)
).

% Algorithm: We take digits from the start of U (call them Ur)
% and divide by V to get a digit Q of the ratio.
% Essentially the usual long division algorithm.
% Qhat is an approximation to Q. It may be at most 2 too big.
%
% If the first digit of V is less than base/2, then
% we scale both the numerator and denominator. This
% way, we can use Knuth's[*] nifty trick for finding
% an accurate approximation to Q. That's all we use from
% Knuth; his MIX algorithm is fugly.
%
% [*] Knuth, Semi-numerical algorithms.
%
:- pred quot_rem_rev(list(digit), list(digit), list(digit), list(digit),
list(digit)).
:- mode quot_rem_rev(in, in, in, out, out) is det.
quot_rem_rev(Ur, U, V, Qt, Rm) :-
( V = [V0|_] ->
( V0 < base div 2 ->
quot_rem_rev_2(mul_by_digit_rev(M, Ur),
mul_by_digit_rev(M, U),
mul_by_digit_rev(M, V), Q, R),
Qt = Q,
Rm = div_by_digit_rev(M, R),
M = base div (V0+1)
;
quot_rem_rev_2(Ur, U, V, Qt, Rm)
)
;
error("quot_rem_rev: software error")
).

:- pred quot_rem_rev_2(list(digit), list(digit), list(digit), list(digit),
list(digit)).
:- mode quot_rem_rev_2(in, in, in, out, out) is det.
quot_rem_rev_2(Ur, U, V, Qt, Rm) :-
( pos_lt_rev(Ur, V) ->
( U = [],
Qt = [0],
Rm = Ur
; U = [Ua|Uas],
quot_rem_rev_2(UrUa, Uas, V, Quot, Rem),
Qt = [0|Quot],
Rm = Rem,
list__append(Ur, [Ua], UrUa)
)
;
( U = [],
Qt = [Q],
Rm = NewUr
; U = [Ua|Uas],
quot_rem_rev_2(NewUrUa, Uas, V, Quot, Rem),
Qt = [Q|Quot],
Rm = Rem,
list__append(NewUr, [Ua], NewUrUa)
),
NewUr = pos_sub_rev(Ur, mul_by_digit_rev(Q, V)),
( pos_geq_rev(Ur, mul_by_digit_rev(Qhat, V)) ->
Q = Qhat
; pos_geq_rev(Ur, mul_by_digit_rev(Qhat-1, V)) ->
Q = Qhat-1
;
Q = Qhat - 2
),
( length(Ur) > length(V) ->
Qhat = (U0*B+U1) div V0,
;
Qhat = U0 div V0
),
B = base
).

:- func length(list(T)) = int.
length([]) = 0.
length([_|Xs]) = 1 + length(Xs).

( HT = [Hd|_T] ->
H = Hd
;
).

:- func tail(list(T)) = list(T).
tail(HT) = T :-
( HT = [_H|Tl] ->
T = Tl
;
error("tail: []")
).

% Multiply a *reverse* list of digits (big end first)
% by a digit.
%
% Note: All functions whose name has the suffix "_rev"
% operate on such reverse lists of digits.
:- func mul_by_digit_rev(digit, list(digit)) = list(digit).
mul_by_digit_rev(D, Xs) = Rev :-
list__reverse(Xs, RXs),
Mul = mul_by_digit(D, RXs),
list__reverse(Mul, Rev).

:- func div_by_digit_rev(digit, list(digit)) = list(digit).
div_by_digit_rev(_D, []) = [].
div_by_digit_rev(D, [X|Xs]) = div_by_digit_rev_2(X, Xs, D).

:- func div_by_digit_rev_2(digit, list(digit), digit) = list(digit).
div_by_digit_rev_2(X, Xs, D) = [Q|Rest] :-
Q = X div D,
( Xs = [],
Rest = []
; Xs = [H|T],
Rest = div_by_digit_rev_2(R*base + H, T, D),
R = X rem D
).

:- func pos_sub_rev(list(digit), list(digit)) = list(digit).
pos_sub_rev(Xs, Ys) = Rev :-
list__reverse(Xs, RXs),
list__reverse(Ys, RYs),
Sum = pos_sub(RXs, RYs),
list__reverse(Sum, Rev).

:- pred pos_lt_rev(list(digit), list(digit)).
:- mode pos_lt_rev(in, in) is semidet.
pos_lt_rev(Xs, Ys) :-
list__reverse(Xs, RXs),
list__reverse(Ys, RYs),
big_cmp(i(1, RXs), i(1, RYs)) = lessthan.

:- pred pos_geq_rev(list(digit), list(digit)).
:- mode pos_geq_rev(in, in) is semidet.
pos_geq_rev(Xs, Ys) :-
list__reverse(Xs, RXs),
list__reverse(Ys, RYs),
C = big_cmp(i(1, RXs), i(1, RYs)),
( C = greaterthan ; C = equal).

:- pred pos_is_zero(list(digit)).
:- mode pos_is_zero(in) is semidet.
pos_is_zero(Ds) :-
nuke_zeros(Ds) = [].

integer:pow(A, N, P) :-
( N < integer(0) ->
error("integer:pow: negative exponent")
;
P = big_pow(A, N)
).

:- func big_pow(integer, integer) = integer.
big_pow(A, N) = P :-
( N = integer(0) ->
P = integer(1)
; big_odd(N) ->
P = A * big_pow(A, N-integer(1))
; % even
P = big_sqr(big_pow(A, N/integer(2)))
).

:- func big_sqr(integer) = integer.
big_sqr(A) = A * A.

:- pred big_odd(integer).
:- mode big_odd(in) is semidet.
big_odd(N) :-
( N = integer(0) ->
fail
;
N = i(_S, [D|_Ds]),
D mod 2 = 1
).

% A very basic check of arithmetic on big integers.

:- module bigint.
:- interface.
:- import_module io.

:- pred main(io__state, io__state).
:- mode main(di, uo) is det.

:- implementation.
:- import_module integer, string, list, require.

main -->
{
X = integer:from_string("1234567890987654321"),
Y = integer:from_string(
"98765432101234567890123400000009999111"),
Z = integer(200)
;
error("bigint:main: internal error in test")
},
test(X, Y, Z).

:- pred test(integer, integer, integer, io__state, io__state).
:- mode test(in, in, in, di, uo) is det.

test(X, Y, Z) -->
{
Plus is X + Y,
Times is X * Y,
Minus is X - Y,
Div is Y // X,
Mod is Y mod X,
integer:pow(X,Z,Pow),
fac(Z,Fac)
},
write_message("X: ", X),
write_message("Y: ", Y),
write_message("Z: ", Z),
write_message("X + Y: ", Plus),
write_message("X * Y: ", Times),
write_message("X - Y: ", Minus),
write_message("Y / X: ", Div),
write_message("Y mod X: ", Mod),
write_message("fac(Z): ", Fac),
write_message("pow(X,Z): ", Pow),
{ X0 = integer(100000), X1 = integer(3) },
write_integer(X0), io:write_string(" div mod "),
write_integer(X1), io:write_string(" = "),
write_integer(X0 div X1), io:write_string(" "),
write_integer(X0 mod X1), io:nl,
write_integer(-X0), io:write_string(" div mod "),
write_integer(X1), io:write_string(" = "),
write_integer(X0 div -X1), io:write_string(" "),
write_integer(X0 mod -X1), io:nl,
write_integer(X0), io:write_string(" div mod "),
write_integer(-X1), io:write_string(" = "),
write_integer(-X0 div X1), io:write_string(" "),
write_integer(-X0 mod X1), io:nl,
write_integer(-X0), io:write_string(" div mod "),
write_integer(-X1), io:write_string(" = "),
write_integer(-X0 div -X1), io:write_string(" "),
write_integer(-X0 mod -X1), io:nl,
write_integer(X0), io:write_string(" // rem "),
write_integer(X1), io:write_string(" = "),
write_integer(X0 // X1), io:write_string(" "),
write_integer(X0 rem X1), io:nl,
write_integer(-X0), io:write_string(" // rem "),
write_integer(X1), io:write_string(" = "),
write_integer(X0 // -X1), io:write_string(" "),
write_integer(X0 rem -X1), io:nl,
write_integer(X0), io:write_string(" // rem "),
write_integer(-X1), io:write_string(" = "),
write_integer(-X0 // X1), io:write_string(" "),
write_integer(-X0 rem X1), io:nl,
write_integer(-X0), io:write_string(" // rem "),
write_integer(-X1), io:write_string(" = "),
write_integer(-X0 // -X1), io:write_string(" "),
write_integer(-X0 rem -X1), io:nl.

:- pred write_message(string, integer, io__state, io__state).
:- mode write_message(in, in, di, uo) is det.

write_message(String, Int) -->
io__write_string(String),
{ Str = integer:to_string(Int) },
io__write_string(Str),
io__nl.

:- pred fac(integer, integer).
:- mode fac(in, out) is det.

fac(X,F) :-
( X =< integer(0) ->
F = integer(1)
;
fac(X-integer(1),F1),
F = F1 * X
).

:- pred write_integer(integer, io:state, io:state).
:- mode write_integer(in, di, uo) is det.
write_integer(X) -->
{ S = integer:to_string(X) },
io:write_string(S).

```