% \iffalse meta-comment % %% File: l3fp-round.dtx Copyright(C) 2011-2012,2014 The LaTeX3 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 %% %% http://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 released version of this bundle is available from CTAN. %% %% ----------------------------------------------------------------------- %% %% The development version of the bundle can be found at %% %% http://www.latex-project.org/svnroot/experimental/trunk/ %% %% for those people who are interested. %% %%%%%%%%%%% %% NOTE: %% %%%%%%%%%%% %% %% Snapshots taken from the repository represent work in progress and may %% not work or may contain conflicting material! We therefore ask %% people _not_ to put them into distributions, archives, etc. without %% prior consultation with the LaTeX Project Team. %% %% ----------------------------------------------------------------------- %% % %<*driver> \documentclass[full]{l3doc} \GetIdInfo$Id: l3fp-round.dtx 5354 2014-08-23 01:35:39Z bruno $ {L3 Floating-point rounding} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{^^A % The \textsf{l3fp-round} package\\ Rounding floating points^^A % \thanks{This file describes v\ExplFileVersion, % last revised \ExplFileDate.}^^A % } % % \author{^^A % The \LaTeX3 Project\thanks % {^^A % E-mail: % \href{mailto:latex-team@latex-project.org} % {latex-team@latex-project.org}^^A % }^^A % } % % \date{Released \ExplFileDate} % % \maketitle % % \begin{documentation} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3fp-round} implementation} % % \begin{macrocode} %<*initex|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. % % \subsection{Rounding tools} % % 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 \cs{c_zero} or % \cs{c_one} 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 \cs{c_zero} or \cs{c_one}. % \item \cs{@@_round_s:NNNw} \meta{sign} \meta{digit_1} \meta{digit_2} % \meta{more digits}|;| can expand to |\c_zero ;| or |\c_one ;|. % \item \cs{@@_round_neg:NNN} \meta{sign} \meta{digit_1} \meta{digit_2} % can expand to \cs{c_zero} or \cs{c_one}. % \end{itemize} % See implementation comments for details on the syntax. % % \begin{macro}[int, rEXP]{\@@_round:NNN} % \begin{macro}[aux, rEXP] % { % \@@_round_to_nearest: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 \cs{c_zero}, and otherwise % to \cs{c_one}. 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 will 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 \cs{c_zero}, but this is % superseded by \cs{@@_round_return_one:}, which instead returns % \cs{c_one}, expanding everything and removing \cs{c_zero} 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. % \begin{macrocode} \cs_new:Npn \@@_round_return_one: { \exp_after:wN \c_one \tex_romannumeral:D } \cs_new:Npn \@@_round_to_ninf:NNN #1 #2 #3 { \if_meaning:w 2 #1 \if_int_compare:w #3 > \c_zero \@@_round_return_one: \fi: \fi: \c_zero } \cs_new:Npn \@@_round_to_zero:NNN #1 #2 #3 { \c_zero } \cs_new:Npn \@@_round_to_pinf:NNN #1 #2 #3 { \if_meaning:w 0 #1 \if_int_compare:w #3 > \c_zero \@@_round_return_one: \fi: \fi: \c_zero } \cs_new:Npn \@@_round_to_nearest:NNN #1 #2 #3 { \if_int_compare:w #3 > \c_five \@@_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 } \cs_new_eq:NN \@@_round:NNN \@@_round_to_nearest:NNN % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[EXP, int]{\@@_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 |\c_zero ;| if rounding $\meta{final sign} % \meta{digit}.\meta{more digits}$ to an integer truncates, and to % |\c_one ;| 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_use:N \__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 1 + \fi: \fi: #3 ; } % \end{macrocode} % \end{macro} % % \begin{macro}[int, EXP]{\@@_round_digit:Nw} % \begin{syntax} % \cs{__int_value:w} \cs{@@_round_digit:Nw} \meta{digit} \meta{intexpr} |;| % \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 \c_one \else: \if_meaning:w 5 #1 \c_one \else: \c_zero \fi: \fi: \if_int_compare:w \__int_eval:w #2 > \c_zero \__int_eval:w \c_one + \fi: \fi: #1 } % \end{macrocode} % \end{macro} % % \begin{macro}[int, EXP]{\@@_round_neg:NNN} % \begin{macro}[aux, EXP] % { % \@@_round_to_nearest_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 \cs{c_zero} or \cs{c_one} 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 \cs{c_one}. Otherwise, \emph{i.e.}, % if the result is rounded back to the first operand, then this function % returns \cs{c_zero}. % % 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:Npn \@@_round_to_ninf_neg:NNN #1 #2 #3 { \if_meaning:w 0 #1 \if_int_compare:w #3 > \c_zero \@@_round_return_one: \fi: \fi: \c_zero } \cs_new:Npn \@@_round_to_zero_neg:NNN #1 #2 #3 { \if_int_compare:w #3 > \c_zero \@@_round_return_one: \fi: \c_zero } \cs_new:Npn \@@_round_to_pinf_neg:NNN #1 #2 #3 { \if_meaning:w 2 #1 \if_int_compare:w #3 > \c_zero \@@_round_return_one: \fi: \fi: \c_zero } \cs_new_eq:NN \@@_round_to_nearest_neg:NNN \@@_round_to_nearest: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}[aux,EXP]{\@@_round_o:Nw} % This function expects one or two arguments. % \begin{macrocode} \cs_new:Npn \@@_round_o:Nw #1#2 @ { \if_case:w \__int_eval:w \@@_array_count:n {#2} - \c_one \__int_eval_end: \@@_round:Nwn #1 #2 {0} \tex_romannumeral:D \or: \@@_round:Nww #1 #2 \tex_romannumeral:D \else: \@@_error:nffn { num-args } { \@@_round_name_from_cs:N #1 () } { 1 } { 2 } \exp_after:wN \c_nan_fp \tex_romannumeral:D \fi: -`0 } % \end{macrocode} % \end{macro} % % \begin{macro}[aux, 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}[int, EXP]{\@@_round:Nww, \@@_round:Nwn} % \begin{macro}[aux, EXP] % { % \@@_round_normal:NwNNnw , % \@@_round_normal:NnnwNNnn , % \@@_round_pack:Nw , % \@@_round_normal:NNwNnn , % \@@_round_normal_end:wwNnn , % \@@_round_special:NwwNnn , % \@@_round_special_aux:Nw % } % \begin{macrocode} \cs_new:Npn \@@_round:Nww #1#2 ; #3 ; { \@@_small_int:wTF #3; { \@@_round:Nwn #1#2; } { \@@_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_sixteen - #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_use:N \__int_eval:w \if_int_compare:w #2 > \c_zero 1 \__int_value:w #2 \exp_after:wN \@@_round_pack:Nw \int_use:N \__int_eval:w 1#3 + \else: \if_int_compare:w #3 > \c_zero 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 + \c_one \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 \tex_romannumeral:D -`0 \@@_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_use:N \__int_eval:w \c_one \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 \tex_romannumeral:D -`0 \@@_sanitize:Nw #1#2; {1000}{0000}{0000}{0000}; } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintChanges % % \PrintIndex