Make my infinite squares math-proof
Update The issue with scaling in graphicx
is fixed in the development sources for the next release (probably January 2017) and the issue with the xetex driver discussed in the comments is fixed and already on CTAN.
The main issue is that the division macro in graphics was written to do a reasonable job for normal image scaling ranges without using more than a few dozen bytes of code. Basically it fails here.
You can use the division from expl3
which is vastly more accurate (and uses several orders of magnitude more code:-)
Then you avoid the arithmetic overflow, although if you push it too far you run out of main memory unless you start fiddling with texmf.cnf or use luatex and dynamic memory allocation.
\documentclass{article}
\usepackage{graphicx}
\def\squaretext#1{%
\begin{tabular}{@{}l @{}r @{}}
\multicolumn{2}{c}{#1}\\
\rotatebox[origin=c]{90}{#1} & \rotatebox[origin=c]{270}{#1}\\
\multicolumn{2}{c}{\rotatebox[origin=c]{180}{#1}}\\
\end{tabular}%
}
\newdimen\scalefactor
\newdimen\outerboxwidth
\newdimen\innerboxwidth
\long\def\infinitesquares#1{%
\scalefactor=12pt
\outerboxwidth=20\scalefactor % any bigger results in arithmetic overflow
\noindent
\loop
\innerboxwidth=\dimexpr\outerboxwidth-\scalefactor
\resizebox{\outerboxwidth}{!}{\squaretext{#1}}%
\kern-\innerboxwidth%
\advance\outerboxwidth by -2\scalefactor
\advance\scalefactor by -.1\scalefactor % any different results in TeX capacity exceeded
\ifdim\outerboxwidth > \scalefactor
\repeat
}
\usepackage{expl3}
\ExplSyntaxOn
\cs_set:cpn { Gscale@div } #1 #2 #3
{
\cs_set:Npx #1
{
\fp_to_decimal:n
{
\dim_to_decimal_in_sp:n { #2 } / \dim_to_decimal_in_sp:n { #3 }
}
}
}
\ExplSyntaxOff
\begin{document}
\def\knuth{This is \TeX, a document compiler intended to produce typesetting of high quality.} % texdoc tex, line 1
\def\ezekiel{And the altar shall be twelve cubits long, twelve broad, square in the four squares thereof.} % Ezekiel 43:15, KJV
\errorcontextlines500
\infinitesquares{\knuth}
\vfill
\infinitesquares{\ezekiel}
\end{document}
As diagnosed by David Carlisle, the culprit is graphics
's \Gscale@div
. Indeed notice that the employed algorithm may create overflows, a typical example being with 8192
divided by 0.75
(in theory, it should be allowed to divide it by as small as 0.5+epsilon
, to obey the <16384
constraint on the result from \maxdimen=16384pt-1sp
, taking as premise that the final result itself is produced via \the
applied to a dimen register)
This answer has now four parts A, B, C, D displayed in order C+D, B, A.
A: original answer which defines a one-liner replacement of
\Gscale@div
for dividing two lengths, using e-TeX (and skipping some checks done by original\Gscale@div
). I know there is a similar expression elsewhere on this site, which I have hit against recently I recall it was in some post from 2011 or 2012.B: a replacement of
\Gscale@div
which does not use e-TeX facilities and is written using the usual scratch macros and registers of LaTeX. The computation is guaranteed not to create overflow if the (rounded) result is at most16384 - 2/65536
. As discussed below, when the divisor is at most0.5pt
a truncation, but when it is at least0.5pt
a rounding of the exact ratio is done to an integer multiple ofsp
unit (=1/65536
). For people wanting rounding in all cases for coherence, see part D.C: here on top, a
\divdef
macro for dividing two TeX dimensions, with no restriction whatsoever on the size of the result. Initially I wrote it starting from B and with dividing counts in mind, but as the result is a fixed point decimal with up to five digits after the mark, and as the only way to use this as input in TeX (natively, with no extra lines of code) is to consider it as a dimension (in pt, usually), in the end I again assumed the inputs to be dimension resgisters or specifications (the\setlength
part is dropped --- and its overhead whencalc
package is used --- as this is written for being usable in Plain too.) This can be used directly in Plain TeX. The\divdef
macro is a very small variant of the\Gscale@div
or part B, which exploits the fact that the latter separates the integer part from the decimal part (but some pieces of the code use operations on counts where\Gscale@div
of Part B uses dimensions). I hesitated about again using\the
on a dimension to do the final conversion to decimal writing, because it could be handled otherwise, but then one would have to worry about recovering the same rounding exactly as TeX would do via\the
. The ratioR
is computed as the nearest integral multiple of1/65536
(ties go to even; but see next item for more precise description) and then (its fractional part is) converted to a decimal number via TeX's\the
.D: actually the code in C and B has two branches; the above description about rounding applies when the divisor is
>0.5pt
. For a divisor at most0.5pt
the fraction is truncated, not rounded, to nearest multiple of1/65536
, before conversion to decimal expansion via\the
. Thus in part D, the code is modified to always do the rounding, with ties go to even. I hesitated about this becauseTeX
's\divide
truncates rather than rounds. On the other hande-TeX
's/
operator (which allows only integral divisors, by the way) rounds (which is a cause of annoyance sometimes), but it rounds away from zero, not obeying the "ties go to even" rule.
Note 1: naturally the 4 or 5 decimal digits are illusory precision when the integer part is large, in the sense that the dimensions we divide may have already incorporated roundings of a much lesser precision. But it is the mathematically precise result of evaluating the ratio of the integers which internally code the two dimensions as integral multiples of sp unit.
Note 2: as the result may (largely!) exceed \maxdimen
as a multiple of pt
unit, the code in C and D should set a flag to tell when this is the case, so that subsequent routines can make decisions based on that.
Part C+D. "divide two dimensions and output a fixed point number (with at most 4 or 5 decimals after the mark)" macro, with no condition whatsoever on the size (does not use e-TeX, written for Plain)
The image below illustrate division with a small divisor, hence truncation is done as explained above. Notice that for comparison if one does
\dimen@ 3pt
\divide\dimen@ by 10
\the\dimen@
TeX prints 0.29999pt
, which is coherent with what one observes below for example in case of division of \maxdimen
by 10
. This is due to the fact that the code below truncates the exact ratio when the divisor is small (at most than 0.5pt
).
But here is now the same thing when using Part D code which rounds in all cases: (observe the differences)
In both cases an interesting issue is illustrated by 1073741823/37
. If we go straight to decimal expansion (without the intermediate rounding/truncating to an integer multiple of 1/65536
) the correct fractional part is .27027...
(and is barely bigger). But because of the intermediate step either as in Part C or in Part D code, we end up with .27026
as fractional part.
Code for Part C:
% plain TeX, mais j'ai besoin de \loop à la LaTeX
\catcode`\@ 11
\countdef\ddf@cnta=\z@
\countdef\ddf@cntb=\tw@
\countdef\ddf@cntc=4
\countdef\ddf@cntd=6
% LaTeX loop or any loop allowing \else\repeat:
\long\def\loop #1\repeat{%
\def \iterate {#1\relax \expandafter \iterate \fi}\iterate
\let \iterate \relax }
\def\divdef #1#2#3{%
% description:
% computes R = #2/#3 as nearest multiple of 1/65536 (ties go to even)
% then define the macro #1 to be the decimal expansion of this up to five digits
% after decimal mark the last job being made by TeX's \the.
% the computation is guaranteed overflow free (it is not
% checked if #3 = 0). But there is not much one can do with the
% raw output if it exceeds `\maxdimen`. Notice that trailing zeroes
% of the fractional part are suppressed, and if nothing remains,
% the result put into #1 has no decimal mark either.
% note: if #3<32768sp=0.5pt, the ratio R is not rounded, but truncated
% to nearest integer multiple of 1/65536 (before decimal conversion).
\begingroup
\dimen@ #3\relax % denominator
\dimen@ii #2\relax % numerator
\ifdim\dimen@<\z@
\dimen@-\dimen@
\dimen@ii-\dimen@ii
\fi
\ifdim\dimen@ii<\z@
\def\ddf@sgn{-}\dimen@ii-\dimen@ii
\else
\let\ddf@sgn\empty % no \@empty in Plain !
\fi
\ddf@cnta\dimen@ % non negative denominator (we hope non zero...)
\ddf@cntb\dimen@ii % non negative numerator
\divide\ddf@cntb\ddf@cnta % integer part of ratio, will be stored in \ddf@cntd
\ddf@cntd\ddf@cntb
\multiply\ddf@cntb-\ddf@cnta % no overflow possible because TeX's division truncates
\advance\ddf@cntb\dimen@ii % now numerator in \ddf@cntb is < denom
\count@\z@ % will store fractional part as a multiple of sp's
\ifnum\ddf@cntb>\z@
\ifnum\ddf@cnta>32768\relax
\ddf@cnta65536\relax
\loop
\ddf@cntc\dimen@ % denominator
\advance\ddf@cntc-\ddf@cntb
\ifnum\ddf@cntb<\ddf@cntc
\divide\ddf@cnta\tw@
\advance\ddf@cntb\ddf@cntb
\else
\ifnum\ddf@cntb=\ddf@cntc
\divide\ddf@cnta\tw@
\advance\count@\ddf@cnta
\ddf@cnta\z@ % abort the loop here
\else
\advance\count@\ddf@cnta % not same order as in previous branch!
\divide\ddf@cnta\tw@
\ddf@cnta-\ddf@cnta
\advance\ddf@cntc\ddf@cntc
\ddf@cntb\ddf@cntc
\fi
\fi
\ifnum\ddf@cnta=\z@\else % signed quantity: can not do if foo>\z@ ...
\repeat
% it is possible here that \count@ is 65536
\else
% here denom <= 2^15=32768 (=0.5pt), hence 65536num < 2^31
\multiply\ddf@cntb65536\relax
\divide\ddf@cntb\ddf@cnta
\count@\ddf@cntb % at most 65535 because division truncates
\fi
\fi
\dimen@\count@ sp\relax
\expandafter\divdef@end\the\dimen@ #1%
}
\begingroup
\catcode`P 12
\catcode`T 12
\lowercase{\gdef\divdef@end #1.#2PT}#3{%
\advance\ddf@cntd #1\relax % almost always #1=0
\ifnum#2>\z@
\edef\x{\endgroup\edef\noexpand#3{\ddf@sgn\the\ddf@cntd.#2}}%
\else
\ddf@cntd\ddf@sgn\ddf@cntd
\edef\x{\endgroup\edef\noexpand#3{\the\ddf@cntd}}%
\fi
\x
}\endgroup
\input xinttools.sty
\def\TAB{&}\def\CR{\cr}
\tabskip 20pt
\halign {#\hfil &#\hfil \cr
\xintFor* #1 in {\xintSeq {1}{50}}\do
{\divdef\x \maxdimen{#1sp}\number\maxdimen/#1=\x
\ifodd #1 \TAB\else\expandafter\CR\fi
}}
% test
% \dimen@ 3pt
% \divide\dimen@ by 10
% \the\dimen@
\bye
Code for Part D (it differs only in the lines in the denom <= 2^15=32768
branch):
\catcode`\@ 11
\countdef\ddf@cnta=\z@
\countdef\ddf@cntb=\tw@
\countdef\ddf@cntc=4
\countdef\ddf@cntd=6
% LaTeX loop or any loop allowing \else\repeat:
\long\def\loop #1\repeat{%
\def \iterate {#1\relax \expandafter \iterate \fi}\iterate
\let \iterate \relax }
\def\divdef #1#2#3{%
% description:
% computes R = #2/#3 as nearest multiple of 1/65536 (ties go to even)
% then define the macro #1 to be the decimal expansion of this up to five digits
% after decimal mark the last job being made by TeX's \the.
\begingroup
\dimen@ #3\relax % denominator
\dimen@ii #2\relax % numerator
\ifdim\dimen@<\z@
\dimen@-\dimen@
\dimen@ii-\dimen@ii
\fi
\ifdim\dimen@ii<\z@
\def\ddf@sgn{-}\dimen@ii-\dimen@ii
\else
\let\ddf@sgn\empty % no \@empty in Plain !
\fi
\ddf@cnta\dimen@ % non negative denominator (we hope non zero...)
\ddf@cntb\dimen@ii % non negative numerator
\divide\ddf@cntb\ddf@cnta % integer part of ratio, will be stored in \ddf@cntd
\ddf@cntd\ddf@cntb
\multiply\ddf@cntb-\ddf@cnta % no overflow possible because TeX's division truncates
\advance\ddf@cntb\dimen@ii % now numerator in \ddf@cntb is < denom
\count@\z@ % will store fractional part as a multiple of sp's
\ifnum\ddf@cntb>\z@
\ifnum\ddf@cnta>32768\relax
\ddf@cnta65536\relax
\loop
\ddf@cntc\dimen@ % denominator
\advance\ddf@cntc-\ddf@cntb
\ifnum\ddf@cntb<\ddf@cntc
\divide\ddf@cnta\tw@
\advance\ddf@cntb\ddf@cntb
\else
\ifnum\ddf@cntb=\ddf@cntc
\divide\ddf@cnta\tw@
\advance\count@\ddf@cnta
\ddf@cnta\z@ % abort the loop here
\else
\advance\count@\ddf@cnta % not same order as in previous branch!
\divide\ddf@cnta\tw@
\ddf@cnta-\ddf@cnta
\advance\ddf@cntc\ddf@cntc
\ddf@cntb\ddf@cntc
\fi
\fi
\ifnum\ddf@cnta=\z@\else % signed quantity: can not do if foo>\z@ ...
\repeat
% it is possible here that \count@ is 65536
% in case of a tie at the last unit the rounding was to even!
\else
% here denom <= 2^15=32768 (=0.5pt), hence 65536num <= 2^31 - 65536
\multiply\ddf@cntb65536\relax
% extra steps to do rounding
\ddf@cntc\ddf@cnta
\divide\ddf@cntc\tw@
\advance\ddf@cntb\ddf@cntc % no overflow possible
\ddf@cntc\ddf@cntb % need to keep copy for later branch
\divide\ddf@cntc\ddf@cnta
\count@\ddf@cntc
\ifodd\ddf@cnta
% odd denom, no tie possible
\else
\multiply\ddf@cntc\ddf@cnta
\ifnum\ddf@cntb=\ddf@cntc
% implement "ties go to even", the rounding was "up"
\ifodd\count@\advance\count@\m@ne\fi
\fi
\fi
% to get \count@ 65536 we would need to have N/D >= 65535.5/65536
% i.e. N/D >= 1 - 1/131072, but N/D<= 1 - 1/D, D<=32768, hence
% despite the rounding this branch always produces \count@ < 65536.
\fi
\fi
\dimen@\count@ sp\relax
% (\the\count@) % debug check
\expandafter\divdef@end\the\dimen@ #1%
}
\begingroup
\catcode`P 12
\catcode`T 12
\lowercase{\gdef\divdef@end #1.#2PT}#3{%
\advance\ddf@cntd #1\relax % almost always #1=0
\ifnum#2>\z@
\edef\x{\endgroup\edef\noexpand#3{\ddf@sgn\the\ddf@cntd.#2}}%
\else
\ddf@cntd\ddf@sgn\ddf@cntd
\edef\x{\endgroup\edef\noexpand#3{\the\ddf@cntd}}%
\fi
\x
}\endgroup
\input xinttools.sty
\def\TAB{&}\def\CR{\cr}
\tabskip 20pt
\halign {#\hfil &#\hfil \cr
\xintFor* #1 in {\xintSeq {1}{50}}\do
{\divdef\x \maxdimen{#1sp}\number\maxdimen/#1=\x
\ifodd #1 \TAB\else\expandafter\CR\fi
}}
Part B: a non-eTeX \Gscale@div
drop-in replacement
Here is an attempt to divide 8192
by 0.75
:
\Gscale@div\x {8192pt}{.75pt}
./betterdiv.tex:138: Arithmetic overflow.
<recently read> \dimen@ii
l.138 \Gscale@div\x {8192pt}{.75pt}
Here a pure Knuth TeX (*) approach to make a macro doing the precise
computation with guarantee of no overflow (if rounded theoretical exact value
is at most 16384-2/65536
.) I tried to follow the style of LaTeX and graphics.sty in using macros and temporary registers.
(*) the \loop
must like the LaTeX one allow \else\repeat
construct.
\documentclass{article}
\usepackage{graphicx}
\makeatletter
% new definition of \Gscale@div avoiding arithmetic overflows,
% (does NOT use e-TeX !... hence some sweat and efforts ...)
\def\Gscale@div#1#2#3{%
\setlength\dimen@{#3}%
\ifdim\dimen@=\z@
\PackageError{graphics}{Division by 0}\@eha
\dimen@#2%
\fi
\setlength\dimen@ii{#2}%
\ifdim\dimen@<\z@
\dimen@-\dimen@
\dimen@ii-\dimen@ii
\fi
\ifdim\dimen@ii<\z@
% Attention! \@tempa, \@tempb, \@tempc in use in graphics.sty across calls
\def\Gscale@sgn{-}\dimen@ii-\dimen@ii
\else
\let\Gscale@sgn\@empty
\fi
\@tempcnta\dimen@ % denominator
\@tempcntb\dimen@ii % numerator
\divide\@tempcntb\@tempcnta
\@tempdimb\@tempcntb\p@ % integer part of ratio. Naturally, may overflow!
\multiply\@tempcntb-\@tempcnta
\advance\@tempcntb\dimen@ii
\dimen@ii\@tempcntb sp\relax % now num<denom. No \@sp like \p@ ?
\count@\z@ % will store fractional part as a multiple of sp's
\ifdim\dimen@ii>\z@
\ifdim\dimen@>32768sp\relax
\@tempcnta65536\relax
\loop
\@tempdima\dimen@ % denominator
\advance\@tempdima-\dimen@ii
\ifdim\dimen@ii<\@tempdima
\divide\@tempcnta\tw@
\advance\dimen@ii\dimen@ii
\else
\ifdim\dimen@ii=\@tempdima
\divide\@tempcnta\tw@
\advance\count@\@tempcnta
\@tempcnta\z@ % abort the loop here
\else
\advance\count@\@tempcnta % not same order as in previous branch!
\divide\@tempcnta\tw@
\@tempcnta-\@tempcnta
\advance\@tempdima\@tempdima
\dimen@ii\@tempdima
\fi
\fi
\ifnum\@tempcnta=\z@\else % signed quantity: can not do if foo>\z@ ...
\repeat
\else
% here denom <= 2^15=32768 (=0.5pt), hence 65536num < 2^31
\multiply\@tempcntb65536\relax
\divide\@tempcntb\@tempcnta
\count@\@tempcntb
\fi
\fi
\dimen@\count@ sp\relax
\advance\dimen@\@tempdimb
\dimen@\Gscale@sgn\dimen@
\edef#1{\strip@pt\dimen@}%
}
\makeatother
%\usepackage{xinttools}
\begin{document}
% test
\makeatletter
% \xintFor* #1 in {\xintSeq {1}{100}}\do {
% \Gscale@div\x{1pt}{#1pt}
% % \Gscale@div\y{1pt}{-#1pt}
% % \Gscale@div\z{-1pt}{#1pt}
% % \Gscale@div\t{-1pt}{-#1pt}
% #1, \x, %\y, \z, \t
% }
% \xintFor* #1 in {\xintSeq {2}{100}}\do {
% \Gscale@div\x{32767sp}{#1sp}
% % \Gscale@div\y{1pt}{-#1pt}
% % \Gscale@div\z{-1pt}{#1pt}
% % \Gscale@div\t{-1pt}{-#1pt}
% #1, \x, %\y, \z, \t
% }
% \Gscale@div\x {1pt}{65536sp}
% 1pt/65536sp = \x
\Gscale@div\x {8192pt}{65535sp}
8192pt/65535sp = \x
\Gscale@div\x {8192pt}{.75pt}
8192pt/.75pt = \x
\makeatother
\end{document}
Part A: original answer
As an alternative with no package, you can define \Gscale@div
using e-TeX facilities.
\makeatletter
\def\Gscale@div #1#2#3{% defines #1 to be the ratio #2/#3, where #2 and #3
% are lengths (registers or expressions).
% The ratio #2/#3 should evaluate to less than 16384 in absolute value to
% avoid arithmetic overflow. It will be computed as fixed point
% number with about 4 or 5 digits after decimal mark.
\edef #1%
{\strip@pt\dimexpr
\numexpr\dimexpr#2\relax*65536/\dimexpr#3\relax\relax sp\relax}%
}
\makeatother
Full code (I have left original comments as in David's answer they may now be obsolete):
\documentclass{article}
\usepackage{graphicx}
\def\squaretext#1{%
\begin{tabular}{@{}l @{}r @{}}
\multicolumn{2}{c}{#1}\\
\rotatebox[origin=c]{90}{#1} & \rotatebox[origin=c]{270}{#1}\\
\multicolumn{2}{c}{\rotatebox[origin=c]{180}{#1}}\\
\end{tabular}%
}
\newdimen\scalefactor
\newdimen\outerboxwidth
\newdimen\innerboxwidth
\long\def\infinitesquares#1{%
\scalefactor=12pt
\outerboxwidth=20\scalefactor % any bigger results in arithmetic overflow
\noindent
\loop
\innerboxwidth=\dimexpr\outerboxwidth-\scalefactor
\resizebox{\outerboxwidth}{!}{\squaretext{#1}}%
\kern-\innerboxwidth%
\advance\outerboxwidth by -2\scalefactor
\advance\scalefactor by -.1\scalefactor % any different results in TeX capacity exceeded
\ifdim\outerboxwidth > \scalefactor
\repeat
}
\makeatletter
\def\Gscale@div #1#2#3{% defines #1 to be the ratio #2/#3, where #2 and #3
% are lengths (registers or expressions).
% The ratio #2/#3 should evaluate to less than 16384 in absolute value to
% avoid arithmetic overflow. It will be computed as fixed point
% number with about 4 or 5 digits after decimal mark.
\edef #1%
{\strip@pt\dimexpr
\numexpr\dimexpr#2\relax*65536/\dimexpr#3\relax\relax sp\relax}%
}
\makeatother
\begin{document}
\def\knuth{This is \TeX, a document compiler intended to produce typesetting of high quality.} % texdoc tex, line 1
\def\ezekiel{And the altar shall be twelve cubits long, twelve broad, square in the four squares thereof.} % Ezekiel 43:15, KJV
\errorcontextlines500
\infinitesquares{\knuth}
\vfill
\infinitesquares{\ezekiel}
\end{document}
There is also a genuine mathematical problem with your code. Let's call S
your scalefactor
and T
your outerboxwidth
and l
the small value you use to iterate S
as S->S-l S
. Thus l
is 0.1
in the code I copied. Also initially say you set T=NS
. For your loop not to be infinite one obtains that the following should be true:
N < 2/l
Hence for l=0.1
, the condition is N < 20
. For l=0.2
, the condition is N < 10
. For l=0.5
the condition would be N < 4
. Actually it seems to work for l=0.1
and N=20
but rounding errors conspire to help the loop terminate I guess. (see end of this post.)
The maths is the one of geometric series. Let's us write m=1-l
, so after n iterations one has
S_n = m^n S_0
T_n = T_0 - 2S_0 (1+m+..+m^{n-1})
The limit value of the T
's is
T_0 - 2S_0 1/(1-m) = (N - 2/l) S_0
Hence if N - 2/l >= 1
, all the T
's are always > S_0
and the loop is infinite.
Actually the loop terminates if for some n
, T_n <= S_n
, which we translate into
N - 2(1-m^n)/l <= m^n
N - 2/l + (2/l -1) m^n <= 0 (*)
As 0<l<1
, we have 2/l - 1 > 1
. Thus the inequality above can be realized only if N < 2/l
. And if N < 2/l
, then indeed for sufficiently large n
, it will be true that (*) holds.
Hence the mathematical condition for termination if N < 2/l
. However one observes it does work for N=20
and l=0.1
: I have not looked in detail but some rounding effect should be the explanation which helps the loop terminate. For l=0.1
I get the code to complete with N=20.05
but it raises TeX capacity exceeded
for N=20.06
.
Similarly TeX capacity exceeded
with N=4.01
and l=0.5
but it compiles with N=4
.
This is all using the \Gscale@div
from my other answer.