From c628b1aa9cf528de739c1de45acf3739561619ed Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Fri, 5 Nov 2021 03:01:04 +0000 Subject: CTAN sync 202111050301 --- macros/generic/texdimens/README.md | 530 ++----------------------------- macros/generic/texdimens/texdimens.md | 560 +++++++++++++++++++++++++++++++++ macros/generic/texdimens/texdimens.pdf | Bin 0 -> 48310 bytes macros/generic/texdimens/texdimens.sty | 2 +- macros/generic/texdimens/texdimens.tex | 137 ++++++-- 5 files changed, 704 insertions(+), 525 deletions(-) create mode 100644 macros/generic/texdimens/texdimens.md create mode 100644 macros/generic/texdimens/texdimens.pdf (limited to 'macros/generic') diff --git a/macros/generic/texdimens/README.md b/macros/generic/texdimens/README.md index ecd8631193..c0ba961f30 100644 --- a/macros/generic/texdimens/README.md +++ b/macros/generic/texdimens/README.md @@ -8,530 +8,66 @@ Copyright (c) 2021 Jean-François Burnol This file is part of the texdimens package distributed under the LPPL 1.3c. See file LICENSE.md. -Development: https://github.com/jfbu/texdimens +Repository: https://github.com/jfbu/texdimens -Release: `0.99 2021/11/02` +Release: `0.99d 2021/11/04` -## Aim of this package - -Utilities and documentation related to TeX dimensional units, usable -both with Plain (`\input texdimens`) and with LaTeX (`\usepackage{texdimens}`). - -The aim of this package is to address the issue of expressing dimensions -(or dimension expressions evaluated by `\dimexpr`) in the various TeX -units, to the extent possible. - -This project requires the e-TeX extensions `\dimexpr` and `\numexpr`. -The notation `` in the macro descriptions refers to a -*dimensional expression* as accepted by `\dimexpr`. The syntax has some -peculiarities: among them the fact that `-(...)` (for example `-(3pt)`) -is illegal, one must use alternatives such as `0pt-(...)` or a -sub-expression `-\dimexpr...\relax` for example. - -Notice that this is WIP and inaccuracies may exist even relative to -descriptions of TeX handlings due to limited time available for the -project. - -## Quick review of basics: TeX points and scaled points - -TeX dimensions are represented internally by a signed integer which is -in absolute value at most `0x3FFFFFFF`, i.e. `1073741823`. The -corresponding unit is called the "scaled point", i.e. `1sp` is `1/65536` -of one TeX point `1pt`, or rather `1pt` is represented internally as `65536`. - -If `\foo` is a dimen register: - -- `\number\foo` produces the integer `N` such as `\foo` is the same as `Nsp`, - -- inside `\numexpr`, `\foo` is replaced by `N`, - -- `\the\foo` produces a decimal `D` (with at most five places) followed - with `pt` (catcode 12 tokens) and this output `Dpt` can serve as input - in a dimen assignment to produce the same dimension as `\foo`. One can - also use the catcode 11 characters `pt` for this. Digits and decimal - mark must have their standard catcode 12. - -When TeX encounters a dimen denotation of the type `Dpt` it will -compute `N` in a way equivalent to `N = round(65536 D)` where ties are -rounded away from zero. Only 17 decimal places of `D` are -kept as it can be shown that going beyond can not change the result. - -When `\foo` has been assigned as `Dpt`, `\the\foo` will produce some -`Ept` where `E` is not necessarily the same as `D`. But it is guaranteed -that `Ept` defines the same dimension as `Dpt̀`. - -## Further units known to TeX on input - -TeX understands on input further units: `bp`, `cm`, `mm`, `in`, `pc`, -`cc`, `nc`, `dd` and `nd`. It also understands font-dependent units `ex` and -`em`, and PDFTeX adds the `px` dimension unit. Japanese engines also -add specific units. +## Usage -The `ex`, `em`, and `px` units are handled somewhat differently by (pdf)TeX than -`bp`, `cm`, `mm`, `in`, `pc`, `cc`, `nc`, `dd` and `nd` units. For the former -(let's use the generic notation `uu`), the exact same dimensions are obtained -from an input `D uu` where `D` is some decimal or from `D ` where `` -stands for some dimension register which records `1uu` or `\dimexpr1uu\relax`. In -contrast, among the latter, i.e. the core TeX units, this is false except for the -`pc` unit. +Utilities and documentation related to TeX dimensional units, usable: -The explanation is as follows (in this discussion `sp` is kept aside). +- with Plain TeX: `\input texdimens` -TeX associates (explicitly for the core units, implicitly for the units corresponding -to internal dimensions) to each unit `uu` a fraction `phi` which is a conversion -factor. For the internal dimensions `ex`, `em`, `px` or in the case of multiplying -a dimension by a decimal, this `phi` is morally `f/65536` where `f` is the integer -such that `1 uu=f sp`. For core units however, the hard-coded ratio `n/d` never has a -denominator `d` whici is a power of `2`, except for the `pc` whose associated ratio -factor is `12/1` (and arguably for the `sp` for which morally `phi` is `1/65536` but -we keep it separate from the general discussion). +- with LaTeX: `\usepackage{texdimens}` -As a result the value of `1 uu` as `f sp` is at best irrelevant -or at worst misleading regarding the way TeX parses `D uu`. Notice for example -that `1.00375` is the exact value of the `phi` factor for the `bp` unit but -that `1.00375pt>1bp` ... -Here is a table with the hard-coded conversion factors: - - uu phi reduced real approximation 1uu in sp= x=[65536phi]/65536 \the<1uu> - (Python output) [65536phi] (real approximation) - -- ---------- ------- ------------------ --------- -------------------- ---------- - bp 7227/7200 803/800 1.00375 65781 1.0037384033203125 1.00374pt - nd 685/642 same 1.0669781931464175 69925 1.0669708251953125 1.06697pt - dd 1238/1157 same 1.070008643042351 70124 1.07000732421875 1.07pt - mm 7227/2540 same 2.8452755905511813 186467 2.8452606201171875 2.84526pt - pc 12/1 12 12.0 786432 12.0 12.0pt - nc 1370/107 same 12.80373831775701 839105 12.803726196289062 12.80373pt - cc 14856/1157 same 12.84010371650821 841489 12.840103149414062 12.8401pt - cm 7227/254 same 28.45275590551181 1864679 28.452743530273438 28.45274pt - in 7227/100 same 72.27 4736286 72.26998901367188 72.26999pt - -When TeX parses an assignment `U uu` with a decimal `U` and a unit `uu`, -be it a core unit, or a unit corresponding to an internal dimension, -it first handles `U` as with the `pt` unit. -This means that it computes `N = round(65536*U)`. It then multiplies -this `N` by the conversion factor `phi` and truncates towards zero the -mathematically exact result to obtain an integer `T`: -`T=trunc(N*phi)`. The assignment `Uuu` is concluded by defining the -value of the dimension to be `Tsp`. - -Regarding the core units, we always have `phi>1`. -The increasing -sequence `0<=trunc(phi)<=trunc(2phi)<=...` is thus *strictly increasing* -and, as `phi` is never astronomically close to `1`, -**it always has jumps**: not all TeX dimensions can be obtained from an -assignment using a core unit distinct from the `pt` (and `sp` of course, -but we already said it was kept out of the discussion here). - -On the other hand when `phi<1`, then the sequence `trunc(N phi)` is not -strictly increasing, already because `trunc(phi)=0` and besides here -`phi=f/65536`, so the `65536` integers `0..65535` are mapped to `f` -integers `0..(f-1)` inducing non one-to-oneness. But -all integers in the `0..(2**30-1)` range will be attained for some input, -so there is surjectivity. - -The "worst" unit is the largest i.e. the `in` whose conversion factor is -`72.27`. The simplest unit to understand is the `pc` as it corresponds -to an integer ratio `12`: only dimensions which in scaled points are -multiple of `12` are exactly representable in the `pc` unit. - -This also means that some dimensions expressible in one unit may not be -available with another unit. For example, and perhaps surprisingly, -there is no decimal `D` which would achieve `1in==Dcm`: the "step" -between attainable dimensions is `72--73sp` for the `in` and `28--29sp` -for the `cm`, and as `1in` differs internally from `2.54cm` by only -`12sp` it is impossible to adjust -either the `in` side or the `cm` side to obtain equality. - -In particular `1in==2.54cm` is **false** in TeX, but it is true that -`100in==254cm`... (it is already true that `50in==127cm`). It is also -false that `10in==25.4cm` but it is true that `10in==254mm`... It is -false though that `1in==25.4mm`! - - >>> (\dimexpr1in, \dimexpr2.54cm); - @_1 4736286, 4736274 - >>> (\dimexpr10in, \dimexpr25.4cm); - @_2 47362867, 47362855 - >>> (\dimexpr100in, \dimexpr254cm); - @_3 473628672, 473628672 - - >>> (\dimexpr1in, \dimexpr25.4mm); - @_4 4736286, 4736285 - >>> (\dimexpr10in, \dimexpr254mm); - @_5 47362867, 47362867 - -`\maxdimen` can be expressed only with `pt`, `bp`, and `nd`. For the -other core units the maximal attainable dimensions in `sp` unit are given in -the middle column of the next table. - - maximal allowed the corresponding minimal TeX dimen denotation - (with 5 places) maximal attainable dim. causing "Dimension too large" - --------------- ------------------------- -------------------------- - 16383.99999pt 1073741823sp (=\maxdimen) 16383.99999237060546875pt - 16322.78954bp 1073741823sp (=\maxdimen) 16322.78954315185546875bp - 15355.51532nd 1073741823sp (=\maxdimen) 15355.51532745361328125nd - 15312.02584dd 1073741822sp 15312.02584075927734375dd - 5758.31742mm 1073741822sp 5758.31742095947265625mm - 1365.33333pc 1073741820sp 1365.33333587646484375pc - 1279.62627nc 1073741814sp 1279.62627410888671875nc - 1276.00215cc 1073741821sp 1276.00215911865234375cc - 575.83174cm 1073741822sp 575.83174896240234375cm - 226.70540in 1073741768sp 226.70540618896484375in +## Aim of this package -Perhaps for these various peculiarities with dimensional units, TeX does -not provide an output facility for them similar to what `\the` achieves for -the `pt`. +The aim of this package is to provide facilities to express dimensions +(or dimension expressions evaluated by `\dimexpr`) using the various +available TeX units, to the extent possible. ## Macros of this package (summary) -This package provides expandable (most are f-expandable, see the code) -macros (they can be nested as they parse their inputs via `\dimexpr`): +This package provides expandable macros: - `\texdimenpt`, - `\texdimenUU`, `\texdimenUUup` and `\texdimenUUdown` with `UU` standing for one of `bp`, `cm`, `mm`, `in`, `pc`, `cc`, `nc`, `dd` and `nd`, -- `\texdimenbothincm` (and relatives not listed here, see below), -- and `\texdimenwithunit`, added at `0.99`. +- `\texdimenbothincm` (and relatives, see full documentation), +- `\texdimenwithunit`. For example `\texdimenbp` takes on input some dimension or dimension expression and produces on output a decimal `D` such that `D bp` is guaranteed to be the same dimension as the input, if that one admits any representation as `E bp`; else it will be either the closest match from -above or from below. The `\texdimenbpup` and `\texdimenbpdown` allow to -choose the direction of approximation. - -`\texdimenwithunit{}{}` produces a decimal `D` such -that `D \dimexpr dimen2\relax` is represented internally the same as -`dimen1` if at all possible, else is a closest match, but one does not -know if from below or above. If `dimen2<1pt` all TeX dimensions `dimen1` -are attainable. If `dimen2>1pt` not all `dimen1` are attainable. - -Negative dimensions behave as if replaced by their absolute value, then -at last step the sign (if result is not zero) is applied (so "down" means -"towards zero", and "up" means "away from zero"). - -Do not confuse `\texdimenwithunit{dim}{1bp}` with -`\texdimenbp{dim}`. The former produces a decimal `D` such that -`D\dimexpr 1bp\relax` is represented internally as is `dim` if at all -possible, whereas the latter produces a decimal `D` such that `D bp` is -the one aiming at being the same as `dim`. Using `D\dimexpr 1bp\relax` -implies a conversion factor equal to `65781/65536`, whereas `D bp` -involves the `803/800` conversion factor. - - -## Macros of this package (full list) - -1. For input `X` equal to (or sufficiently close to) `\maxdimen` and - those units `uu` for which `\maxdimen` is not exactly representable - (i.e. all core units except `pt`, `bp` and `nd`), the output `D` of the - "up" macros `\texdimenup{X}`, if used as `Duu` in a dimension - assignment or expression, will (naturally) trigger a "Dimension too - large" error. -2. For `dd`, `nc` and `in`, and input `X` equal to (or sufficiently - close to) `\maxdimen` it turns out that `\texdimen{X}` produces - an output `D` such that `Duu` is the first "virtually attainable" TeX - dimension *beyond* `\maxdimen`. Hence `Duu` will trigger on use - "Dimension too large error". -3. Again for the `dd`, `nc` and `in` units, both the "down" and "up" macros - will trigger "Dimension too large" during their execution if used - with an input equal to (or sufficiently close to) `\maxdimen`. -4. With `\texdimenwithunit{dimen1}{dimen2}` and if `\maxdimen` - is not representable exactly by `dimen2` used as a base dimension, - (which may happen only if `dimen2>1pt`) it might - be that the decimal `D` produced from `\maxdimen` or nearby dimensions - will trigger "Dimension too large" if an attempt to use `D ` is - made. - -`\texdimenpt{}` - -> Does `\the\dimexpr \relax` then removes the `pt`. - -`\texdimenbp{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dbp` -> represents the dimension exactly if possible. If not possible it -> will differ by `1sp` from the original dimension, but it is not -> known in advance if it will be above or below. - -> `\maxdimen` on input produces `16322.78954` and indeed is realized as `16322.78954bp`. - -`\texdimenbpdown{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dbp` -> represents the dimension exactly if possible. If not possible it -> will be smaller by `1sp` from the original dimension. - -`\texdimenbpup{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dbp` -> represents the dimension exactly if possible. If not possible it -> will be larger by `1sp` from the original dimension. - -`\texdimennd{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dnd` -> represents the dimension exactly if possible. If not possible it -> will differ by `1sp` from the original dimension, but it is not -> known in advance if it will be above or below. - -> `\maxdimen` on input produces `15355.51532` and indeed is realized as `15355.51532nd`. - -`\texdimennddown{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dnd` -> represents the dimension exactly if possible. If not possible it -> will be smaller by `1sp` from the original dimension. - -`\texdimenndup{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dnd` -> represents the dimension exactly if possible. If not possible it -> will be larger by `1sp` from the original dimension. - -`\texdimendd{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Ddd` -> represents the dimension exactly if possible. If not possible it -> will differ by `1sp` from the original dimension, but it is not -> known in advance if it will be above or below. - -> Warning: the output for `\maxdimen` is `15312.02585` but `15312.02585dd` -> will trigger "Dimension too large" error. -> `\maxdimen-1sp` is attainable via `15312.02584dd`. - -`\texdimendddown{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Ddd` -> represents the dimension exactly if possible. If not possible it -> will be smaller by `1sp` from the original dimension. - -`\texdimenddup{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Ddd` -> represents the dimension exactly if possible. If not possible it -> will be larger by `1sp` from the original dimension. - -`\texdimenmm{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dmm` -> represents the dimension exactly if possible. If not possible it -> will either be the closest from below or from above, but it is not -> known in advance which one (and it is not known if the other choice -> would have been closer). - -> `\maxdimen` as input produces on output `5758.31741` and indeed the -> maximal attainable dimension is `5758.31741mm` (`1073741822sp`). - -`\texdimenmmdown{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dmm` -> represents the dimension exactly if possible. If not possible it -> will be largest representable dimension smaller than the original one. - -`\texdimenmmup{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dmm` -> represents the dimension exactly if possible. If not possible it -> will be smallest representable dimension larger than the original one. - -`\texdimenpc{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dpc` -> represents the dimension exactly if possible. If not possible it -> will be the closest representable one (in case of tie, the approximant -> from above is chosen). - -> `\maxdimen` as input produces on output `1365.33333` and indeed the -> maximal attainable dimension is `1365.33333pc` (`1073741820sp`). - -`\texdimenpcdown{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dpc` -> represents the dimension exactly if possible. If not possible it -> will be largest representable dimension smaller than the original one. - -`\texdimenpcup{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dpc` -> represents the dimension exactly if possible. If not possible it -> will be smallest representable dimension larger than the original one. - -`\texdimennc{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dnc` -> represents the dimension exactly if possible. If not possible it -> will either be the closest from below or from above, but it is not -> known in advance which one (and it is not known if the other choice -> would have been closer). - -> Warning: the output for `\maxdimen` is `1279.62628` but `1279.62628nc` -> will trigger "Dimension too large" error. -> `\maxdimen-9sp` is attainable via `1279.62627nc`. - -`\texdimenncdown{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dnc` -> represents the dimension exactly if possible. If not possible it -> will be largest representable dimension smaller than the original one. - -`\texdimenncup{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dnc` -> represents the dimension exactly if possible. If not possible it -> will be smallest representable dimension larger than the original one. - -`\texdimencc{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dcc` -> represents the dimension exactly if possible. If not possible it -> will either be the closest from below or from above, but it is not -> known in advance which one (and it is not known if the other choice -> would have been closer). - -> `\maxdimen` as input produces on output `1276.00215` and indeed the -> maximal attainable dimension is `1276.00215cc` (`1073741821sp`). - -`\texdimenccdown{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dcc` -> represents the dimension exactly if possible. If not possible it -> will be largest representable dimension smaller than the original one. - -`\texdimenccup{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dcc` -> represents the dimension exactly if possible. If not possible it -> will be smallest representable dimension larger than the original one. - -`\texdimencm{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dcm` -> represents the dimension exactly if possible. If not possible it -> will either be the closest from below or from above, but it is not -> known in advance which one (and it is not known if the other choice -> would have been closer). - -> `\maxdimen` as input produces on output `575.83174` and indeed the -> maximal attainable dimension is `575.83174cm` (`1073741822sp`). - -`\texdimencmdown{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dcm` -> represents the dimension exactly if possible. If not possible it -> will be largest representable dimension smaller than the original one. - -`\texdimencmup{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dcm` -> represents the dimension exactly if possible. If not possible it -> will be smallest representable dimension larger than the original one. - -`\texdimenin{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Din` -> represents the dimension exactly if possible. If not possible it -> will either be the closest from below or from above, but it is not -> known in advance which one (and it is not known if the other choice -> would have been closer). - -> Warning: the output for `\maxdimen` is `226.70541` but `226.70541in` -> will trigger "Dimension too large" error. -> `\maxdimen-55sp` is maximal attainable dimension (via `226.7054in`). - -`\texdimenindown{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Din` -> represents the dimension exactly if possible. If not possible it -> will be largest representable dimension smaller than the original one. - -`\texdimeninup{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Din` -> represents the dimension exactly if possible. If not possible it -> will be smallest representable dimension larger than the original one. - -`\texdimenbothcmin{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Din` -> is the largest dimension smaller than the original one and -> exactly representable both in the `in` and `cm` units. - -`\texdimenbothincm{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dcm` -> is the largest dimension smaller than the original one and -> exactly representable both in the `in` and `cm` units. It thus represents -> the same dimension as `\texdimenbothcmin{}in`. - -`\texdimenbothcminpt{}` - -> Produces a decimal (with up to five decimal places) `D` such that `Dpt` -> is the largest dimension smaller than the original one and -> exactly representable both in the `in` and `cm` units. It thus represents -> the same dimension as the one provided by `\texdimenbothcmin` and -> `\texdimenbothincm`. - -`\texdimenbothincmpt{}` - -> Same as `\texdimenbothcminpt`. - -`\texdimenbothcminsp{}` - -> Produces an integer (explicit digit tokens) `N` such that `Nsp` -> is the largest dimension smaller than the original one and -> exactly representable both in the `in` and `cm` units. - -`\texdimenbothincmsp{}` - -> Same as `\texdimenbothcminsp`. - -`\texdimenwithunit{}{}` - -> Produces a decimal `D` such that `D\dimexpr \relax` is -> considered by TeX the same as `` if at all possible. -> If not possible it will be a closest match either from above or below. - - -## Extras? - -As already stated the "up" and also the "down" macros for the `dd`, `nc` -and `in` units will trigger "Dimension too large" if used with inputs -equal to or very near `\maxdimen`. "Safe" variants which are guaranteed -never to trigger this error but have some extra overhead to filter out -inputs very close to `\maxdimen` will *perhaps* be provided if there is -some demand for it. - -But of course anyhow the output from the "up" macros if used -as input with the corresponding unit will be beyond `\maxdimen` if the -latter is not attainable, i.e. for all units except `bp`, and `nd` -(and `pt` but there is no "up" macro for it). - -The -dimensions representable with both `in` and `cm` units have the shape -`trunc(3613.5*k)sp` for some integer `k`. The largest one smaller -than a given dimension will thus differ from it by at most about `0.055pt`, -which is also about `0.02mm`. +above or from below (for this unit the error is at most `1sp`). -For example `\texdimenbothincm{1cm}` expands to `0.99994cm` which maps -internally to `1864566sp` which differs from TeX's `1cm` by only -`-113sp`. It can be obtained from `0.39368in` or `28.45102pt`. +The variants `\texdimenbpup` and `\texdimenbpdown` allow to choose the +direction of approximation. -And `\texdimenbothcmin{1in}` expands to `0.99945in`, maps internally to -`4733685sp` which differs from TeX's `1in` by `-2601sp`. It can be obtained -as `2.5386cm` or `72.2303pt`. +`\texdimenwithunit{}{}` produces a decimal `D` such that +`D \dimexpr dimen2\relax` is parsed by TeX into the same dimension as +`dimen1` if this is at all possible. If `dimen2<1pt` all TeX dimensions +`dimen1` are attainable. If `dimen2>1pt` not all `dimen1` are +attainable. If not attainable, the decimal `D` will ensure a closest +match from below or from above but one does not know if the +approximation from the other direction is better or worst. -Currently the package does not provide analogous approximations from above. -For the `1in` for example it would be `4737298sp`, i.e. `1.00021in` which -differs from TeX's `1in` by `+1012sp` and is obtained also as `2.54054cm` -and `72.28543pt`. +In a sense, this macro divides `` by `` but please refer +to the full documentation (`texdimens.md`, `texdimens.pdf`) for relevant +information on how TeX handles dimensions. ## Acknowledgements -Thanks to Denis Bitouzé for raising an issue on the LaTeX3 tracker which -gave the initial motivation for this package. +Thanks to Denis Bitouzé for raising an +[issue](https://github.com/latex3/latex3/issues/953) +on the LaTeX3 tracker which became the initial stimulus for this package. -Thanks to Ruixi Zhang for reviving the topic about handling also -the `ex` and `em` cases, which led to `\texdimenwithunit`. +Thanks to Ruixi Zhang for reviving the above linked-to thread +and opening up here [issue #2](https://github.com/jfbu/texdimens/issues/2) +asking to add handling of the `ex`,`em`, and `px` cases. This was done +at release `0.99` via the addition of `\texdimenwithunit`. diff --git a/macros/generic/texdimens/texdimens.md b/macros/generic/texdimens/texdimens.md new file mode 100644 index 0000000000..98ae9f5b3b --- /dev/null +++ b/macros/generic/texdimens/texdimens.md @@ -0,0 +1,560 @@ +texdimens +========= + +## Copyright and License + +Copyright (c) 2021 Jean-François Burnol + +This file is part of the texdimens package distributed under the +LPPL 1.3c. See file LICENSE.md. + +Repository: https://github.com/jfbu/texdimens + +Release: `0.99d 2021/11/04` + +## Usage + +Utilities and documentation related to TeX dimensional units, usable: + +- with Plain TeX: `\input texdimens` + +- with LaTeX: `\usepackage{texdimens}` + +## Aim of this package + +The aim of this package is to provide facilities to express dimensions +(or dimension expressions evaluated by `\dimexpr`) using the various +available TeX units, to the extent possible. + +## Macros of this package (summary) + +This package provides expandable macros: + +- `\texdimenpt`, +- `\texdimenUU`, `\texdimenUUup` and + `\texdimenUUdown` with `UU` standing for one of `bp`, `cm`, `mm`, `in`, + `pc`, `cc`, `nc`, `dd` and `nd`, +- `\texdimenbothincm` (and relatives not listed here, see below), +- `\texdimenwithunit`. + +For example `\texdimenbp` takes on input some dimension or dimension +expression and produces on output a decimal `D` such that `D bp` is +guaranteed to be the same dimension as the input, if that one admits any +representation as `E bp`; else it will be either the closest match from +above or from below (for this unit the error is at most `1sp`). + +The variants `\texdimenbpup` and `\texdimenbpdown` allow to choose the +direction of approximation. + +`\texdimenwithunit{}{}` produces a decimal `D` such that +`D \dimexpr dimen2\relax` is parsed by TeX into the same dimension as +`dimen1` if this is at all possible. If `dimen2<1pt` all TeX dimensions +`dimen1` are attainable. If `dimen2>1pt` not all `dimen1` are +attainable. If not attainable, the decimal `D` will ensure a closest +match from below or from above but one does not know if the +approximation from the other direction is better or worst. + +In a sense, this macro divides `` by `` but please continue +reading this documentation for relevant +information on how TeX handles dimensions. + +## Quick review of basics: TeX points and scaled points + +This project requires the e-TeX extensions `\dimexpr` and `\numexpr`. +The notation `` in the macro descriptions refers to a +*dimensional expression* as accepted by `\dimexpr`. The syntax has some +peculiarities: among them the fact that `-(...)` (for example `-(3pt)`) +is illegal, one must use alternatives such as `0pt-(...)` or a +sub-expression `-\dimexpr...\relax` for example. + +TeX dimensions are represented internally by a signed integer which is +in absolute value at most `0x3FFFFFFF`, i.e. `1073741823`. The +corresponding unit is called the "scaled point", i.e. `1sp` is `1/65536` +of one TeX point `1pt`, or rather `1pt` is represented internally as `65536`. + +If `\foo` is a dimen register: + +- `\number\foo` produces the integer `N` such as `\foo` is the same as `Nsp`, + +- inside `\numexpr`, `\foo` is replaced by `N`, + +- `\the\foo` produces a decimal `D` (with at most five places) followed + with `pt` (catcode 12 tokens) and this output `Dpt` can serve as input + in a dimen assignment to produce the same dimension as `\foo`. One can + also use the catcode 11 characters `pt` for this. Digits and decimal + mark must have their standard catcode 12. + +When TeX encounters a dimen denotation of the type `Dpt` it will +compute `N` in a way equivalent to `N = round(65536 D)` where ties are +rounded away from zero. Only 17 decimal places of `D` are +kept as it can be shown that going beyond can not change the result. + +When `\foo` has been assigned as `Dpt`, `\the\foo` will produce some +`Ept` where `E` is not necessarily the same as `D`. But it is guaranteed +that `Ept` defines the same dimension as `Dpt`. + +## Further units known to TeX on input + +TeX understands on input further units: `bp`, `cm`, `mm`, `in`, `pc`, +`cc`, `nc`, `dd` and `nd`. It also understands font-dependent units +`ex` and `em`, and PDFTeX adds the `px` dimension unit. Japanese +engines also add specific units. + +The `ex`, `em`, and `px` units are handled somewhat differently by +(pdf)TeX than `bp`, `cm`, `mm`, `in`, `pc`, `cc`, `nc`, `dd` and `nd` +units. For the former (let's use the generic notation `uu`), the exact +same dimensions are obtained from an input `D uu` where `D` is some +decimal or from `D ` where `` stands for some dimension +register which records `1uu` or `\dimexpr1uu\relax`. In contrast, among +the latter, i.e. the core TeX units, this is false except for the `pc` +unit. + +TeX associates (explicitly for the core units, implicitly for the units +corresponding to internal dimensions) to each unit `uu` a fraction `phi` +which is a conversion factor. For the internal dimensions `ex`, `em`, +`px` or in the case of multiplying a dimension by a decimal, this `phi` +is morally `f/65536` where `f` is the integer such that `1 uu=f sp`. +For core units however, the hard-coded ratio `n/d` never has a +denominator `d` which is a power of `2`, except for the `pc` whose +associated ratio factor is `12/1` (and arguably for the `sp` for which +morally `phi` is `1/65536` but we keep it separate from the general +discussion). + +Here is a table with the hard-coded conversion factors: + + uu phi reduced real approximation 1uu in sp= \the<1uu> + (Python output) [65536phi] + -- ---------- ------- ------------------ --------- ---------- + bp 7227/7200 803/800 1.00375 65781 1.00374pt + nd 685/642 same 1.0669781931464175 69925 1.06697pt + dd 1238/1157 same 1.070008643042351 70124 1.07pt + mm 7227/2540 same 2.8452755905511813 186467 2.84526pt + pc 12/1 12 12.0 786432 12.0pt + nc 1370/107 same 12.80373831775701 839105 12.80373pt + cc 14856/1157 same 12.84010371650821 841489 12.8401pt + cm 7227/254 same 28.45275590551181 1864679 28.45274pt + in 7227/100 same 72.27 4736286 72.26999pt + +The values of `1uu` in the `sp` and `pt` units are irrelevant and even +misleading regarding the TeX parsing of `D uu` input. Notice for +example that `\the\dimexpr1bp\relax` gives `1.00374pt` but the actual +conversion factor is `1.00375`. + +When TeX parses an assignment `U uu` with a decimal `U` and a unit `uu`, +be it a core unit, or a unit corresponding to an internal dimension, +it first handles `U` as with the `pt` unit. +This means that it computes `N = round(65536*U)`. It then multiplies +this `N` by the conversion factor `phi` and truncates towards zero the +mathematically exact result to obtain an integer `T`: +`T=trunc(N*phi)`. The assignment `Uuu` is concluded by defining the +value of the dimension to be `Tsp`. + +Regarding the core units, we always have `phi>1`. +The increasing +sequence `0<=trunc(phi)<=trunc(2phi)<=...` is thus *strictly increasing* +and, as `phi` is never astronomically close to `1`, +**it always has jumps**: not all TeX dimensions can be obtained from an +assignment using a core unit distinct from the `pt` (and `sp` of course, +but we already said it was kept out of the discussion here). + +On the other hand when `phi<1`, then the sequence `trunc(N phi)` is not +strictly increasing, already because `trunc(phi)=0` and besides here +`phi=f/65536`, so the `65536` integers `0..65535` are mapped to `f` +integers `0..(f-1)` inducing non one-to-oneness. But +all integers in the `0..(2**30-1)` range will be attained for some input, +so there is surjectivity. + +The "worst" unit is the largest i.e. the `in` whose conversion factor is +`72.27`. The simplest unit to understand is the `pc` as it corresponds +to an integer ratio `12`: only dimensions which in scaled points are +multiple of `12` are exactly representable in the `pc` unit. + +This also means that some dimensions expressible in one unit may not be +available with another unit. For example, and perhaps surprisingly, +there is no decimal `D` which would achieve `1in==Dcm`: the "step" +between attainable dimensions is `72--73sp` for the `in` and `28--29sp` +for the `cm`, and as `1in` differs internally from `2.54cm` by only +`12sp` it is impossible to adjust +either the `in` side or the `cm` side to obtain equality. + +In particular `1in==2.54cm` is **false** in TeX, but it is true that +`100in==254cm`... (it is already true that `50in==127cm`). It is also +false that `10in==25.4cm` but it is true that `10in==254mm`... It is +false though that `1in==25.4mm`! + + >>> (\dimexpr1in, \dimexpr2.54cm); + @_1 4736286, 4736274 + >>> (\dimexpr10in, \dimexpr25.4cm); + @_2 47362867, 47362855 + >>> (\dimexpr100in, \dimexpr254cm); + @_3 473628672, 473628672 + + >>> (\dimexpr1in, \dimexpr25.4mm); + @_4 4736286, 4736285 + >>> (\dimexpr10in, \dimexpr254mm); + @_5 47362867, 47362867 + +`\maxdimen` can be expressed only with `pt`, `bp`, and `nd`. For the +other core units the maximal attainable dimensions in `sp` unit are given in +the middle column of the next table. + + maximal allowed the corresponding minimal TeX dimen denotation + (with 5 places) maximal attainable dim. causing "Dimension too large" + --------------- ------------------------- -------------------------- + 16383.99999pt 1073741823sp (=\maxdimen) 16383.99999237060546875pt + 16322.78954bp 1073741823sp (=\maxdimen) 16322.78954315185546875bp + 15355.51532nd 1073741823sp (=\maxdimen) 15355.51532745361328125nd + 15312.02584dd 1073741822sp 15312.02584075927734375dd + 5758.31742mm 1073741822sp 5758.31742095947265625mm + 1365.33333pc 1073741820sp 1365.33333587646484375pc + 1279.62627nc 1073741814sp 1279.62627410888671875nc + 1276.00215cc 1073741821sp 1276.00215911865234375cc + 575.83174cm 1073741822sp 575.83174896240234375cm + 226.70540in 1073741768sp 226.70540618896484375in + +Perhaps for these various peculiarities with dimensional units, TeX does +not provide an output facility for them similar to what `\the` achieves for +the `pt`. + +## Macros of this package (full list) + +The macros are all expandable, and most are f-expandable (check the +source code). They parse their arguments via `\dimexpr` so can be nested +(with appropriate units added, as the outputs always are bare decimal +numbers). + +Negative dimensions behave as if replaced by their absolute value, then +at last step the sign (if result is not zero) is applied (so "down" means +"towards zero", and "up" means "away from zero"). + +Remarks about "Dimension too large" issues: + +1. For input `X` equal to (or sufficiently close to) `\maxdimen` and + those units `uu` for which `\maxdimen` is not exactly representable + (i.e. all core units except `pt`, `bp` and `nd`), the output `D` of the + "up" macros `\texdimenup{X}`, if used as `Duu` in a dimension + assignment or expression, will (naturally) trigger a "Dimension too + large" error. +2. The same potentially happens with `\texdimenwithunit{dimen1}{dimen2}` + if `\maxdimen` + is not representable exactly by `dimen2` used as a base dimension, + (which may happen only if `dimen2>1pt`): it is possible that the + output `D`, if consequently used as `D\dimexpr dimen2\relax` will + trigger "Dimension too large". +3. For `dd`, `nc` and `in`, and input `X` equal to (or sufficiently + close to) `\maxdimen` it turns out that `\texdimen{X}` produces + an output `D` such that `Duu` is the first "virtually attainable" TeX + dimension *beyond* `\maxdimen`. Hence here also `Duu` will trigger on use + "Dimension too large error". +4. Again for the `dd`, `nc` and `in` units, both the "down" and "up" macros + will trigger "Dimension too large" *during their execution* if used + with an input equal to (or sufficiently close to) `\maxdimen`. + +`\texdimenpt{}` + +> Does `\the\dimexpr \relax` then removes the `pt`. + +`\texdimenbp{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dbp` +> represents the dimension exactly if possible. If not possible it +> will differ by `1sp` from the original dimension, but it is not +> known in advance if it will be above or below. + +> `\maxdimen` on input produces `16322.78954` and indeed is realized as +> `16322.78954bp`. + +`\texdimenbpdown{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dbp` +> represents the dimension exactly if possible. If not possible it +> will be smaller by `1sp` from the original dimension. + +`\texdimenbpup{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dbp` +> represents the dimension exactly if possible. If not possible it +> will be larger by `1sp` from the original dimension. + +`\texdimennd{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dnd` +> represents the dimension exactly if possible. If not possible it +> will differ by `1sp` from the original dimension, but it is not +> known in advance if it will be above or below. + +> `\maxdimen` on input produces `15355.51532` and indeed is realized as +> `15355.51532nd`. + +`\texdimennddown{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dnd` +> represents the dimension exactly if possible. If not possible it +> will be smaller by `1sp` from the original dimension. + +`\texdimenndup{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dnd` +> represents the dimension exactly if possible. If not possible it +> will be larger by `1sp` from the original dimension. + +`\texdimendd{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Ddd` +> represents the dimension exactly if possible. If not possible it +> will differ by `1sp` from the original dimension, but it is not +> known in advance if it will be above or below. + +> Warning: the output for `\maxdimen` is `15312.02585` but `15312.02585dd` +> will trigger "Dimension too large" error. +> `\maxdimen-1sp` is attainable via `15312.02584dd`. + +`\texdimendddown{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Ddd` +> represents the dimension exactly if possible. If not possible it +> will be smaller by `1sp` from the original dimension. + +`\texdimenddup{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Ddd` +> represents the dimension exactly if possible. If not possible it +> will be larger by `1sp` from the original dimension. + +`\texdimenmm{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dmm` +> represents the dimension exactly if possible. If not possible it +> will either be the closest from below or from above, but it is not +> known in advance which one (and it is not known if the other choice +> would have been closer). + +> `\maxdimen` as input produces on output `5758.31741` and indeed the +> maximal attainable dimension is `5758.31741mm` (`1073741822sp`). + +`\texdimenmmdown{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dmm` +> represents the dimension exactly if possible. If not possible it +> will be largest representable dimension smaller than the original one. + +`\texdimenmmup{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dmm` +> represents the dimension exactly if possible. If not possible it +> will be smallest representable dimension larger than the original one. + +`\texdimenpc{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dpc` +> represents the dimension exactly if possible. If not possible it +> will be the closest representable one (in case of tie, the approximant +> from above is chosen). + +> `\maxdimen` as input produces on output `1365.33333` and indeed the +> maximal attainable dimension is `1365.33333pc` (`1073741820sp`). + +`\texdimenpcdown{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dpc` +> represents the dimension exactly if possible. If not possible it +> will be largest representable dimension smaller than the original one. + +`\texdimenpcup{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dpc` +> represents the dimension exactly if possible. If not possible it +> will be smallest representable dimension larger than the original one. + +`\texdimennc{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dnc` +> represents the dimension exactly if possible. If not possible it +> will either be the closest from below or from above, but it is not +> known in advance which one (and it is not known if the other choice +> would have been closer). + +> Warning: the output for `\maxdimen` is `1279.62628` but `1279.62628nc` +> will trigger "Dimension too large" error. +> `\maxdimen-9sp` is attainable via `1279.62627nc`. + +`\texdimenncdown{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dnc` +> represents the dimension exactly if possible. If not possible it +> will be largest representable dimension smaller than the original one. + +`\texdimenncup{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dnc` +> represents the dimension exactly if possible. If not possible it +> will be smallest representable dimension larger than the original one. + +`\texdimencc{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dcc` +> represents the dimension exactly if possible. If not possible it +> will either be the closest from below or from above, but it is not +> known in advance which one (and it is not known if the other choice +> would have been closer). + +> `\maxdimen` as input produces on output `1276.00215` and indeed the +> maximal attainable dimension is `1276.00215cc` (`1073741821sp`). + +`\texdimenccdown{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dcc` +> represents the dimension exactly if possible. If not possible it +> will be largest representable dimension smaller than the original one. + +`\texdimenccup{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dcc` +> represents the dimension exactly if possible. If not possible it +> will be smallest representable dimension larger than the original one. + +`\texdimencm{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dcm` +> represents the dimension exactly if possible. If not possible it +> will either be the closest from below or from above, but it is not +> known in advance which one (and it is not known if the other choice +> would have been closer). + +> `\maxdimen` as input produces on output `575.83174` and indeed the +> maximal attainable dimension is `575.83174cm` (`1073741822sp`). + +`\texdimencmdown{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dcm` +> represents the dimension exactly if possible. If not possible it +> will be largest representable dimension smaller than the original one. + +`\texdimencmup{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dcm` +> represents the dimension exactly if possible. If not possible it +> will be smallest representable dimension larger than the original one. + +`\texdimenin{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Din` +> represents the dimension exactly if possible. If not possible it +> will either be the closest from below or from above, but it is not +> known in advance which one (and it is not known if the other choice +> would have been closer). + +> Warning: the output for `\maxdimen` is `226.70541` but `226.70541in` +> will trigger "Dimension too large" error. +> `\maxdimen-55sp` is maximal attainable dimension (via `226.7054in`). + +`\texdimenindown{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Din` +> represents the dimension exactly if possible. If not possible it +> will be largest representable dimension smaller than the original one. + +`\texdimeninup{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Din` +> represents the dimension exactly if possible. If not possible it +> will be smallest representable dimension larger than the original one. + +`\texdimenbothcmin{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Din` +> is the largest dimension smaller than the original one and +> exactly representable both in the `in` and `cm` units. + +`\texdimenbothincm{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dcm` +> is the largest dimension smaller than the original one and +> exactly representable both in the `in` and `cm` units. It thus represents +> the same dimension as `\texdimenbothcmin{}in`. + +`\texdimenbothcminpt{}` + +> Produces a decimal (with up to five decimal places) `D` such that `Dpt` +> is the largest dimension smaller than the original one and +> exactly representable both in the `in` and `cm` units. It thus represents +> the same dimension as the one provided by `\texdimenbothcmin` and +> `\texdimenbothincm`. + +`\texdimenbothincmpt{}` + +> Same as `\texdimenbothcminpt`. + +`\texdimenbothcminsp{}` + +> Produces an integer (explicit digit tokens) `N` such that `Nsp` +> is the largest dimension smaller than the original one and +> exactly representable both in the `in` and `cm` units. + +`\texdimenbothincmsp{}` + +> Same as `\texdimenbothcminsp`. + +`\texdimenwithunit{}{}` + +> Produces a decimal `D` such that `D\dimexpr \relax` is +> considered by TeX the same as `` if at all possible. If +> not possible it will be a closest match either from above or below +> (but one does not know if the other direction is a better or worst +> match). +> +> `\texdimenwithunit{dim}{1bp}` and `\texdimenbp{dim}` are not +> the same: The former produces a decimal `D` such that `D\dimexpr +> 1bp\relax` is represented internally as is `dim` if at all possible, +> whereas the latter produces a decimal `D` such that `D bp` is the one +> aiming at being the same as `dim`. Using `D\dimexpr 1bp\relax` implies +> a conversion factor equal to `65781/65536`, whereas `D bp` involves +> the `803/800` conversion factor. + + +## Extras? + +As already stated the "up" and also the "down" macros for the `dd`, `nc` +and `in` units will trigger "Dimension too large" if used with inputs +equal to or very near `\maxdimen`. "Safe" variants which are guaranteed +never to trigger this error but have some extra overhead to filter out +inputs very close to `\maxdimen` will *perhaps* be provided if there is +some demand for it. + +But of course anyhow the output from the "up" macros if used +as input with the corresponding unit will be beyond `\maxdimen` if the +latter is not attainable, i.e. for all units except `bp`, and `nd` +(and `pt` but there is no "up" macro for it). + +The +dimensions representable with both `in` and `cm` units have the shape +`trunc(3613.5*k)sp` for some integer `k`. The largest one smaller +than a given dimension will thus differ from it by at most about `0.055pt`, +which is also about `0.02mm`. + +For example `\texdimenbothincm{1cm}` expands to `0.99994cm` which maps +internally to `1864566sp` which differs from TeX's `1cm` by only +`-113sp`. It can be obtained from `0.39368in` or `28.45102pt`. + +And `\texdimenbothcmin{1in}` expands to `0.99945in`, maps internally to +`4733685sp` which differs from TeX's `1in` by `-2601sp`. It can be obtained +as `2.5386cm` or `72.2303pt`. + +Currently the package does not provide analogous approximations from above. +For the `1in` for example it would be `4737298sp`, i.e. `1.00021in` which +differs from TeX's `1in` by `+1012sp` and is obtained also as `2.54054cm` +and `72.28543pt`. + +## Acknowledgements + +Thanks to Denis Bitouzé for raising an +[issue](https://github.com/latex3/latex3/issues/953) +on the LaTeX3 tracker which became the initial stimulus for this package. + +Thanks to Ruixi Zhang for reviving the above linked-to thread +and opening up here [issue #2](https://github.com/jfbu/texdimens/issues/2) +asking to add handling of the `ex`,`em`, and `px` cases. This was done +at release `0.99` via the addition of `\texdimenwithunit`. + + diff --git a/macros/generic/texdimens/texdimens.pdf b/macros/generic/texdimens/texdimens.pdf new file mode 100644 index 0000000000..95e97ee293 Binary files /dev/null and b/macros/generic/texdimens/texdimens.pdf differ diff --git a/macros/generic/texdimens/texdimens.sty b/macros/generic/texdimens/texdimens.sty index 1b41c47d68..e906d11fa3 100644 --- a/macros/generic/texdimens/texdimens.sty +++ b/macros/generic/texdimens/texdimens.sty @@ -1,5 +1,5 @@ % This is file texdimens.tex, part of texdimens package, which % is distributed under the LPPL 1.3c. Copyright (c) 2021 Jean-François Burnol -\ProvidesPackage{texdimens}[2021/11/02 v0.99 conversion of TeX dimensions to decimals (JFB)] +\ProvidesPackage{texdimens}[2021/11/04 v0.99d conversion of TeX dimensions to decimals (JFB)] \@@input texdimens.tex\relax \endinput \ No newline at end of file diff --git a/macros/generic/texdimens/texdimens.tex b/macros/generic/texdimens/texdimens.tex index d8bf99e0bc..61086d138d 100644 --- a/macros/generic/texdimens/texdimens.tex +++ b/macros/generic/texdimens/texdimens.tex @@ -1,18 +1,18 @@ % 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/02 v0.99 -% All macros from 0.9delta release have changed names: \texdimen prefix -% has replaced \texdimin. -\edef\texdimensendinput{\endlinechar\the\endlinechar\catcode`\noexpand _=\the\catcode`\_\relax\noexpand\endinput}% +% 2021/11/04 v0.99d +\edef\texdimensendinput{\endlinechar\the\endlinechar% +\catcode`\noexpand _=\the\catcode`\_% +\catcode`\noexpand @=\the\catcode`\@\relax\noexpand\endinput}% \endlinechar13\relax% -\catcode`\_=11 +\catcode`\_=11 \catcode`\@=11 % only for using \p@ of Plain. Check exists? % % Mathematics ("down" and "up" macros) % =========== % % In the entire discussion here, "uu" stands for some core unit, % or some unit corresponding to a dimension > 1pt. For the case -% of a unit corresponding to a dimension < 1pt, i.e. to +% of a unit corresponding to a dimension < 1pt, i.e. to % \texdimenwithunit macro added at 0.99, refer to the % comments of issue #2 on the tracker site. % @@ -378,39 +378,122 @@ % Mathematics % =========== % -% (see discussion #2) -% +% As explained in the README.md, 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. We first consider v=(T+0.5)/phi. As its +% distance to the extremities is 0.5/phi>0.5,(*) its rounding M +% to an integer verifies automatically T/phi < M < (T+1)/phi, so +% is a candidate. The TeX core conversion of Msp to a Dpt with +% D a decimal of at most 5 fractional digits will provide a D +% such that indeed M=round(D*65536). +% +% (*) attention that this fails if phi=1, we definitely can not +% use the N=round((2T+1)*32768/f) formula for f=65536, it gives +% N=T+1 in place of N=T... It is funny the formula works for all +% f apart from f=65536... +% +% Attention that if T=0, the M=round(0.5/phi) will be at least 1, +% so the produced decimal D will not be 0.0, although it will +% be true that D \dimexpr f sp\relax produces the zero dimension, +% the above analysis being correct even for T=0. +% % Implementation % ============== % -% (see discussion #2) -% #2 is unit, assumed positive. We will need to branch whether -% #2 is <1pt or >1pt. +% \texdimenwithunit{dim1}{dim2}. dim2>0 assumed. +% We first get f from dim2 and branch according to whether f>65536, +% f=65536 or f<65536. +% We will also need to check the sign of T (dim1=T sp). +% We then compute in both branches round((2T+1)*32768/f), but +% in an indirect way in the f<65536 branch to avoid overflow. +% We will need also to intercept T=0 to avoid producing a non +% pleasing positive D in that case (still correct for f<65536; +% this is issue #3). +% +% In the f<65536 branch we first do the Euclidean division +% 2T+1 = k*2*f + R with 0<=R<2f. The k is obtained as round((2T+1-f)/(2f)) +% which can be computed in a numexpr (and never gives -1 even for T=0) +% Then (2T+1)*32768/f=65536*k + R*32768/f +% Then R*32768/f<=65536-32768/f<65536-32768/65536=65536-0.5 +% Hence the numexpr evaluation gives an integer B<65536. +% +% N.B.: if f>=65536, we still have R*32768/f<65536 as R<2f +% so the only difference is that the B could be here 65536 +% +% From \the\dimexpr Bsp, we get a decimal E < 1, so E=0.ddddd +% (or less digits) and the looked for D will be the contatenation +% k.ddddd with k as obtained earlier. This procedure has no possible +% arithmetic overflow. +% +% #2 is assumed positive. The case #2=1pt=65536sp is special, and +% must be filtered out (issues #4, #6). % pre-multiplication of #1 by 2 here \def\texdimenwithunit#1#2{\expandafter\texdimenwithunit_ \the\numexpr\dimexpr#2\expandafter;\the\numexpr2*\dimexpr#1;}% -\def\texdimenwithunit_#1;#2{\ifnum#1>65535 - \expandafter\texdimenwithunit_A\else\expandafter\texdimenwithunit_B\fi - #2#1;% +\let\texdimenorthat\texdimenfirstofone +\def\texdimendothis#1#2\texdimenorthat#3{\fi#1}% +\def\texdimenwithunit_#1;#2{% + \ifnum#1=\p@\texdimendothis\texdimenwithunit_p@\fi + \ifnum#1>\p@\texdimendothis\texdimenwithunit_A\fi + \texdimenorthat\texdimenwithunit_B#2#1;% }% -% unit>=1pt, handle this as for bp +% we premultiplied the first argument by 2... must be undone now! +% and we must avoid overflow. Very underoptimal, but user is not +% supposed to do something as silly as actually using this unit=1pt case! +\def\texdimenwithunit_p@#1#2;#3;{\expandafter\texdimenstrippt\the\dimexpr\numexpr#1#3/2sp\relax}% +% unit>1pt, handle this as for bp. Attention it would be wrong for unit=1pt! \def\texdimenwithunit_A#1#2;#3;{\expandafter\texdimenstrippt \the\dimexpr\numexpr(#1#3+\if-#1-\fi1)*32768/#2sp\relax }% -% unit<1pt -% if dim1<0, simply negate result for dim1>0 as it can not be 0.0 -\def\texdimenwithunit_B#1{\if-#1\expandafter\texdimenwithunit_Bneg\fi\texdimenwithunit_Ba#1}% +% unit<1pt. Attention it would be wrong for unit=1pt! +% if dim1<0, simply negate result for dim1>0 as it can not possibly be 0.0 +% Indeed (2T+1)*32768/f will be at least 3*32768/f so its rounding at least 2. +% Add a check for dim1=0 to fix issue #3 +\def\texdimenzerominusfork #10-#2#3\krof {#2}% +\def\texdimenwithunit_B#1{\texdimenzerominusfork + #1-\texdimenwithunit_Bzero + 0#1\texdimenwithunit_Bneg + 0-\texdimenwithunit_Ba + \krof#1}% \def\texdimenwithunit_Ba#1#2;#3;{\expandafter\texdimenwithunit_Bb\the\numexpr#1#3+1;#2;}% \def\texdimenwithunit_Bb#1;#2;{\expandafter\texdimenwithunit_Bc\the\numexpr(#1-#2)/(2*#2);#1;#2;}% -% not adding f-expandability slight overhead here -% oh well, let's do \the\numexpr..+0.ddddd which does the trick and allows -% recycling strippt here with no need of another utility -% (thinking about it it means we could do this for unit >1pt as this method -% works even for \the\dimexpr producing 1pt or more... (as TeX outputs 1.0pt, not 1pt -% so the dot remains to stop the \numexpr scan) +% I was hesitating between leaving k in the stream (breaking f-expandability) +% and then remove the "0" and trailing "pt" from 0.ddddd pt, but opted +% finally for doing \the\numexpr..+0.ddddd which is f-expandable and allows +% recycling strippt here with no need of another utility. +% +% This means (see the nota bene above) that we could apply this procedure +% also for f>65536, because at worst we will get a \the\numexprk+1.0, which +% gives the correct result. I tested and found about 39% longer execution time +% if dim2>1pt does same calculations as for dim2<1pt, and at the same time +% dim2<1pt was improved about 11% from skipping the conditional \def\texdimenwithunit_Bc#1;#2;#3;{\the\numexpr#1+\expandafter\texdimenstrippt \the\dimexpr\numexpr(#2-#1*2*#3)*32768/#3sp\relax}% -% definitly not caring about f-expandability here -\def\texdimenwithunit_Bneg\texdimenwithunit_Ba-#1;#2;% - {-\expandafter\texdimenwithunit_Bb\the\numexpr#2+1;#1;}% +% Here, definitely not caring about f-expandability. Or efficiency. +\def\texdimenwithunit_Bneg-{-\texdimenwithunit_Ba{}}% +\def\texdimenwithunit_Bzero#1;#2;{0.0}% \texdimensendinput -- cgit v1.2.3