背景:
我被索引生成过程中出现的问题困住了"Dimension too large"
。这是一个包含文件链接的大型索引(以及其他相关信息),我确信问题不在于索引包,而在于我添加相关信息的代码。我在实际用例中收到的消息是:
./FileName-FileName.ind:8741:尺寸太大。\pgfmath@x
l.8741 ...文件路径} \hyperpage{119} ?
如果我减少这个索引的大小(即使它包含了被链接到的有问题的文件,如上面的错误消息所列),问题仍然存在不是发生。因此,这不是错误消息中列出的文件的问题,而是确定要添加哪些相关信息的宏的问题。
如果我能得到堆栈转储并知道问题发生前代码调用了哪一部分pgfmath
,那么我就能有希望确定问题的根源。在问题出现之前,相同的代码被执行了 1000 次,所以我不能通过在代码中放入未定义的宏来手动跟踪来提前终止。
示例测试用例
虽然下面“编写”的 MWE 中的事情很明显,但它们并不在我的实际代码中。下面的 MWE 终止于:
./TeX-SE.tex:38: 尺寸太大。
无法再次读取
\relax l.38 }
%
因此,从错误消息中我知道问题出在主源代码中。但是从那里,我如何确定问题出在哪里?还请注意,此处的错误消息并未表明问题出在某个pgfmath
函数上。
参考:
笔记:
- 在下面的 MWE 中,问题的根源很明显,并且可以进行多种修复。但是,在我的实际使用案例中,我尚未能够缩小具有类似错误消息的问题根源。因此,这个问题是关于如何我是否缩小了问题根源的范围,不是我该如何解决具体的问题..
代码:
\begin{filecontents*}{GrillFunctions.sty}
\newcommand*{\FunctionValue}[1]{%
% ... lots of code ....
\pgfmathsetmacro{\NewValue}{\arabic{MyCounter}/100}%
\num{\NewValue}%
% ... lots of code ....
}
\end{filecontents*}
\begin{filecontents*}{GrillMacros.sty}
\newcommand*{\DeterminePercentage}[1]{%
% ... lots of code ....
% ... Don't know what in here is creating the problem.
% Obvious given this MWE, but in actual usage not so obvious
\FunctionValue{#1}%
% ... lots of code ....
}
\end{filecontents*}
\documentclass[12pt]{article}
\usepackage{siunitx}
\usepackage{xstring}
\usepackage{tikz}
\usepackage{GrillFunctions}
\usepackage{GrillMacros}
\newcounter{MyCounter}
\setcounter{MyCounter}{100000}
\begin{document}
\IfStrEq{\arabic{MyCounter}}{0}{%
Value of counter is zero.%
}{%
Value of counter is \DeterminePercentage{\arabic{MyCounter}}.%
}%
\end{document}
答案1
因此,如果我正在调试这个,我会添加跟踪,这样 MWE 看起来就像
\begin{document}
\tracingmacros2
\tracingassigns2
然后运行 MWE,在出现错误时停止:
! Dimension too large.
<to be read again>
\relax
l.39 }
%
? x
No pages of output.
然后在编辑器中查看日志文件,从错误开始:
#1<-\pgfmath@operand@b
#2<-\pgfmath@operand@a
\pgfmath@operand@b ->100000
! Dimension too large.
<to be read again>
\relax
l.39 }
所以我们知道\pgfmath@operand@b
最后一个宏扩展的第一个参数最终是 100000,在这个上下文中太大了。有趣的是 100000 这个值从何而来。因此,最简单的方法就是向后搜索 100000,而不是试图追踪完整的扩展逻辑
它出现在不同的地方,例如
{changing \pgfmath@stack@operand@top=macro:->100000}
但在那种情况下它来自
#1<-100000
即它是该宏的第一个参数,因此回过头来看看它来自哪里......
\pgfmathresult ->100000
看起来很有希望......
但继续
#1<-100000/100
有点线索,它试图计算出百分比......
\@arabic #1->\number #1
#1<-\c@MyCounter
{changing \@xs@arg@i=\relax}
{into \@xs@arg@i=\long macro:->100000}
嗯,100000 是的阿拉伯值,MyCounter
并且没有更早出现的 100000(因为我开始追踪之后,begin{document}
我本可以在开始追踪的时候开始追踪,\setcounter{MyCounter}{100000}
但那样就是作弊了:-)
因此,日志基本上会告诉您,LaTeX 计数器MyCounter
在计算中使用时,其值为 100000,这个值太大了。此时,您要么认识到自己处于什么位置以及需要更改什么,要么将跟踪移回(也许打开\tracingifs
或打开其他跟踪选项,看看如何以及为什么MyCounter
得到这个值...
因此,尽管日志有 2500 行,但如果您倒着读,只读其中的六行,还是相当易读的:-)
答案2
TeX 是一种宏扩展语言,因此没有堆栈跟踪:当宏\foo
扩展为替换文本时,不会记录该宏来自一个宏而不是另一个宏或只是直接作为源的一部分的事实。因此,调试 TeX 问题需要使用 TeX 提供的工具采用不同的方法。
查找问题的最常用方法可能就是使用宏\tracingall
,该宏在纯 TeX、LaTeX 和 ConTeXt 中均可用。\tracingall
设置一些寄存器后,TeX 会在日志文件中打印其他信息。如评论中所述,原始设置(\tracingmacros=2\relax
属于的一部分\tracingall
)可能是在追踪因宏扩展而导致的问题时使用的最有用的设置。添加\tracingall
或\tracingmacros=2\relax
可能会导致日志文件非常长,然后需要手动读取。在寻找错误时,最简单的方法可能是搜索错误行,然后向后阅读以查看导致错误的原因。
对于这种更复杂的情况,其他技术也很有用。其中一种是“经典”的,即通过插入一些未定义的控制序列来强制 TeX 暂停运行(例如 \ERROR
) 部分地追踪源头。这有助于确定问题发生的位置,从而减少所需的跟踪量。
对于循环情况,在循环中添加一些代码可能会很有用,例如将寄存器值打印到日志中(例如 \immediate\write-1{Value is: \the\RegisterOfInterest}
)或者仅当满足某些条件时才开始跟踪(例如 \ifnum\mycount>1000 \expandafter\tracingall\fi
)。
除了这些“标准”技术之外,它通常还需要仔细修改用来“提取”重要信息的来源和定义,然后构建一些测试用例并系统地进行。
以问题中的例子来说,如果我\tracingmacros
在条件之前应用\IfStrEq
并在条件之后将其关闭,我会得到 14449 行的日志,错误在第 2066 行。错误之前的相关行如下:
\pgfmathdivide@ #1#2->\begingroup \pgfmath@x =#1pt\relax \pgfmath@y =#2pt\relax
\let \pgfmath@sign =\pgfmath@empty \ifdim 0pt=\pgfmath@y \pgfmath@error {You'v
e asked me to divide `#1' by `#2', but I cannot divide any number by `#2'}\fi \
afterassignment \pgfmath@xa \c@pgfmath@counta \the \pgfmath@y \relax \ifdim 0pt
=\pgfmath@xa \divide \pgfmath@x by\c@pgfmath@counta \else \ifdim 0pt>\pgfmath@x
\def \pgfmath@sign {-}\pgfmath@x =-\pgfmath@x \fi \ifdim 0pt>\pgfmath@y \expan
dafter \def \expandafter \pgfmath@sign \expandafter {\pgfmath@sign -}\pgfmath@y
=-\pgfmath@y \fi \ifdim 1pt>\pgfmath@y \pgfmathreciprocal@ {\pgfmath@tonumber
{\pgfmath@y }}\pgfmath@x =\pgfmath@sign \pgfmathresult \pgfmath@x \else \def \p
gfmathresult {0}\pgfmath@divide@periodtrue \c@pgfmath@counta =0\relax \pgfmathd
ivide@@ \pgfmath@x =\pgfmath@sign \pgfmathresult pt\relax \fi \fi \pgfmath@retu
rnone \pgfmath@x \endgroup
#1<-\pgfmath@operand@b
#2<-\pgfmath@operand@a
\pgfmath@operand@b ->100000
! Dimension too large.
<to be read again>
\relax
l.39 }
%
?
我们在这里看到的是 TeX使用as和as扩展\pgfmathdivide@
了那个相当长的定义。TeX 现在处理这些标记:它开始一个组(它没有出现在这个跟踪中:我会回到它),然后到达\pgfmath@operand@b
#1
\pgfmath@operand@b
#2
\pgfmath@x =#1pt\relax
这是一个原始的寄存器分配。这需要#1
扩展,所以我们在日志中看到\pgfmath@operand@b ->100000
(记住#1
是\pgfmath@operand@b
)。然后会导致错误,因为值太大而无法分配给寄存器(a 的限制dimen
是16383.99998pt
,并且pt
这里用作单位的100000
as 太大)。
如果我还设置了,\tracingcommands=2\relax
我会得到更长的日志,但这里有更多信息:
\pgfmathdivide@ #1#2->\begingroup \pgfmath@x =#1pt\relax \pgfmath@y =#2pt\relax
\let \pgfmath@sign =\pgfmath@empty \ifdim 0pt=\pgfmath@y \pgfmath@error {You'v
e asked me to divide `#1' by `#2', but I cannot divide any number by `#2'}\fi \
afterassignment \pgfmath@xa \c@pgfmath@counta \the \pgfmath@y \relax \ifdim 0pt
=\pgfmath@xa \divide \pgfmath@x by\c@pgfmath@counta \else \ifdim 0pt>\pgfmath@x
\def \pgfmath@sign {-}\pgfmath@x =-\pgfmath@x \fi \ifdim 0pt>\pgfmath@y \expan
dafter \def \expandafter \pgfmath@sign \expandafter {\pgfmath@sign -}\pgfmath@y
=-\pgfmath@y \fi \ifdim 1pt>\pgfmath@y \pgfmathreciprocal@ {\pgfmath@tonumber
{\pgfmath@y }}\pgfmath@x =\pgfmath@sign \pgfmathresult \pgfmath@x \else \def \p
gfmathresult {0}\pgfmath@divide@periodtrue \c@pgfmath@counta =0\relax \pgfmathd
ivide@@ \pgfmath@x =\pgfmath@sign \pgfmathresult pt\relax \fi \fi \pgfmath@retu
rnone \pgfmath@x \endgroup
#1<-\pgfmath@operand@b
#2<-\pgfmath@operand@a
{\begingroup}
{\dimen153}
\pgfmath@operand@b ->100000
! Dimension too large.
<to be read again>
\relax
l.40 }
%
?
请注意,通过命令跟踪,TeX 向我展示了\begingroup
正在执行的命令以及它试图分配寄存器的事实,这恰好是在这种情况下发生的\dimen153
(您可以在之前的日志中检查)。\pgfmath@x
\dimen153
你会发现在这个例子pgf
中很多的工作,你得到非常长日志:使用适当的工具来查看它(类似于less
“程序员编辑器”,而不是试图一次性将所有文件加载到内存中的东西)。
答案3
下面解释并消除了测试用例的问题。它旨在帮助人们解决特定问题,但不是一般调试情况的答案。
的数学引擎pgf
将没有单位的值视为值pt
。因此,您100000pt
隐式使用了导致错误的单位。如果您故意使用单位(在我的答案中sp
),则可以规避该问题。我的代码给出了您可能想要的结果(?)。如果一切正常,100
您的代码会给出结果。如果这确实是您的意图,请在下面的代码中替换为。1000
65.536
655.36
\RequirePackage{filecontents}
\begin{filecontents*}{GrillFunctions.sty}
\newcommand*{\FunctionValue}[1]{%
% ... lots of code ....
\pgfmathsetmacro{\NewValue}{\arabic{MyCounter}sp*65.536}% <----------
\num[round-mode=places,round-precision=2]{\NewValue}%
% ... lots of code ....
}
\end{filecontents*}
\begin{filecontents*}{GrillMacros.sty}
\newcommand*{\DeterminePercentage}[1]{%
% ... lots of code ....
% ... Don't know what in here is creating the problem.
% Obvious given this MWE, but in actual usage not so obvious
\FunctionValue{#1}%
% ... lots of code ....
}
\end{filecontents*}
\documentclass[12pt]{article}
\usepackage{siunitx}
\usepackage{xstring}
\usepackage{tikz}
\usepackage{GrillFunctions}
\usepackage{GrillMacros}
\newcounter{MyCounter}
\setcounter{MyCounter}{100000}
\begin{document}
\IfStrEq{\arabic{MyCounter}}{0}{%
Value of counter is zero.%
}{%
Value of counter is \DeterminePercentage{\arabic{MyCounter}}.%
}%
\end{document}
答案4
如您所见,计数器对于涉及 TeX 边界数字的计算也受到限制。最好切换到fpu
库,这样您在 2^324 以内就不会遇到任何问题。除此之外,您无论如何都应该使用适当的工具而不是 TeX :) 这是一个相当大的数字fpu
。如果我没记错的话,最近的 L3 数学也可以处理这些。
\documentclass[12pt]{article}
\usepackage{xstring,tikz}
\usetikzlibrary{fpu}
\def\FunctionValue#1{%
\pgfkeys{/pgf/fpu}%
\pgfmathsetmacro{\NewValue}{#1/100}%
\pgfmathprintnumber[fixed,1000 sep={}]{\NewValue}%
\pgfkeys{/pgf/fpu=false}%
}
\def\DeterminePercentage#1{\FunctionValue{#1}}
\def\myval{100000000000000000000000000000000000000}
\begin{document}
Value of counter is \DeterminePercentage{\myval}.%
\end{document}