% % div16.tex -- perform 16.16 bit fixed point division % by pts@fazekas.hu at Fri Dec 27 22:40:52 CET 2002 --- ... % % This .tex file contains TeX macros for double precision multiplication, % division of integer and real numbers and image size scaling. The most % remarkable macro in this file is \@@muldivletposdim (see the comments in % front of it for detailed documentation). % % If you process this file with % Plain TeX, the unit regression testsuite will be run, and the results of % the operations issued at the end of this file will be printed to the % terminal and into the .log file. % % The documentation of this macro package is scattered through this file as % embedded TeX comments. % % History % ~~~~~~~ % started at Fri Dec 27 22:40:52 CET 2002 % --- Sat Dec 28 01:26:52 CET 2002 % 31.31 bit arithmetic: Tue Jan 7 18:12:53 CET 2003 % skippable, partial \@@muldivpos at Tue Jan 7 21:00:17 CET 2003 % skippable, finished, doesn't work at Tue Jan 7 21:43:25 CET 2003 % first successful test case at Tue Jan 7 22:34:32 CET 2003 % all test cases OK at Tue Jan 7 23:30:32 CET 2003 % Fri Jan 24 12:56:23 CET 2003 % uses laemu.sty % \expandafter\ifx\csname ifLaTeX\endcsname\relax\input laemu.sty\relax\fi% \NeedsTeXFormat{LaTeX2e} \ProvidesPackage{div16b}[2003/01/24 v0.0 high precision div and mul] % % SUXX: `\count0=2\count1' and `\advance\count0 by 2\count1' disallowed % Dat: `\count0=-\count1' and `\advance\count0 by -\count1' OK % % Imp: @@ namespace % % \def\@empty{} % from laemu.sty % \catcode`\@=11 % \makeatletter %\def\loop#1\repeat{% % \def\iterate{#1\relax\expandafter\iterate\fi}% % \iterate \let\iterate\relax% %}% %\let\repeat\fi% % vvv so we can nest a \@@loop inside \loop or vica versa \def\@@loop#1\@@repeat{% \def\@@iterate{#1\relax\expandafter\@@iterate\fi}% \@@iterate \let\@@iterate\relax% }% \let\@@repeat\fi% %\repeat %alma %\iftrue %korte %\loop \def\@@gobbleend#1\end{}% %** `\@@aftergroupbegin ... \end' types `...' token-by-token \aftergroup %** Argument of \@@aftergroupbegin must not contain \end, {, or } \def\@@aftergroupbegin#1{% \ifx#1\end% %\message{done} \else% %\message{\string#1}% \aftergroup#1% \expandafter\@@aftergroupbegin% \fi% }% %** Doesn't make a \global assignment to #1, but keeps its value after the %** the next `}'. %** @param #1 name of a \count, \dimen or \skip register (i.e \count0 or %** \jot) \def\@@noresetaftergroup#1{% \@@aftergroupbegin#1=\end% \expandafter\@@aftergroupbegin\the#1 \end% % ^^^ The space before \end is required, because in LaTeX, \end is expandable \aftergroup\relax% finish assignment }% %** Doesn't work if the definition of #1 contains strange tokens %** @param #1 a macro \cs name \def\@@keepdefaftergroup#1{% \aftergroup\def% \aftergroup#1% \aftergroup{% \expandafter\@@aftergroupbegin#1\end% \aftergroup}% }% %** Performs the `#1 := #2' assignment, avoids `Dimension too large' error if %** both #1 and #2 are \counts. %** @param #1 a \count or \dimen register specification %** @param #2 a \count or \dimen register specification, an integer, a real %** number or a dimension. Numbers are assumed to have unit `sp'. \def\@@letdimcount#1#2{{% \afterassignment\@@letdimcount@a\count0=#2 \end% % Dat: now \@@letdimcount@c contains any of "", "sp", ".45", ".45pt" \afterassignment\@@gobbleend\dimen0=1\@@letdimcount@c sp \end% "1", "1sp" "1.45sp" "1.45pt" % Dat: 1.99999999sp == 1sp \ifdim\dimen0=1sp% %\showthe\count0 \message{\the\count0-sp}% \afterassignment\@@gobbleend#1=\count0 sp \end% \else% \afterassignment\@@gobbleend\dimen0=#2 sp \end% \afterassignment\@@gobbleend#1=\dimen0 sp \end% \fi% \@@noresetaftergroup{#1}% return(\T) }}% \def\@@letdimcount@a#1\end{\def\@@letdimcount@c{#1}}% %** Performs the `#1 := #2' assignment, doesn't avoid `Dimension too large' %** error if both #1 and #2 are \counts. %** `\@@letdimcount#1#2' is the same as `\@@letdimcountany#1#2{sp}', but %** \@@letdimcount avoids the error. %** @param #1 a \count or \dimen register specification %** @param #2 a \count or \dimen register specification, an integer, a real %** number or a dimension. Numbers are assumed to have unit `#3'. %** @param #3 default metric unit \def\@@letdimcountany#1#2#3{{% \afterassignment\@@letdimcount@a\dimen0=#2 #3 bp\end% #1=\dimen0% \@@noresetaftergroup{#1}% return(\T) }}% \iffalse % Regression test for \@@letdimcount: \@@letdimcount{\count0}{2147483647} \message{\the\count0} \@@letdimcount{\count0}{2147483647sp} \message{\the\count0} \@@letdimcount{\count0}{2147483647.0sp} \message{\the\count0} \@@letdimcount{\count0}{2147483647.999999sp} \message{\the\count0} \@@letdimcount{\count0}{16383.99999pt} \message{\the\count0} \@@letdimcount{\dimen0}{16383.99999pt} \message{\the\dimen0} %\@@letdimcount{\count0}{16384pt} % Dimension too large. %\@@letdimcount{\dimen0}{2000000000} % Dimension too large. \@@letdimcount{\dimen0}{\count0} \message{\the\dimen0} \fi % --- % !! why no \space in \write? %** Performs the assignment `#1 := \dimen0 / \dimen1', %** @param #1 is a name of a TeX dimen/count register (i.e \jot or \count7). %** If dimen, then it contains quotient in dimension `pt' (i.e 1in / 1cm %** is 2.54pt). If count, then its value divided by 65536 is the quotient %** (so this is 16.16 bit fixed-point notation). %** \dimen0 must not be negative. \dimen1 must be positive. %** Overwrites \dimen2. \def\@@divletpos#1{% \dimen2=1pt\relax% \multiply#1 0% `#1 := 0' works for both \dimen and \count registers \loop\ifdim\dimen0>0pt% \immediate\write16{0 a=\the\dimen0:b=\the\dimen1:m=\the\dimen2:q=\the#1} \@@loop\ifdim\dimen1<0.5\dimen0% \advance\dimen1\dimen1% \advance\dimen2\dimen2% % ^^^ this is the only statement where an arithmetic overflow can occur. % an arithmetic overflow means that the divisor is zero is very % small positive, so the result will be a ``dimen too large'' \@@repeat% \immediate\write16{1 a=\the\dimen0\space b=\the\dimen1\space m=\the\dimen2\space q=\the#1} \@@loop\ifdim\dimen1>\dimen0% \divide\dimen1 2% \divide\dimen2 2% \ifdim\dimen2=0pt% \dimen0\dimen2% \dimen1\dimen2% \fi% \@@repeat% \immediate\write16{2 a=\the\dimen0\space b=\the\dimen1\space m=\the\dimen2\space q=\the#1} \ifdim\dimen0>0pt% \@@loop\ifdim\dimen0<8192pt% \multiply\dimen0 2% \multiply\dimen1 2% \@@repeat% \fi% \immediate\write16{3 a=\the\dimen0\space b=\the\dimen1\space m=\the\dimen2\space q=\the#1} \advance\dimen0-\dimen1% \advance#1\dimen2% % ^^^ Dat: because of this, \dimen2 must be a dimen (and not a count) \immediate\write16{4 a=\the\dimen0\space b=\the\dimen1\space m=\the\dimen2\space q=\the#1} \repeat% %\ifdim\dimen0>0pt \message{SUXX}\fi% }% %** The same as \@@divletpos, except for #2 and #3 may be either positive or %** negative, and they might be either dimen or count registers. (A count is %** measured in sp.) %** Please don't pass \dimen0, \dimen1 or \dimen2 as argument. %** @param #1 is a name of a TeX dimen/count register (i.e \jot or \count7). %** If dimen, then it contains quotient in dimension `pt' (i.e 1in / 1cm %** is 2.54pt). If count, then its value divided by 65536 is the quotient %** (so this is 16.16 bit fixed-point notation). %** @param #2 and #3 are token lists %** containing a _positive_ integer or floating point value %** (measured in sp, must be specified explicitly), or a dimen such as `2.54cm'. \def\@@divletdim#1#2#3{{% \afterassignment\@@gobbleend\dimen0=#2 sp\end% \dimen0 := #1, convert count -> sp \afterassignment\@@gobbleend\dimen1=#3 sp\end% \ifdim\dimen1=0pt% \divide\dimen0 by0% trigger a TeX division-by-zero error \else% \ifdim\dimen1<0pt% \dimen1-\dimen1% \ifdim\dimen0<0pt% \dimen0-\dimen0% \@@divletpos{#1}% \else \@@divletpos{#1}% #1-#1% \fi% \else% \ifdim\dimen0<0pt% \dimen0-\dimen0% \@@divletpos{#1}% #1-#1% \else \@@divletpos{#1}% \fi% \fi% \fi% % Imp: round to nearest integer \@@noresetaftergroup{#1}% }}% % --- Dobule (high) precision multiplication and division. % The type co31.31 denotes two \count registers, representing a nonnegative % value in 31.31 bit fixed point notation. The sign bits of the registers % are unused. %** Subtracts #3#4 from #1#2, modifies #1#2 in place %** @param #1 \control-sequence on less %** @param #2 \control-sequence on greater-or-equal %** @param #3#4 co31.31 %** @param #5#6 co31.31 \def\@@dolt#1#2#3#4#5#6{% \ifnum#3<#5% \expandafter#1% \else% \ifnum#3>#5% \expandafter#2% \else% \ifnum#4<#6% \expandafter#1% \else% \expandafter#2% \fi% \fi% \fi% }% %** Subtracts #3#4 from #1#2, modifies #1#2 in place %** @param #1#2 co31.31 %** @param #3#4 co31.31 \def\@@letsub#1#2#3#4#5{% \ifnum#2<#4% \advance#1-1% \advance#2-#4% \advance#2-#5% #2 += 0x80000000 \else% \advance#2-#4% \fi% \advance#1-#3% }% %** Adds #3#4 to #1#2, modifies #1#2 in place %** @param #1#2 co31.31 %** @param #3#4 co31.31 %** @param #5 \countx, whose value is -0x80000000 (-2147483648) \def\@@letadd#1#2#3#4#5{% % \message{add:\the#3:\the#4}% \advance#2#5% #2 -= 0x80000000 \ifnum#2<-#4% orig#2-0x80000000<-#4 <=> orig#2+#4 < 0x80000000 \advance#2-#5% #2 += 0x80000000 \else% \advance#1 1% \fi% \advance#2#4% \advance#1#3% }% %** Multiplies #1#2 by two, modifies #1#2 in place %** @param #1#2 co31.31 \def\@@lettwice#1#2{% \advance#1#1% \ifnum#2>1073741823% \advance#1 1% \advance#2-1073741824% \fi% \advance#2#2% }% %** Divides #1#2 by two, discards LSB, modifies #1#2 in place %** @param #1#2 co31.31 \def\@@lethalf#1#2{% \divide#2 2% \ifodd#1% \advance#2 1073741824% \fi% \divide#1 2% }% % vvv SUXX: \newif is `\outer' \newif\ifLOOP % \def\x{ \expandafter\newif\csname ifLOOP\endcsname}% %** `\@@muldivletposdim#1#2#3#4' performs the assignment `#1 := #2 * #3 / #4'. %** `\@@muldivletposdim#1{1pt}#3#4' performs the assignment `#1 := #3 / #4'. %** `\@@muldivletposdim#1#2#3{1pt}' performs the assignment `#1 := #2 * #3'. %** %** All source arguments (#2, #3, #4) can be valid TeX dimen/count values, or %** \dimen / \count register specifications. Input values may be negative or %** positive or zero. %** %** -- Before each multiplication and division, the arguments are converted %** to `pt' (if the argument is a bare number or a \count register, it is %** treated as being a dimension in `sp'), the operation is performed on %** the numbers, and `pt' is added %** to the resulting number, so the result will be a dimension. %** -- The default unit of measure is `sp'. That is, all bare numbers and %** \count registers are treated as being a dimension in `sp'. This %** applies to both source and target argument. (You may notice that 1pt %** == 65536sp. So the operations are performed in a fixed-point %** arithmetic with 16 bits in the fraction.) %** -- The multiplication is performed exactly (i.e without loss of %** precision). However, the division and the 31.31 -> 15.16 rounding %** after the division may degrade precision. Internal calculations are %** performed in fixed-point arithmetic with 31 bits in the fraction. %** -- The following constants are valid source arguments, and represent the %** same number (one and a half): `1.5pt', `98304sp', `98304 sp', %** `98304', `98304.1sp', `98304.9sp'. %** -- To specify the real number `1.5', please specify `1.5 pt'. %** -- To specify the integer `42', please specify `42 pt'. %** -- If you do only division, set #2 := `1pt'. In that special case you may %** specify bare integers (or `... sp') for _both_ #3 and #4. %** -- The following are valid arguments: `\count0', %** `\dimen7', `\jot', `\interdisplaylinepenalty' %** -- Due to a limitation of TeX's fixed-precision arithmetic, it is %** impossible to specify exactly `1.5pt' in inches. `0.02075958251953in' %** is `1.49974pt', and `0.02075958251954in' is `1.50084pt'. (Of course, %** `1.5pt' is `1.5pt'.) That's because TeX converts the real constant %** 0.02075958251953 to the fraction `1360/65536' and %** 0.02075958251954 to the fraction `1361/65536', before multiplying. %** -- If you specify >=2 of \dimen0, \dimen1 and \dimen2 as input, please %** specify them in this order. %** %** @param #1 target, must be a \dimen or \count register %** specification (including `\count0' and `\jot'). If a \dimen register, %** its maximum will be 1073741823sp. If a \count register, its maximum %** will be 2147483647. %** @param #2 must be >=0 %** @param #3 must be >=0 %** @param #4 must be >0 %** \def\@@muldivletposdim#1#2#3#4{{% %\afterassignment\@@gobbleend\dimen0=#2 sp\end% \dimen0 := #1, convert count -> sp %\afterassignment\@@gobbleend\dimen1=#3 sp\end% %\afterassignment\@@gobbleend\dimen2=#4 sp\end% \countdef\Y 2 \@@letdimcount\Y{#4}% \countdef\F 14 \ifnum\Y<0 \Y-\Y \F-1 \else \F1 \fi% % \showthe\Y% \ifnum0=\Y % SUXX: \ifnum\Y=0 doesn't work \divide\Y0% trigger a TeX division-by-zero error (`arithmetic overflow') \else% % vvv declare my input arguments as local variables \countdef\XA 0 \@@letdimcount\XA{#2}% treat dimen as in `sp' \ifnum\XA<0 \XA-\XA \F-\F\fi% \countdef\XB 1 \@@letdimcount\XB{#3}% \ifnum\XB<0 \XB-\XB \F-\F\fi% %\countdef\XA 0 \XA\dimen0% treat dimen as in `sp' %\countdef\XB 1 \XB\dimen1% %\countdef\Y 2 \Y \dimen2% % vvv declare local variables \countdef\C 3% \countdef\B 4% \countdef\R 5% \countdef\S 6% \countdef\T 7% \countdef\U 8% \countdef\D 9% \countdef\A 10% \countdef\L 11% \countdef\M 12% % vvv ++++ comment out the line below to get rid of the error ++++ \countdef\G 13 \relax \G-1073741824 \advance\G\G% G := -0x80000000 % % Imp: support negative numbers here \ifnum\XB=\Y% shortcut \T\XA% \else% \ifnum\XA=\Y% shortcut \T\XB% \else% really multiply and divide \@@muldivpos% % % vvv convert (round) \T\U: co31.31 -> \T: dimen15.16 % return (\U>=0x7fffc000) ? ((\T+1)<<16) : (\T<<16)+((\U+0x4000)>>15) \multiply\T65536% result too large \ifnum\U<2147467264% 0x80000000-0x4000 \advance\U16384% \divide\U32768% \advance\T\U% \else% \advance\T65536% \fi% \fi% \fi% \ifnum\F<0 \T-\T\fi% %\afterassignment\@@gobbleend#1=\T sp\end% works if #1 is a dimen/count \@@letdimcount{#1}\T% \@@noresetaftergroup{#1}% return(\T) \fi% else of division-by-zero }}% %** Performs the assignment `\T\U := \XA * \XB / \Y'. Destroys contents of %** \XA \XB \Y \C \B \R \S \T \U \D \A \L and \M during operations. All %** control sequences mentioned in this paragraph must be predefined \count %** registers. %** Assumes \newif\LOOP, sets \LOOPfalse upon return %** Inputs are: \XA \XB \Y and \G. \XA \XB \Y must be in 15.16 bit %** fixed-point notation (i.e dimension in `pt' converted to a \count). %** \G must be -0x80000000. %** Outputs are \T and \U. \T\U is a real number in 31.31 bit fixed-point %** notation (the sign bits are unused). %** This is helper routine called by \@@muldivletposdim. \def\@@muldivpos{% %\message{A}% \R\XA \divide\R65536 \S-\R \multiply\S65536 \advance\S\XA% my(\R,\S)=(( \XA >>16), \XA &0xffff); %\message{A.S=\the\S,R=\the\XA}% \T\XB \divide\T65536 \U-\T \multiply\U65536 \advance\U\XB% my(\T,\U)=(( \XB >>16), \XB &0xffff); \D0 \A\Y \divide\A65536 \multiply\A32768 % my (\D,\A)=(0, ((( \Y >>16)&0xffff)<<15)); % round() final result \L\R \advance\L\L \multiply\L\T \M0 \@@letadd\D\A\L\M\G% \@@letadd( (\D,\A), ((\R*\T)<<1, 0) \L\R \multiply\L\U \M\L \divide\L32768 \multiply\L32768 \advance\M-\L \divide\L32768 \multiply\M65536 \@@letadd\D\A\L\M\G% \@@letadd( (\D,\A), ((\R*\U)>>15, ((\R*\U)&0x7fff)<<16) \L\S \multiply\L\T \M\L \divide\L32768 \multiply\L32768 \advance\M-\L \divide\L32768 \multiply\M65536 \@@letadd\D\A\L\M\G% \@@letadd( (\D,\A), ((\S*\T)>>15, ((\S*\T)&0x7fff)<<16) \ifnum\S>32767 % \L0 \M\U \multiply\M32768 \@@letadd\D\A\L\M\G% \@@letadd( (\D,\A), (0, \U<<15) \advance\S-32768% \fi% %\message{B.S=\the\S,U=\the\U}% \L0 \M\S \multiply\M\U \@@letadd\D\A\L\M\G% \@@letadd( (\D,\A), (0, \S*\U) \C\Y \divide\C 32768 \B-\C \multiply\B32768 \advance\B\Y \multiply\B65536 % my(\C,\B)=(( \Y >>15),(( \Y &0x7fff)<<16)); \L1 \M0 \T0 \U0% my (\L,\M)=(1,0); (\T,\U)=(0,0); %\showthe\D \showthe\A \showthe\C \showthe\B %\message{C}% \loop% vvv while (\D!=0 or \A!=0) % DA != 0 % \D!=0 <=> \D>0 %\message{if}% \LOOPfalse% \ifnum\D>0 \LOOPtrue \fi% Dat: must have a space after `>0' \ifnum\A>0 \LOOPtrue \fi% \ifLOOP% loop condition \R\D \S\A \@@lethalf\R\S% (\R,\S)=(\D,\A); _Half(\R,\S); \@@loop% vvv while (_lt((\C,\B),(\R,\S))) % while (CB32767\divide\T0\fi% die "overflow2 or division by 0" if \T>=0x8000 % ^^^ !! Imp: more meaningful overflow error message (here and at other places) % return value: \T\U: co31.31 % to convert it to \dimen, do: (\U>=0x7fffc000) ? ((\T+1)<<16) : (\T<<16)+((\U+0x4000)>>15) }% \endinput%