summaryrefslogtreecommitdiff
path: root/macros/latex/required/l3kernel/l3fp-round.dtx
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex/required/l3kernel/l3fp-round.dtx')
-rw-r--r--macros/latex/required/l3kernel/l3fp-round.dtx592
1 files changed, 592 insertions, 0 deletions
diff --git a/macros/latex/required/l3kernel/l3fp-round.dtx b/macros/latex/required/l3kernel/l3fp-round.dtx
new file mode 100644
index 0000000000..b96d496b22
--- /dev/null
+++ b/macros/latex/required/l3kernel/l3fp-round.dtx
@@ -0,0 +1,592 @@
+% \iffalse meta-comment
+%
+%% File: l3fp-round.dtx
+%
+% Copyright (C) 2011-2024 The LaTeX Project
+%
+% It may be distributed and/or modified under the conditions of the
+% LaTeX Project Public License (LPPL), either version 1.3c of this
+% license or (at your option) any later version. The latest version
+% of this license is in the file
+%
+% https://www.latex-project.org/lppl.txt
+%
+% This file is part of the "l3kernel bundle" (The Work in LPPL)
+% and all files in that bundle must be distributed together.
+%
+% -----------------------------------------------------------------------
+%
+% The development version of the bundle can be found at
+%
+% https://github.com/latex3/latex3
+%
+% for those people who are interested.
+%
+%<*driver>
+\documentclass[full,kernel]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3fp-round} module\\ Rounding floating points^^A
+% }
+%
+% \author{^^A
+% The \LaTeX{} Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2024-05-08}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3fp-round} implementation}
+%
+% \begin{macrocode}
+%<*package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=fp>
+% \end{macrocode}
+%
+% ^^A todo: provide an interface for rounding modes.
+% ^^A todo: provide a \l_@@_rounding_mode_int giving the current mode.
+% ^^A todo: make transcendental function obey the correct rounding mode.
+% ^^A todo: optimize all rounding functions for various rounding modes.
+% ^^A todo: reduce the number of almost identical functions.
+%
+% \begin{macro}[EXP]
+% {
+% \@@_parse_word_trunc:N,
+% \@@_parse_word_floor:N,
+% \@@_parse_word_ceil:N
+% }
+% \begin{macrocode}
+\cs_new:Npn \@@_parse_word_trunc:N
+ { \@@_parse_function:NNN \@@_round_o:Nw \@@_round_to_zero:NNN }
+\cs_new:Npn \@@_parse_word_floor:N
+ { \@@_parse_function:NNN \@@_round_o:Nw \@@_round_to_ninf:NNN }
+\cs_new:Npn \@@_parse_word_ceil:N
+ { \@@_parse_function:NNN \@@_round_o:Nw \@@_round_to_pinf:NNN }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]
+% {
+% \@@_parse_word_round:N, \@@_parse_round:Nw,
+% }
+% \begin{macrocode}
+\cs_new:Npn \@@_parse_word_round:N #1#2
+ {
+ \@@_parse_function:NNN
+ \@@_round_o:Nw \@@_round_to_nearest:NNN #1
+ #2
+ }
+\cs_new:Npn \@@_parse_round:Nw #1 #2 \@@_round_to_nearest:NNN #3#4
+ { #2 #1 #3 }
+
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Rounding tools}
+%
+% \begin{variable}{\c_@@_five_int}
+% This is used as the half-point for which numbers are rounded
+% up/down.
+% \begin{macrocode}
+\int_const:Nn \c_@@_five_int { 5 }
+% \end{macrocode}
+% \end{variable}
+%
+% Floating point operations often yield a result that cannot be exactly
+% represented in a significand with $16$ digits. In that case, we need to
+% round the exact result to a representable number. The \textsc{ieee}
+% standard defines four rounding modes:
+% \begin{itemize}
+% \item Round to nearest: round to the representable floating point
+% number whose absolute difference with the exact result is the
+% smallest. If the exact result lies exactly at the mid-point
+% between two consecutive representable floating point numbers,
+% round to the floating point number whose last digit is even.
+% \item Round towards negative infinity: round to the greatest
+% floating point number not larger than the exact result.
+% \item Round towards zero: round to a floating point number with the
+% same sign as the exact result, with the largest absolute value not
+% larger than the absolute value of the exact result.
+% \item Round towards positive infinity: round to the least floating
+% point number not smaller than the exact result.
+% \end{itemize}
+% This is not fully implemented in \pkg{l3fp} yet, and transcendental
+% functions fall back on the \enquote{round to nearest} mode. All
+% rounding for basic algebra is done through the functions defined in
+% this module, which can be redefined to change their rounding behaviour
+% (but there is not interface for that yet).
+%
+% The rounding tools available in this module are many variations on a
+% base function \cs{@@_round:NNN}, which expands to |0\exp_stop_f:| or
+% |1\exp_stop_f:| depending on whether the final result should be rounded up
+% or down.
+% \begin{itemize}
+% \item \cs{@@_round:NNN} \meta{sign} \meta{digit_1} \meta{digit_2}
+% can expand to |0\exp_stop_f:| or |1\exp_stop_f:|.
+% \item \cs{@@_round_s:NNNw} \meta{sign} \meta{digit_1} \meta{digit_2}
+% \meta{more digits}|;| can expand to |0\exp_stop_f:;| or |1\exp_stop_f:;|.
+% \item \cs{@@_round_neg:NNN} \meta{sign} \meta{digit_1} \meta{digit_2}
+% can expand to |0\exp_stop_f:| or |1\exp_stop_f:|.
+% \end{itemize}
+% See implementation comments for details on the syntax.
+%
+% \begin{macro}[rEXP]{\@@_round:NNN}
+% \begin{macro}[rEXP]
+% {
+% \@@_round_to_nearest:NNN,
+% \@@_round_to_nearest_ninf:NNN,
+% \@@_round_to_nearest_zero:NNN,
+% \@@_round_to_nearest_pinf:NNN,
+% \@@_round_to_ninf:NNN,
+% \@@_round_to_zero:NNN,
+% \@@_round_to_pinf:NNN
+% }
+% \begin{syntax}
+% \cs{@@_round:NNN} \meta{final sign} \meta{digit_1} \meta{digit_2}
+% \end{syntax}
+% If rounding the number $\meta{final sign}
+% \meta{digit_1}.\meta{digit_2}$ to an integer rounds it towards zero
+% (truncates it), this function expands to |0\exp_stop_f:|, and otherwise
+% to |1\exp_stop_f:|. Typically used within the scope of an
+% \cs{@@_int_eval:w}, to add~$1$ if needed, and thereby round
+% correctly. The result depends on the rounding mode.
+%
+% It is very important that \meta{final sign} be the final sign of the
+% result. Otherwise, the result would be incorrect in the case of
+% rounding towards~$-\infty$ or towards~$+\infty$. Also recall that
+% \meta{final sign} is~$0$ for positive, and~$2$ for negative.
+%
+% By default, the functions below return |0\exp_stop_f:|, but this is
+% superseded by \cs{@@_round_return_one:}, which instead returns
+% |1\exp_stop_f:|, expanding everything and removing |0\exp_stop_f:| in the
+% process. In the case of rounding towards~$\pm\infty$ or
+% towards~$0$, this is not really useful, but it prepares us for the
+% \enquote{round to nearest, ties to even} mode.
+%
+% The \enquote{round to nearest} mode is the default. If the
+% \meta{digit_2} is larger than~$5$, then round up. If it is less
+% than~$5$, round down. If it is exactly $5$, then round such that
+% \meta{digit_1} plus the result is even. In other words, round up if
+% \meta{digit_1} is odd.
+%
+% The \enquote{round to nearest} mode has three variants, which differ
+% in how ties are rounded: down towards $-\infty$, truncated towards $0$,
+% or up towards $+\infty$.
+% \begin{macrocode}
+\cs_new:Npn \@@_round_return_one:
+ { \exp_after:wN 1 \exp_after:wN \exp_stop_f: \exp:w }
+\cs_new:Npn \@@_round_to_ninf:NNN #1 #2 #3
+ {
+ \if_meaning:w 2 #1
+ \if_int_compare:w #3 > \c_zero_int
+ \@@_round_return_one:
+ \fi:
+ \fi:
+ \c_zero_int
+ }
+\cs_new:Npn \@@_round_to_zero:NNN #1 #2 #3 { \c_zero_int }
+\cs_new:Npn \@@_round_to_pinf:NNN #1 #2 #3
+ {
+ \if_meaning:w 0 #1
+ \if_int_compare:w #3 > \c_zero_int
+ \@@_round_return_one:
+ \fi:
+ \fi:
+ \c_zero_int
+ }
+\cs_new:Npn \@@_round_to_nearest:NNN #1 #2 #3
+ {
+ \if_int_compare:w #3 > \c_@@_five_int
+ \@@_round_return_one:
+ \else:
+ \if_meaning:w 5 #3
+ \if_int_odd:w #2 \exp_stop_f:
+ \@@_round_return_one:
+ \fi:
+ \fi:
+ \fi:
+ \c_zero_int
+ }
+\cs_new:Npn \@@_round_to_nearest_ninf:NNN #1 #2 #3
+ {
+ \if_int_compare:w #3 > \c_@@_five_int
+ \@@_round_return_one:
+ \else:
+ \if_meaning:w 5 #3
+ \if_meaning:w 2 #1
+ \@@_round_return_one:
+ \fi:
+ \fi:
+ \fi:
+ \c_zero_int
+ }
+\cs_new:Npn \@@_round_to_nearest_zero:NNN #1 #2 #3
+ {
+ \if_int_compare:w #3 > \c_@@_five_int
+ \@@_round_return_one:
+ \fi:
+ \c_zero_int
+ }
+\cs_new:Npn \@@_round_to_nearest_pinf:NNN #1 #2 #3
+ {
+ \if_int_compare:w #3 > \c_@@_five_int
+ \@@_round_return_one:
+ \else:
+ \if_meaning:w 5 #3
+ \if_meaning:w 0 #1
+ \@@_round_return_one:
+ \fi:
+ \fi:
+ \fi:
+ \c_zero_int
+ }
+\cs_new_eq:NN \@@_round:NNN \@@_round_to_nearest:NNN
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_round_s:NNNw}
+% \begin{syntax}
+% \cs{@@_round_s:NNNw} \meta{final sign} \meta{digit} \meta{more digits} |;|
+% \end{syntax}
+% Similar to \cs{@@_round:NNN}, but with an extra semicolon, this
+% function expands to |0\exp_stop_f:;| if rounding $\meta{final sign}
+% \meta{digit}.\meta{more digits}$ to an integer truncates, and to
+% |1\exp_stop_f:;| otherwise. The \meta{more digits} part must be a digit,
+% followed by something that does not overflow a \cs{int_use:N}
+% \cs{@@_int_eval:w} construction. The only relevant information about
+% this piece is whether it is zero or not.
+% \begin{macrocode}
+\cs_new:Npn \@@_round_s:NNNw #1 #2 #3 #4;
+ {
+ \exp_after:wN \@@_round:NNN
+ \exp_after:wN #1
+ \exp_after:wN #2
+ \int_value:w \@@_int_eval:w
+ \if_int_odd:w 0 \if_meaning:w 0 #3 1 \fi:
+ \if_meaning:w 5 #3 1 \fi:
+ \exp_stop_f:
+ \if_int_compare:w \@@_int_eval:w #4 > \c_zero_int
+ 1 +
+ \fi:
+ \fi:
+ #3
+ ;
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_round_digit:Nw}
+% \begin{syntax}
+% \cs{int_value:w} \cs{@@_round_digit:Nw} \meta{digit} \meta{int expr} |;|
+% \end{syntax}
+% This function should always be called within an \cs{int_value:w}
+% or \cs{@@_int_eval:w} expansion; it may add an extra
+% \cs{@@_int_eval:w}, which means that the integer or integer
+% expression should not be ended with a synonym of \tn{relax}, but
+% with a semi-colon for instance.
+% \begin{macrocode}
+\cs_new:Npn \@@_round_digit:Nw #1 #2;
+ {
+ \if_int_odd:w \if_meaning:w 0 #1 1 \else:
+ \if_meaning:w 5 #1 1 \else:
+ 0 \fi: \fi: \exp_stop_f:
+ \if_int_compare:w \@@_int_eval:w #2 > \c_zero_int
+ \@@_int_eval:w 1 +
+ \fi:
+ \fi:
+ #1
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_round_neg:NNN}
+% \begin{macro}[EXP]
+% {
+% \@@_round_to_nearest_neg:NNN,
+% \@@_round_to_nearest_ninf_neg:NNN,
+% \@@_round_to_nearest_zero_neg:NNN,
+% \@@_round_to_nearest_pinf_neg:NNN,
+% \@@_round_to_ninf_neg:NNN,
+% \@@_round_to_zero_neg:NNN,
+% \@@_round_to_pinf_neg:NNN
+% }
+% \begin{syntax}
+% \cs{@@_round_neg:NNN} \meta{final sign} \meta{digit_1} \meta{digit_2}
+% \end{syntax}
+% This expands to |0\exp_stop_f:| or |1\exp_stop_f:| after doing the following
+% test. Starting from a number of
+% the form $ \meta{final sign}0.\meta{15 digits}\meta{digit_1} $ with exactly
+% $15$ (non-all-zero) digits before \meta{digit_1}, subtract from it
+% $\meta{final sign}0.0\ldots{}0\meta{digit_2}$, where there are $16$~zeros.
+% If in the current rounding mode the result should be rounded down,
+% then this function returns |1\exp_stop_f:|. Otherwise, \emph{i.e.},
+% if the result is rounded back to the first operand, then this function
+% returns |0\exp_stop_f:|.
+%
+% It turns out that this negative \enquote{round to nearest}
+% is identical to the positive one. And this is the default mode.
+% \begin{macrocode}
+\cs_new_eq:NN \@@_round_to_ninf_neg:NNN \@@_round_to_pinf:NNN
+\cs_new:Npn \@@_round_to_zero_neg:NNN #1 #2 #3
+ {
+ \if_int_compare:w #3 > \c_zero_int
+ \@@_round_return_one:
+ \fi:
+ \c_zero_int
+ }
+\cs_new_eq:NN \@@_round_to_pinf_neg:NNN \@@_round_to_ninf:NNN
+\cs_new_eq:NN \@@_round_to_nearest_neg:NNN \@@_round_to_nearest:NNN
+\cs_new_eq:NN \@@_round_to_nearest_ninf_neg:NNN
+ \@@_round_to_nearest_pinf:NNN
+\cs_new:Npn \@@_round_to_nearest_zero_neg:NNN #1 #2 #3
+ {
+ \if_int_compare:w #3 < \c_@@_five_int \else:
+ \@@_round_return_one:
+ \fi:
+ \c_zero_int
+ }
+\cs_new_eq:NN \@@_round_to_nearest_pinf_neg:NNN
+ \@@_round_to_nearest_ninf:NNN
+\cs_new_eq:NN \@@_round_neg:NNN \@@_round_to_nearest_neg:NNN
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{The \texttt{round} function}
+%
+% ^^A todo: This macro is intermingled with l3fp-parse.
+% ^^A todo: Add explanations.
+% \begin{macro}[EXP]{\@@_round_o:Nw, \@@_round_aux_o:Nw}
+% First check that all arguments are floating point numbers.
+% The |trunc|, |ceil| and |floor| functions expect one or two
+% arguments (the second is $0$ by default), and the |round| function
+% also accepts a third argument (\texttt{nan} by default), which
+% changes |#1| from \cs{@@_round_to_nearest:NNN} to one of its
+% analogues.
+% \begin{macrocode}
+\cs_new:Npn \@@_round_o:Nw #1
+ {
+ \@@_parse_function_all_fp_o:fnw
+ { \@@_round_name_from_cs:N #1 }
+ { \@@_round_aux_o:Nw #1 }
+ }
+\cs_new:Npn \@@_round_aux_o:Nw #1#2 @
+ {
+ \if_case:w
+ \@@_int_eval:w \@@_array_count:n {#2} \@@_int_eval_end:
+ \@@_round_no_arg_o:Nw #1 \exp:w
+ \or: \@@_round:Nwn #1 #2 {0} \exp:w
+ \or: \@@_round:Nww #1 #2 \exp:w
+ \else: \@@_round:Nwww #1 #2 @ \exp:w
+ \fi:
+ \exp_after:wN \exp_end:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_round_no_arg_o:Nw}
+% \begin{macrocode}
+\cs_new:Npn \@@_round_no_arg_o:Nw #1
+ {
+ \cs_if_eq:NNTF #1 \@@_round_to_nearest:NNN
+ { \@@_error:nnnn { num-args } { round () } { 1 } { 3 } }
+ {
+ \@@_error:nffn { num-args }
+ { \@@_round_name_from_cs:N #1 () } { 1 } { 2 }
+ }
+ \exp_after:wN \c_nan_fp
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_round:Nwww}
+% Having three arguments is only allowed for |round|, not |trunc|,
+% |ceil|, |floor|, so check for that case. If all is well, construct
+% one of \cs{@@_round_to_nearest:NNN}, \cs{@@_round_to_nearest_zero:NNN},
+% \cs{@@_round_to_nearest_ninf:NNN}, \cs{@@_round_to_nearest_pinf:NNN}
+% and act accordingly.
+% \begin{macrocode}
+\cs_new:Npn \@@_round:Nwww #1#2 ; #3 ; \s_@@ \@@_chk:w #4#5#6 ; #7 @
+ {
+ \cs_if_eq:NNTF #1 \@@_round_to_nearest:NNN
+ {
+ \tl_if_empty:nTF {#7}
+ {
+ \exp_args:Nc \@@_round:Nww
+ {
+ @@_round_to_nearest
+ \if_meaning:w 0 #4 _zero \else:
+ \if_case:w #5 \exp_stop_f: _pinf \or: \else: _ninf \fi: \fi:
+ :NNN
+ }
+ #2 ; #3 ;
+ }
+ {
+ \@@_error:nnnn { num-args } { round () } { 1 } { 3 }
+ \exp_after:wN \c_nan_fp
+ }
+ }
+ {
+ \@@_error:nffn { num-args }
+ { \@@_round_name_from_cs:N #1 () } { 1 } { 2 }
+ \exp_after:wN \c_nan_fp
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_round_name_from_cs:N}
+% \begin{macrocode}
+\cs_new:Npn \@@_round_name_from_cs:N #1
+ {
+ \cs_if_eq:NNTF #1 \@@_round_to_zero:NNN { trunc }
+ {
+ \cs_if_eq:NNTF #1 \@@_round_to_ninf:NNN { floor }
+ {
+ \cs_if_eq:NNTF #1 \@@_round_to_pinf:NNN { ceil }
+ { round }
+ }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_round:Nww, \@@_round:Nwn}
+% \begin{macro}[EXP]
+% {
+% \@@_round_normal:NwNNnw ,
+% \@@_round_normal:NnnwNNnn ,
+% \@@_round_pack:Nw ,
+% \@@_round_normal:NNwNnn ,
+% \@@_round_normal_end:wwNnn ,
+% \@@_round_special:NwwNnn ,
+% \@@_round_special_aux:Nw
+% }
+% If the number of digits to round to is an integer or infinity all is
+% good; if it is \texttt{nan} then just produce a \texttt{nan};
+% otherwise invalid as we have something like |round(1,3.14)| where
+% the number of digits is not an integer.
+% \begin{macrocode}
+\cs_new:Npn \@@_round:Nww #1#2 ; #3 ;
+ {
+ \@@_small_int:wTF #3; { \@@_round:Nwn #1#2; }
+ {
+ \if:w 3 \@@_kind:w #3 ;
+ \exp_after:wN \use_i:nn
+ \else:
+ \exp_after:wN \use_ii:nn
+ \fi:
+ { \exp_after:wN \c_nan_fp }
+ {
+ \@@_invalid_operation_tl_o:ff
+ { \@@_round_name_from_cs:N #1 }
+ { \@@_array_to_clist:n { #2; #3; } }
+ }
+ }
+ }
+\cs_new:Npn \@@_round:Nwn #1 \s_@@ \@@_chk:w #2#3#4; #5
+ {
+ \if_meaning:w 1 #2
+ \exp_after:wN \@@_round_normal:NwNNnw
+ \exp_after:wN #1
+ \int_value:w #5
+ \else:
+ \exp_after:wN \@@_exp_after_o:w
+ \fi:
+ \s_@@ \@@_chk:w #2#3#4;
+ }
+\cs_new:Npn \@@_round_normal:NwNNnw #1#2 \s_@@ \@@_chk:w 1#3#4#5;
+ {
+ \@@_decimate:nNnnnn { \c_@@_prec_int - #4 - #2 }
+ \@@_round_normal:NnnwNNnn #5 #1 #3 {#4} {#2}
+ }
+\cs_new:Npn \@@_round_normal:NnnwNNnn #1#2#3#4; #5#6
+ {
+ \exp_after:wN \@@_round_normal:NNwNnn
+ \int_value:w \@@_int_eval:w
+ \if_int_compare:w #2 > \c_zero_int
+ 1 \int_value:w #2
+ \exp_after:wN \@@_round_pack:Nw
+ \int_value:w \@@_int_eval:w 1#3 +
+ \else:
+ \if_int_compare:w #3 > \c_zero_int
+ 1 \int_value:w #3 +
+ \fi:
+ \fi:
+ \exp_after:wN #5
+ \exp_after:wN #6
+ \use_none:nnnnnnn #3
+ #1
+ \@@_int_eval_end:
+ 0000 0000 0000 0000 ; #6
+ }
+\cs_new:Npn \@@_round_pack:Nw #1
+ { \if_meaning:w 2 #1 + 1 \fi: \@@_int_eval_end: }
+\cs_new:Npn \@@_round_normal:NNwNnn #1 #2
+ {
+ \if_meaning:w 0 #2
+ \exp_after:wN \@@_round_special:NwwNnn
+ \exp_after:wN #1
+ \fi:
+ \@@_pack_twice_four:wNNNNNNNN
+ \@@_pack_twice_four:wNNNNNNNN
+ \@@_round_normal_end:wwNnn
+ ; #2
+ }
+\cs_new:Npn \@@_round_normal_end:wwNnn #1;#2;#3#4#5
+ {
+ \exp_after:wN \@@_exp_after_o:w \exp:w \exp_end_continue_f:w
+ \@@_sanitize:Nw #3 #4 ; #1 ;
+ }
+\cs_new:Npn \@@_round_special:NwwNnn #1#2;#3;#4#5#6
+ {
+ \if_meaning:w 0 #1
+ \@@_case_return:nw
+ { \exp_after:wN \@@_zero_fp:N \exp_after:wN #4 }
+ \else:
+ \exp_after:wN \@@_round_special_aux:Nw
+ \exp_after:wN #4
+ \int_value:w \@@_int_eval:w 1
+ \if_meaning:w 1 #1 -#6 \else: +#5 \fi:
+ \fi:
+ ;
+ }
+\cs_new:Npn \@@_round_special_aux:Nw #1#2;
+ {
+ \exp_after:wN \@@_exp_after_o:w \exp:w \exp_end_continue_f:w
+ \@@_sanitize:Nw #1#2; {1000}{0000}{0000}{0000};
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macrocode}
+%</package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintChanges
+%
+% \PrintIndex