% This is file texdimens.tex, part of texdimens package, which % is distributed under the LPPL 1.3c. Copyright (c) 2021 Jean-François Burnol % 2021/11/17 v1.1 \edef\texdimensendinput{\endlinechar\the\endlinechar% \catcode`\noexpand _=\the\catcode`\_% \catcode`\noexpand @=\the\catcode`\@\relax\noexpand\endinput}% \endlinechar13\relax% % only for using \p@ (also \z@ now) of Plain. Check if \p@, \z@ exists? \catcode`\_=11 \catcode`\@=11 % so tempted to do \input xintkernel.sty to have some utilities... % not even a \@gobble in Plain... \def\texdimenfirstofone#1{#1}% \def\texdimengobtilminus#1-{}% \def\texdimenzerominusfork #10-#2#3\krof {#2}% % % \texdimenuu, \texdimenuudown, \texdimenuuup % =========================================== % % Mathematics % ----------- % % In the entire discussion here, "uu" stands for some core unit, % or some unit corresponding to an internal dimension > 1pt. % % Main question at the origin of this file was: % Is T sp attainable from unit "uu"?. % If not, what is largest dimension < Tsp which is? % % Here we suppose T>0. TeX parsing of D uu is equivalent to: % % D uu --> N = round(D * 65536) --> T = trunc (N * phi) % % phi>1 is the conversion factor associated to "uu" % psi=1/phi, psi<1. Define U(N, phi) = trunc (N * phi) % % U(N,phi) is thus the strictly increasing sequence, % indexed by non-negative integers, of non-negative % attainable dimensions. (in sp unit) % % T>0, then: % % U(N)<= T < U(N+1) iff N = ceil((T+1)psi) - 1 % U(M)< T <= U(M+1) iff M = ceil(T psi) - 1 % % In other words: % % - the largest attainable dimension not exceeding T sp % is obtained via the integer "Zd = ceil((T+1)psi) - 1 = N", % (i.e. find D with Zd=round(65536 D) then "D uu" is "down" % approximation) % % - the smallest attainable dimension at least equal to T sp % is obtained from the integer "Zu = ceil(T psi) = M + 1" % % - the two "Z"'s are either equal (i.e. T is attained) or Zu=Zd+1. % % \texdimenUU macros use round((T+0.5)*psi) % ----------------------------------------- % % case1: M = N, i.e. Zd T. % % As will be explained later trunc(R phi) can be computed very % easily by hijacking TeX's handling of dimensions, no \numexpr % chains is needed. % % case2: M = N - 1, i.e. T = Zd = Zu is attained: % T psi <= N < (T+1) psi, T = trunc(N phi) % % Let v=(T+0.5)psi. As v = T psi + 0.5 psi it is < N+0.5 % And as v = (T+1)psi - 0.5psi it is > N - 0.5. % So R = round(v) = N. % % We thus have the initial observation which was at the core of this % package initial release: % % - compute R = round((T+0.5) psi) % % - if T is attained, then T = trunc(R * phi) % % - if T is not attained then either { Zd = R and Zu = R+1 } or % {Zd = R-1 and Zu = R}. % % How do we check if R = Zd or Zu? We need to evaluate trunc(R phi) and % compare it with T. This trunc(R phi) can be computed the following way: % % - obtain D pt from \the\dimexpr R sp. Knuth's algorithm guarantees % that R = round(D * 65536) % % - then D uu where uu is the unit with conversion factor phi is % converted by TeX into "trunc(R phi) sp", i.e. trunc(R phi) = % \number\dimexpr Duu\relax, where D pt = \the\dimexpr Rsp\relax. % % Conclusion: % % 1. the macro \texdimenuu does the one-liner R=round((T+0.5) psi) % then \the\dimexpr Rsp\relax gives "Dpt", the "pt" is removed, % we have a decimal D such that "Duu" does what one wants. % % 2. to get Zd (resp. Zu) one can use the D obtained in 1. and check % if "D uu" is at most (or at least) the user input dimension. % % For units with conversion factor phi>2, a simplification is possible. % In that case let X = round(T psi) (it has the advantage compared to % R that we can apply the formula without checking the sign of T). % % Going back to our earlier analyis, now with psi < 0.5 (1uu>2pt) % % case1: T is not attainable % M=N=Zd < T psi < (T+1) psi <= N+1=Zu % As Zd < T psi < Zu, we have round(T psi) = Zd or Zu % % case2: T is attained, i.e. T psi <= N < (T+1) psi. % As psi<0.5, and T psi + psi > N, we have T psi > N - 0.5. % And T psi <= N so N = round(T psi). % % So, for psi < 0.5, the X=round(T psi) can play the same role as % R=round((T+0.5)psi). If T is attained, we get the decimal D from this % X and if T is not attained we know that X is either Zd or Zu. % % The computations of X and Y=trunc(X phi) can be done independently of % sign of T. But the final test has to be changed to Y < T if T < 0 and % then one must replace X by X+1. So we must filter out the sign of the % input. % % Going back to the 10.5, then it would be slightly % less costly to compute X = round(T psi) than R = round((T + 0.5) psi), % but if we then realize that trunc(X phi) < T we do not yet know if % trunc((X+1) phi) = T or is > T, i.e. we don't know if Zd =X or X+1, % and we can not tell yet if T is attained or not. % % In contrast if we find out that trunc(R phi) \maxdimen. % % For T=\maxdimen (or very close) this is what happens for the units % "dd", "nc", and "in". % % Besides, it turns out that this test which is done to decide whether % R=Zu or R=Zd, and on which the initial implementation of the macros % "up" and "down" was done at 0.9 gamma release is a bit costly. % % At 1.0 release, all the "up" and "down" macros were re-implemented % via a more stubborn usage of the ceil() based formulae for Zd and Zu. % This made all usable even with \maxdimen input and besided, proved % on average slightly faster. % % Overcoming the ceil() stumbling block for \texdimenUU{up,down} % -------------------------------------------------------------- % % I will in what follows refer to trunc(), floor() or ceil() only for % positive arguments, obtained as ratios x/y or sometimes as a numexpr % "scaling" operation" x*y/z which uses temporarily use doubled % precision. % % As \numexpr's x/y is round(x/y), with rounding away from zero, we have % access to floor(t) for t>=0 as round(t+0.5)-1 and for t>0 also as % round(t-0.5). The former may cause overflow as it involves % (2x+y)/(2y) but the latter (2x-y)/(2y) will not overflow if x comes % from a dimension as 2x<2**31 then. % % ceil(t) is more complex as it is floor(t)+1 only for t not an integer. % Let's explain how to overcome the challenge for Zd and the "in" unit, % i.e. a conversion factor of 7227/100. % % We want Zd = ceil((T+1)*100/7227) - 1, with T assumed positive. % % Let T = k*7227 + r with 0<= r < 7227, 0<=k, and r>0 if k=0. % % (T+1)*100/7227 becomes 100*k + (r+1)*100/7227 and thus % % Zd = 100 * k + ceil(x) - 1 % % with x = n*100/7227, and n = 1+r, so 00 without overflow in \numexpr this way: % % k = floor(T/7227) = round(T/7227 - 0.5) % = round((2*T - 7227) / 14454) (T>0 used here) % % r = T - 7227 * k = T modulo 7227 % % Zd = 100 * k + 100 - round( (201*7227 - 200*(r+1))/14454 ) % % Everything here is computable within \numexpr and has absolutely no % potential overflow problem at all. The same analysis can be done for % Zu = ceil(T*100/7227) and for all core TeX units. See the comments % below for all obtained formulae and some additional details. % {\catcode`p 12\catcode`t 12 \csname expandafter\endcsname\gdef\csname texdimenstrippt\endcsname#1pt{#1}}% % % pt % \def\texdimenpt#1{\expandafter\texdimenstrippt\the\dimexpr#1\relax}% % % bp 7227/7200 = 803/800 % \def\texdimenbp#1{\expandafter\texdimenstrippt\the\dimexpr\numexpr(% \expandafter\texdimen_bpnddd_signcheck \the\numexpr2*\dimexpr#1\relax\relax)*400/803sp\relax}% \def\texdimen_bpnddd_signcheck#1{\texdimengobtilminus#1-1+#1}% % % nd 685/642 % \def\texdimennd#1{\expandafter\texdimenstrippt\the\dimexpr\numexpr(% \expandafter\texdimen_bpnddd_signcheck \the\numexpr2*\dimexpr#1\relax\relax)*321/685sp\relax}% % % dd 1238/1157 % \def\texdimendd#1{\expandafter\texdimenstrippt\the\dimexpr\numexpr(% \expandafter\texdimen_bpnddd_signcheck \the\numexpr2*\dimexpr#1\relax\relax)*1157/2476sp\relax}% % % mm 7227/2540 phi now >2, use from here on the X = round(T psi) approach % \def\texdimenmm#1{\expandafter\texdimenstrippt\the\dimexpr(#1)*2540/7227\relax}% % % pc 12/1 % \def\texdimenpc#1{\expandafter\texdimenstrippt\the\dimexpr(#1)/12\relax}% % % nc 1370/107 % \def\texdimennc#1{\expandafter\texdimenstrippt\the\dimexpr(#1)*107/1370\relax}% % % cc 14856/1157 % \def\texdimencc#1{\expandafter\texdimenstrippt\the\dimexpr(#1)*1157/14856\relax}% % % cm 7227/254 % \def\texdimencm#1{\expandafter\texdimenstrippt\the\dimexpr(#1)*254/7227\relax}% % % in 7227/100 % \def\texdimenin#1{\expandafter\texdimenstrippt\the\dimexpr(#1)*100/7227\relax}% % % "up and down macros" % -------------------- % % The notation means u/v in numexpr, which does rounding % away from zero. It is essential that the argument be >-0.5 else % not same as +1. All formulae are overflow free. % % The comments are for T > 0. % % Roughly such an approach works for phi = a/b > 1, such that: % % a*(2b+1)<2**31 if a is odd, <2**32 if a is even % % This is true for all core units with quite some margin, the one with % largest a*b being phi=7227/2540 for "mm". % % Note: for a unit such as "ex" or "em" where morally b=65536=2**16, % this limits to a<=16383 if a is odd and to a<=32766 if a is even. % Thus the general \texdimenwithunit{dim1}{dim2} (which for dim2<1pt % computes basically an "up" value) can *not imitate fully* this scheme. % % The macros and formulas in the comments were obtained from a template % (see file generateupdownmacros.py at the project repository), % and we could actually combine them into a generic macro handling % general a/b (assuming above bounds are verified). % But for the the sake of efficiency, this is "rolled-out" here unit per unit. % \def\texdimenuudownup_zero#1;{\z@\relax}% \def\texdimenuudownup_neg#1-{-#1}% % bp 803/800 % T = 803 k + r % Zd = 800 k + 800 - <(1284003 - 1600 r)/1606> \def\texdimenbpdown#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenbpdown_a\the\numexpr\dimexpr#1;% }% \def\texdimenbpdown_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimenbpdown_b#1}% \def\texdimenbpdown_b#1;{\expandafter\texdimenbpdown_c\the\numexpr(2*#1-803)/1606;#1;}% \def\texdimenbpdown_c#1;#2;{\expandafter\texdimenbpdown_d\the\numexpr#2-803*#1;#1;}% \def\texdimenbpdown_d#1;#2;{\numexpr800*#2+800-(1284003-1600*#1)/1606sp\relax}% % Zu = 800 k + 800 + 1 - <(1285603 - 1600 r)/1606> \def\texdimenbpup#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenbpup_a\the\numexpr\dimexpr#1;% }% \def\texdimenbpup_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimenbpup_b#1}% \def\texdimenbpup_b#1;{\expandafter\texdimenbpup_c\the\numexpr(2*#1-803)/1606;#1;}% \def\texdimenbpup_c#1;#2;{\expandafter\texdimenbpup_d\the\numexpr#2-803*#1;#1;}% \def\texdimenbpup_d#1;#2;{\numexpr800*#2+801-(1285603-1600*#1)/1606sp\relax}% % nd 685/642 % T = 685 k + r % Zd = 642 k + 642 - <(878941 - 1284 r)/1370> \def\texdimennddown#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimennddown_a\the\numexpr\dimexpr#1;% }% \def\texdimennddown_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimennddown_b#1}% \def\texdimennddown_b#1;{\expandafter\texdimennddown_c\the\numexpr(2*#1-685)/1370;#1;}% \def\texdimennddown_c#1;#2;{\expandafter\texdimennddown_d\the\numexpr#2-685*#1;#1;}% \def\texdimennddown_d#1;#2;{\numexpr642*#2+642-(878941-1284*#1)/1370sp\relax}% % Zu = 642 k + 642 + 1 - <(880225 - 1284 r)/1370> \def\texdimenndup#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenndup_a\the\numexpr\dimexpr#1;% }% \def\texdimenndup_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimenndup_b#1}% \def\texdimenndup_b#1;{\expandafter\texdimenndup_c\the\numexpr(2*#1-685)/1370;#1;}% \def\texdimenndup_c#1;#2;{\expandafter\texdimenndup_d\the\numexpr#2-685*#1;#1;}% \def\texdimenndup_d#1;#2;{\numexpr642*#2+643-(880225-1284*#1)/1370sp\relax}% % dd 1238/1157 % T = 1238 k + r % Zd = 1157 k + 1157 - <(1431828 - 1157 r)/1238> \def\texdimendddown#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimendddown_a\the\numexpr\dimexpr#1;% }% \def\texdimendddown_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimendddown_b#1}% \def\texdimendddown_b#1;{\expandafter\texdimendddown_c\the\numexpr(#1-619)/1238;#1;}% \def\texdimendddown_c#1;#2;{\expandafter\texdimendddown_d\the\numexpr#2-1238*#1;#1;}% \def\texdimendddown_d#1;#2;{\numexpr1157*#2+1157-(1431828-1157*#1)/1238sp\relax}% % Zu = 1157 k + 1157 + 1 - <(1432985 - 1157 r)/1238> \def\texdimenddup#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenddup_a\the\numexpr\dimexpr#1;% }% \def\texdimenddup_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimenddup_b#1}% \def\texdimenddup_b#1;{\expandafter\texdimenddup_c\the\numexpr(#1-619)/1238;#1;}% \def\texdimenddup_c#1;#2;{\expandafter\texdimenddup_d\the\numexpr#2-1238*#1;#1;}% \def\texdimenddup_d#1;#2;{\numexpr1157*#2+1158-(1432985-1157*#1)/1238sp\relax}% % mm 7227/2540 % T = 7227 k + r % Zd = 2540 k + 2540 - <(36715307 - 5080 r)/14454> \def\texdimenmmdown#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenmmdown_a\the\numexpr\dimexpr#1;% }% \def\texdimenmmdown_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimenmmdown_b#1}% \def\texdimenmmdown_b#1;{\expandafter\texdimenmmdown_c\the\numexpr(2*#1-7227)/14454;#1;}% \def\texdimenmmdown_c#1;#2;{\expandafter\texdimenmmdown_d\the\numexpr#2-7227*#1;#1;}% \def\texdimenmmdown_d#1;#2;{\numexpr2540*#2+2540-(36715307-5080*#1)/14454sp\relax}% % Zu = 2540 k + 2540 + 1 - <(36720387 - 5080 r)/14454> \def\texdimenmmup#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenmmup_a\the\numexpr\dimexpr#1;% }% \def\texdimenmmup_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimenmmup_b#1}% \def\texdimenmmup_b#1;{\expandafter\texdimenmmup_c\the\numexpr(2*#1-7227)/14454;#1;}% \def\texdimenmmup_c#1;#2;{\expandafter\texdimenmmup_d\the\numexpr#2-7227*#1;#1;}% \def\texdimenmmup_d#1;#2;{\numexpr2540*#2+2541-(36720387-5080*#1)/14454sp\relax}% % pc 12/1 % T = 12 k + r % Zd = 1 k + 1 - <(17 - 1 r)/12> \def\texdimenpcdown#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenpcdown_a\the\numexpr\dimexpr#1;% }% \def\texdimenpcdown_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimenpcdown_b#1}% \def\texdimenpcdown_b#1;{\expandafter\texdimenpcdown_c\the\numexpr(#1-6)/12;#1;}% \def\texdimenpcdown_c#1;#2;{\expandafter\texdimenpcdown_d\the\numexpr#2-12*#1;#1;}% \def\texdimenpcdown_d#1;#2;{\numexpr#2+1-(17-#1)/12sp\relax}% % Zu = 1 k + 1 + 1 - <(18 - 1 r)/12> \def\texdimenpcup#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenpcup_a\the\numexpr\dimexpr#1;% }% \def\texdimenpcup_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimenpcup_b#1}% \def\texdimenpcup_b#1;{\expandafter\texdimenpcup_c\the\numexpr(#1-6)/12;#1;}% \def\texdimenpcup_c#1;#2;{\expandafter\texdimenpcup_d\the\numexpr#2-12*#1;#1;}% \def\texdimenpcup_d#1;#2;{\numexpr#2+2-(18-#1)/12sp\relax}% % nc 1370/107 % T = 1370 k + r % Zd = 107 k + 107 - <(147168 - 107 r)/1370> \def\texdimenncdown#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenncdown_a\the\numexpr\dimexpr#1;% }% \def\texdimenncdown_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimenncdown_b#1}% \def\texdimenncdown_b#1;{\expandafter\texdimenncdown_c\the\numexpr(#1-685)/1370;#1;}% \def\texdimenncdown_c#1;#2;{\expandafter\texdimenncdown_d\the\numexpr#2-1370*#1;#1;}% \def\texdimenncdown_d#1;#2;{\numexpr107*#2+107-(147168-107*#1)/1370sp\relax}% % Zu = 107 k + 107 + 1 - <(147275 - 107 r)/1370> \def\texdimenncup#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenncup_a\the\numexpr\dimexpr#1;% }% \def\texdimenncup_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimenncup_b#1}% \def\texdimenncup_b#1;{\expandafter\texdimenncup_c\the\numexpr(#1-685)/1370;#1;}% \def\texdimenncup_c#1;#2;{\expandafter\texdimenncup_d\the\numexpr#2-1370*#1;#1;}% \def\texdimenncup_d#1;#2;{\numexpr107*#2+108-(147275-107*#1)/1370sp\relax}% % cc 14856/1157 % T = 14856 k + r % Zd = 1157 k + 1157 - <(17194663 - 1157 r)/14856> \def\texdimenccdown#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenccdown_a\the\numexpr\dimexpr#1;% }% \def\texdimenccdown_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimenccdown_b#1}% \def\texdimenccdown_b#1;{\expandafter\texdimenccdown_c\the\numexpr(#1-7428)/14856;#1;}% \def\texdimenccdown_c#1;#2;{\expandafter\texdimenccdown_d\the\numexpr#2-14856*#1;#1;}% \def\texdimenccdown_d#1;#2;{\numexpr1157*#2+1157-(17194663-1157*#1)/14856sp\relax}% % Zu = 1157 k + 1157 + 1 - <(17195820 - 1157 r)/14856> \def\texdimenccup#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenccup_a\the\numexpr\dimexpr#1;% }% \def\texdimenccup_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimenccup_b#1}% \def\texdimenccup_b#1;{\expandafter\texdimenccup_c\the\numexpr(#1-7428)/14856;#1;}% \def\texdimenccup_c#1;#2;{\expandafter\texdimenccup_d\the\numexpr#2-14856*#1;#1;}% \def\texdimenccup_d#1;#2;{\numexpr1157*#2+1158-(17195820-1157*#1)/14856sp\relax}% % cm 7227/254 % T = 7227 k + r % Zd = 254 k + 254 - <(3678035 - 508 r)/14454> \def\texdimencmdown#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimencmdown_a\the\numexpr\dimexpr#1;% }% \def\texdimencmdown_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimencmdown_b#1}% \def\texdimencmdown_b#1;{\expandafter\texdimencmdown_c\the\numexpr(2*#1-7227)/14454;#1;}% \def\texdimencmdown_c#1;#2;{\expandafter\texdimencmdown_d\the\numexpr#2-7227*#1;#1;}% \def\texdimencmdown_d#1;#2;{\numexpr254*#2+254-(3678035-508*#1)/14454sp\relax}% % Zu = 254 k + 254 + 1 - <(3678543 - 508 r)/14454> \def\texdimencmup#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimencmup_a\the\numexpr\dimexpr#1;% }% \def\texdimencmup_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimencmup_b#1}% \def\texdimencmup_b#1;{\expandafter\texdimencmup_c\the\numexpr(2*#1-7227)/14454;#1;}% \def\texdimencmup_c#1;#2;{\expandafter\texdimencmup_d\the\numexpr#2-7227*#1;#1;}% \def\texdimencmup_d#1;#2;{\numexpr254*#2+255-(3678543-508*#1)/14454sp\relax}% % in 7227/100 % T = 7227 k + r % Zd = 100 k + 100 - <(1452427 - 200 r)/14454> \def\texdimenindown#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenindown_a\the\numexpr\dimexpr#1;% }% \def\texdimenindown_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimenindown_b#1}% \def\texdimenindown_b#1;{\expandafter\texdimenindown_c\the\numexpr(2*#1-7227)/14454;#1;}% \def\texdimenindown_c#1;#2;{\expandafter\texdimenindown_d\the\numexpr#2-7227*#1;#1;}% \def\texdimenindown_d#1;#2;{\numexpr#200+100-(1452427-2*#100)/14454sp\relax}% % Zu = 100 k + 100 + 1 - <(1452627 - 200 r)/14454> \def\texdimeninup#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimeninup_a\the\numexpr\dimexpr#1;% }% \def\texdimeninup_a#1{\texdimenzerominusfork #1-\texdimenuudownup_zero 0#1\texdimenuudownup_neg 0-{}% \krof \texdimeninup_b#1}% \def\texdimeninup_b#1;{\expandafter\texdimeninup_c\the\numexpr(2*#1-7227)/14454;#1;}% \def\texdimeninup_c#1;#2;{\expandafter\texdimeninup_d\the\numexpr#2-7227*#1;#1;}% \def\texdimeninup_d#1;#2;{\numexpr#200+101-(1452627-2*#100)/14454sp\relax}% % % "both in and cm" % ================ % % Mathematics % ----------- % % Let a and b be two non-negative integers such that U = floor(a 7227/100) = % floor(b 7227/254). It can be proven that a=50k, b=127k for some integer k. % The proof is left to reader. So U = floor(7227 k /2) for some k. % % Let's now find the largest such U <= T. So U = floor(k 7227/2)<= T which is % equivalent (as k is integer) to k 7227/2 <= T + 1/2, i.e. % % kmax = floor((2T+1)/7227) % % If we used for x>0 the formula floor(x)=round(x-1/2)= we would end % up basically with some 4T hence overflow problems even in \numexpr. % Here I used <.> to denote rounding in the sense of \numexpr. It is not % 1-periodical due to how negative inputs are handled, but here x-1/2>-1/2. % % The following lemma holds: let T be a non-negative integer then % % floor((2T+1)/7227) = <(2T - 3612)/7227> % % So we can compute this k, hence get a=50k, b=127k, all within \numexpr and % avoiding overflow. % % Implementation % -------------- % % Regarding the output in pt or sp, we seem to need floor(k 7227/2). % The computation of floor(k 7227/2) as <(7227 k - 1)/2> would require to % check if k==0 so we do it rather as <(7227 k + 1)/2> - 1. No overflow % can arise as k = 297147 for \maxdimen, and then 7227 k = 2**31 - 2279 and % there is ample room for 7227k+1 using \numexpr. % % But this step, as well as initial step to get kmax will require to separate % handling of negative input from positive one. % % Alternative % ----------- % % For non-negative T we can compute U = ((T+1)/7227)*7227. If U <= T keep it, % else if U > T, replace it by U - 3614. This is alternative road to the maximal % floor(k 7227/2) at most equal to T. % \def\texdimenbothincm#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenboth_a \the\numexpr\dimexpr#1\relax\relax-3612)/7227)*127sp\relax}% \def\texdimenbothcmin#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenboth_a \the\numexpr\dimexpr#1\relax\relax-3612)/7227)*50sp\relax}% \def\texdimenboth_a#1{\texdimengobtilminus#1\texdimenboth_neg-\numexpr((2*#1}% \def\texdimenboth_neg-\numexpr((2*-{-\numexpr((2*}% % \def\texdimenbothincmsp#1{\number \expandafter\texdimenbothsp_a\the\numexpr\dimexpr#1\relax\relax -3612)/7227)*7227+1)/2-1\relax}% \def\texdimenbothincmpt#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenbothsp_a\the\numexpr\dimexpr#1\relax\relax -3612)/7227)*7227+1)/2-1sp\relax}% \def\texdimenbothsp_a#1{\texdimengobtilminus#1\texdimenbothsp_neg-\numexpr(((2*#1}% \def\texdimenbothsp_neg-\numexpr(((2*-{-\numexpr(((2*}% % \let\texdimenbothcminpt\texdimenbothincmpt \let\texdimenbothcminsp\texdimenbothincmsp % % "both mm and bp" % ================ % % Mathematics and Algorithm % ------------------------- % % We start from a dimension expressed in sp unit, "T sp". Assume T positive. % We know how to get largest "X sp <= T sp" which is exactly expressible % in mm unit % i.e. can be written X=trunc(a 7227/2540) for some non-negative integer a. % We want to achieve X=trunc(b 803/800) for some b. % % Only the congruence of X modulo 803 matters for this. % It turns out that the mod 803 impossible values are 267, 535, 802. % As pointed out by Ruixi Zhang on the package repo issue #10, % when a<--a+2540, X increases by 7227=9*803 hence the value % modulo 803 does not change. Thus only "a modulo 2540" matters % to check if X(a) is attainable with bp unit. Ruixi Zhang found by % brute force that there are modulo 2540 nine excluded a-values % % Rather than checking if "a mod. 2540" avoids the 9 Ruixi Zhang values % or if "X mod. 803" avoids 267, 535, 802, we will simply basically % check if X sp = \texdimenbp{X sp}bp, as this approach is probably % about the same cost or even less than computing "X mod. 803" and % correspondingly branching. % % The key is that if "a" is bad, then "a-1" is automatically good as % pointed out by R.Z. on #10, which can be seen without knowing the 9 % bad congruences, simply by noticing that a<--a-1 modifies X either to % X-2 or X-3, so if X was bad certainly the new one is not. % % Once "a" has gotten its final value, we apply "\the\dimexpr a sp % = D pt" trick to recover the D such that "D mm" gives rise to the found % dimension. We go via this "Dmm" intermediary also to express the final % result as "X sp", because anyhow the "X" we worked with and had in % our token stream has to be recomputed if a<--a-1, so lets always % recompute it from final "a", and this goes via "D mm" (but see % the paragraph MEMO for alternative for this trunc(a 7227/2540) step). % % I will copy here the style I used for bothincm expansion triggering % via an already positioned \dimexpr waiting to output final result. \def\texdimenbothbpmm#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenbothbpmm_fork\the\numexpr\dimexpr#1;}% \def\texdimenbothbpmm_fork#1{\texdimenzerominusfork #1-\texdimenbothbpmm_zero 0#1\texdimenbothbpmm_neg 0-\texdimenbothbpmm_a \krof#1}% % because this is *inside* a pre-positioned \dimexpr, we don't have % to worry about zero output ending up as -0.0 \def\texdimenbothbpmm_neg-{-\texdimenbothbpmm_a}% \def\texdimenbothbpmm_zero#1;{\z@\relax}% % now, find X sp <= T sp maximal and expressible in mm unit % it will be X=trunc(a 7227/2540), we first get a candidate for "a" \def\texdimenbothbpmm_a#1;% {\expandafter\texdimenbothbpmm_b\the\numexpr#1*2540/7227;#1;}% % we get in a single line the X from this candidate, hijacking TeX's % built-in *7227/2540... the "MEMO" above explains one could do this % purely within \numexpr, working around its division rounds, and % avoiding overflow, but I suspect this would be more costly. \def\texdimenbothbpmm_b#1;{\expandafter\texdimenbothbpmm_c \the\numexpr\dimexpr\expandafter\texdimenstrippt\the\dimexpr#1spmm;#1;}% % now we have X;a;T; \def\texdimenbothbpmm_c#1;#2;#3;{% % If X>T, our candidate "a=#2" must be decreased by 1 and we go to _ca % The original #3 is not needed anymore \ifnum#1>#3 \expandafter\texdimenbothbpmm_ca\fi % Else we decide whether it is "a" or "a-1" we must use. I preferred % to induce a re-grabbing cost here, rather than have \texdimenbothbpmm_ca % re-grab its arguments from \texdimenbothbpmm_d replacement text. \texdimenbothbpmm_d#1;#2;% }% % Here, dynamically at the time of the concluding \dimexpr, we % check if X sp is expressible in bp unit and then use "a" or "a-1" % accordingly \def\texdimenbothbpmm_d#1;#2;{#2sp% \ifnum\dimexpr \expandafter\texdimenstrippt\the\dimexpr\numexpr(2*#1+1)*400/803spbp=#1 \else-1sp\fi % and a \relax to stop the concluding \dimexpr \relax }% % Here we must decrease "a=#2" by 1, recompute X=#1, then loop % back to \texdimenbothbpmm_d. Hesitation between forcing a % re-grab or doing it in one step with the subtraction of 1 done twice \def\texdimenbothbpmm_ca\texdimenbothbpmm_d#1;#2;% {\expandafter\texdimenbothbpmm_cb\the\numexpr#2-1;}% \def\texdimenbothbpmm_cb#1;{% \expandafter\texdimenbothbpmm_d \the\numexpr\dimexpr\expandafter\texdimenstrippt\the\dimexpr#1spmm;#1;% }% % done... % now the lazy way for \texdimenbothmmbp \def\texdimenbothmmbp#1{\expandafter\texdimenstrippt\the\dimexpr \expandafter\texdimenbothmmbp_a\the\numexpr\dimexpr\texdimenbothbpmm{#1}mm;}% % If zero at this stage, we will correctly get 0.0 in the end \def\texdimenbothmmbp_a#1#2;{\numexpr(2*#1#2+\texdimengobtilminus#1-1)*400/803sp\relax}% % \texdimenbothbpmmpt and its alias \texdimenbothmmbppt \def\texdimenbothbpmmpt#1{\texdimenpt{\texdimenbothbpmm{#1}mm}}% \let\texdimenbothmmbppt\texdimenbothbpmmpt % \texdimenbothbpmmsp and its alias \texdimenbothmmbpsp \def\texdimenbothbpmmsp#1{\the\numexpr\dimexpr\texdimenbothbpmm{#1}mm\relax\relax}% \let\texdimenbothmmbpsp\texdimenbothbpmmsp % % \texdimenwithunit % ================= % % Mathematics % ----------- % % The ex and em units are handled by TeX as if multiplying by a % conversion factor f/65536 (here f sp = 1ex resp. = 1em). % % In particular, for any decimal D, input "D em" is handled the exact % same way as input "D\dimexpr 1em\relax"; this is not % the case for the core units except for pt and pc (and sp), whose % conversion factors are the sole ones with a power of 2 denominator % (respectively 1, 1, and 65536). The further difference is that % for the core units apart from sp, the conversion factor is >1. % % We assume for this discussion T is non-negative. % If f/65536 > 1, the analysis is as above : some dimensions T sp % are not attainable as D uu, but the formula % N=round((2T+1)*32768/f) % will give a suitable decimal D via \the\dimexpr N sp\relax. % (if T=0, we get N=0 as 32768/f<0.5) % This D will let TeX convert D uu into T sp, if the dimension % is attainable else it will be a closest match % either from above or below (not necessarily nearest overall). % % If f/65536=1, attention that above formula would give N=1 for % T=0 (was bug #4). % % If f/65536<1, all dimensions Tsp are attainable as D uu. Indeed % D uu is parsed by TeX via N=round(D*65536), then T=trunc(N*phi), % with phi=f/65536. Starting from T we need to find an N such that % T/phi <= N< (T+1)/phi. % % This is equivalent to ceil(T/phi)<= N < ceil((T+1)/phi) % % Now obsolete remark: let v=(T+0.5)/phi. As its % distance to the extremities is 0.5/phi>0.5, (phi>1) its rounding M % to an integer verifies automatically T/phi < M < (T+1)/phi, so % is a valid candidate. This was used at 0.99 release. % (it is funny that N=round((2T+1)*32768/f) works for all f>0 % *except* f=65536). % % The 1.0 release chooses to implement the ceil(T/phi) formula rather as % it is closer to naive expectation "dim1/dim2" of a division. % % It is not obvious to compute this ceil(T/phi) without overflow. % % Implementation % -------------- % % \texdimenwithunit{dim1}{dim2} % % First done at 0.99, then refactored at 1.0: % - to add support for dim2<0pt % - to handle differently the dim2<1pt case and make the output % closer to mathematical dim1/dim2 % % To handle dim2<0pt, we simply simultaneously do % dim1<-- (-dim1) and dim2<-- (-dim2). % % dim2=0pt is not intercepted and will cause division by zero low-level % error. Code comments below were not adjusted and handle only % dim2>0pt. % % We first get f from dim2 and branch according to whether f>65536, % or f<=65536. % We will also need to check the sign of T (dim1=T sp). % f>65536: we compute round((2T+1)*32768/f) % f=65536: merged with f<65536 branch (as it works and avoids checking for it) % f<65536: 0.99 release used the round((2T+1)*32768/f) formula % (it is funny that it works for all f except for f=65536) % % But the output then diverges noticeably from mathematical % dim1/dim2 "=" T*65536/f, the more so the smaller the dim2. % See issue #16 and also the discussion at #13. % % 1.0 release thus opted for the ceil(T*65536/f) formula, as it is the % smallest allowable choice, hence the closest to naive dim1/dim2. % % To avoid arithmetic overflow issues we first do the euclidean % division T = k f + r, 0<= r < f, 0<= k % % The final result in "sp" unit would be k*65536 + C with % C = ceil(r * 65536/f). % % We don't do this k*65536 explicitly as it may overflow and is % anyhow unneeded: the output will be the integer k concatenated with % the decimal E given by TeX from \the\dimexpr C sp, i.e. such that % E pt = C sp, with C = ceil(r*65536/f). % % As r is at most f-1, r*65536/f is at most 65536-65536/f, and as % 65536>=f (we use this branch also for f=65536), C<=65535. Hence % E is never 1.0 but always "0." % % To compute the Euclidean quotient k in \numexpr we use there % <(2T-f)/(2f)> i.e. round((2T-f)/2f) = trunc(T/f) % as we are careful to never have T=0 in-there... % % Computing C = ceil(r * 65536/f) in \numexpr is the delicate % part, as r can be as large as f-1 hence 65535 and 65535*65536 would % overflow. Let's try anyhow to see how to compute ceil() with round(): % % C = 65536 - floor(65536 * (1 - r/f)) % = 65536 - round(65536*(f-r)/f - 0.5) (as r % % Here the problem is with small r, and large f, and naive implementation % of this formula can overflow... % Let's thus retreat to eTeX scaling operation as it % operates with temporary double precision. % % R=round(r*65536/f)= is either C-1 or C % Let x = mathematical exact r*65536/f: % - if R < x, C=R+1. % - if R >= x, C=R. % % C=ceil(r*65536/f) is the smallest integer such that % trunc(C*f/65536)>=r, or more precisely (as f<=65536) the % smallest integer with trunc(C*f/65536)=r. So trunc(R*f/65536) % will be either r (then R=C), or r-1, then R=C-1. % % Method from release 1.0: let's TeX compute P=trunc(R*f/65536) itself! % Via P sp = E where E is a decimal such that E pt = R sp. % So % - if P>=r (it is then equal to r in fact) then C=R % - if P=round(R/4), so t=-2,-1,0,+1. % % Then R*f-65536*r = 4*(S*f-16384*r)+t*f % % We know that R<=C<65536, so <= 16384 and 16384*f % is at worst 2**(14+16)=\maxdimen+1 but we will be in \numexpr, % so no overflow! % And r0, 0 % r = T - k*f % R= % S= % t=R-4*S % % IF: 4*(S*f-16384*r)+t*f < 0 THEN C=R+1 ELSE C=R. % % Ept=\the\dimexpr Csp, E=0.d...d % % End expansion with the contatenation k.d...d % \def\texdimenwithunit#1#2{\expandafter\texdimenwithunit_i % no premultiplication of dim1 by 2 as was done for technical % reasons when dim2<1pt branch used round((2T+1)*32768/f) \the\numexpr\dimexpr#2\expandafter;\the\numexpr\dimexpr#1;% }% \def\texdimenwithunit_i#1{% \texdimengobtilminus#1\texdimenwithunit_switchsigns-% \texdimenwithunit_j#1% }% \def\texdimenwithunit_switchsigns-\texdimenwithunit_j-#1;#2% {% % due to \texdimenwithunit_Bneg we can not simply prefix dim1 % with -, as -0 is bad there. So let's check also if #2 is 0 \texdimenzerominusfork #2-\texdimenwithunit_Bzero % also used in \texdimenwithunit_B 0#2\texdimenwithunit_j % abusive shortcut 0-{\texdimenwithunit_ic#2}% \krof #1;% }% \def\texdimenwithunit_ic#1#2;{\texdimenwithunit_j#2;-#1}% \def\texdimenwithunit_j#1;#2{% \ifnum#1>\p@\texdimenwithunit_A\fi \texdimenwithunit_B#2#1;% }% % unit>1pt, handle this as for bp. % Attention it would be wrong for unit=1pt! \def\texdimenwithunit_A\fi\texdimenwithunit_B#1#2;#3;{\fi \expandafter\texdimenstrippt \the\dimexpr\numexpr(2*#1#3+\texdimengobtilminus#1-1)*32768/#2sp\relax % - fine if dim1>0, <0, or =0 % - with *\p@ better but an early doubled dim2 would complicate 1pt % test and not sure if doing \p@/(2*#2) here advantageous }% % unit<=1pt. % if dim1<0, simply negate result for dim1>0 as it can not possibly be 0.0 % Indeed T*65536/f will be at least 1 so its ceil also (in fact ceil % will even be at least 2 if f<65536). % The dim1=0 case must get filtered out due to way of calculating the % "ceil" in \numexpr \def\texdimenwithunit_B#1{\texdimenzerominusfork #1-\texdimenwithunit_Bzero 0#1\texdimenwithunit_Bneg 0-\texdimenwithunit_Ba \krof#1}% \def\texdimenwithunit_Bzero#1;#2;{0.0}% \def\texdimenwithunit_Ba#1#2;#3;{% % no overflow possible from 2*#1#3 in \numexpr \expanded{\expandafter\texdimenwithunit_Bb \the\numexpr(2*#1#3-#2)/(2*#2);#1#3;#2;}% }% % I could have inserted \expanded\bgroup in \texdimenwithunit_B % but then needed to modify _Bzero (used also by \texdimenwithunit_switchsigns) % so easiest is to simply defined Bneg explicitly here rather than % insisting on deriving it from _Ba \def\texdimenwithunit_Bneg-#1;#2;{% \expanded{-\expandafter\texdimenwithunit_Bb \the\numexpr(2*#2-#1)/(2*#1);#2;#1;}% }% % now k;T;f;. Get the remainder r=T-k*f, and abandon k in the token stream. % the earlier \expanded maintains f-expandability \def\texdimenwithunit_Bb#1;#2;#3;{% #1\expandafter\texdimenwithunit_Bc\the\numexpr#2-#1*#3;#3;% }% % now r;f;. Get R= \def\texdimenwithunit_Bc#1;#2;{% \expandafter\texdimenwithunit_Bd\the\numexpr #1*\p@/#2;#1;#2;% }% % R;r;f; Is 4*(S*f-16384*r)+t*f < 0 ? with S=, t=R-4S \def\texdimenwithunit_Bd#1;#2;#3;{% \expandafter\texdimenwithunitstripzeroandpt \the\dimexpr\numexpr#1% \ifnum\numexpr 4*((#1/4)*#3-16384*#2)<\numexpr(4*(#1/4)-#1)*#3\relax +1\fi sp\relax }% {\catcode`P12\catcode`T12\lowercase{\gdef\texdimenwithunitstripzeroandpt0#1PT}{#1}}% \texdimensendinput