此代码
\documentclass{report}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}
\draw[dashed] (1,-1) -- (1,2) node[above] {$1$};
\draw[dashed] (2,-1) -- (2,2) node[above] {$2$};
\foreach \x in {1,1.1,...,2}
{\draw[fill = yellow] (\x,1) circle (0.05);}
\foreach \x in {1,1.2,...,2}
{\draw[fill = red] (\x,0) circle (0.05);}
\end{tikzpicture}
\end{document}
产生以下输出。
第一个函数\foreach
生成黄色圆圈。在集合 {1,1.1,...,2} 中迭代的最后一个元素 2 处没有圆圈。
第二个\foreach
生成红色圆圈。在 2 处有一个圆圈,它是集合 {1,1.2,...,2} 中迭代的最后一个元素。
为什么这两个命令的行为不同?
答案1
简短回答
您的示例所示的问题是由于舍入误差造成的。请参阅 TikZ/PGF 文档(v2.10 中的第 56 节,第 505 页;或 v3.0 中的第 83 节,第 910 页):
[...] 对于不是 2^{-n} 倍数的小数步长,对于某些较小的 n,舍入误差很容易发生。因此,在 中
\foreach \x in {0,0.1,...,0.5} {\x, }
,0.5
应该用 代替,0.501
以提高稳健性。
在您的特定示例中,对列表的最后一个元素使用略大于 2 的值(例如 2.0001),例如
\foreach \x in {1,1.1,...,2.0001}
给出一些舍入误差的余地并解决问题。
然而,避免舍入误差问题的最好方法是,如g.kov 的回答,percusse 的评论, 和Qrrbrbirlbel 的评论,迭代整数并在其他地方计算感兴趣的值,就像这样
\foreach \xx[evaluate={\x=\xx/10}] in {10,11,...,20}
或者在正文中\foreach
:
\foreach \xx in {10,11,...,20}{
\pgfmathsetmacro\x{.1*\xx}
...
}
有关更详细的解释,请参见下文。
TeX 的定点数字系统
一些实数具有无限的二进制展开,即它们的小数部分包含无限重复的位模式。例如,
0.1(十进制形式)
对应于
0.00011001100110011001100110011...(二进制形式)
位模式“0011”不断重复“永远”。由于有限精度机器数仅为给定实数的小数部分分配有限数量的位,因此它们无法存储无限扩展,因此必须进行一些舍入。TeX 使用 32 位定点数表示系统,其中
- 1 位用于标记溢出(如果有),
- 1 位用于编码数字的符号,
- 14 位用于数字的整数部分,
- 16 位用于数字的小数部分。
它遵循
分别是最大和最小TeX 数字。我所说的“TeX 数字”是指可以表示的数字确切地使用 TeX 数字表示系统。任何大于 16383.99999 的实数都会导致溢出此外,有限精度数系只能表示有限个实数,这意味着-16383.99999 +16383.99999 之间的某些实数不能被表示为确切地用 TeX 数字表示;这些实数只能表示大约通过 TeX 数字。
我不确定 TeX 中的舍入规则是什么,但似乎(正)实数会得到
- 如果二进制小数点右边的第 17 位(二进制展开式)为 1,则向上舍入,
- 如果二进制小数点右边的第 17 位(二进制展开式)为 0,则截断,
对于 0.1 来说,二进制小数点右边的第 17 位是 1;因此,0.1 会被向上舍入到下一个 TeX 数字,即
0.0001100110011010 (二进制形式)
(与上面写的准确值 0.1 比较。)总之,TeX 操作的数字不是 0.1,而是一个略大于 0.1 的数字。相反
0.2(十进制形式)
对应于
0.001100110011001100110011...(二进制形式)
二进制小数点后,模式“0011”会“永远”重复。二进制小数点右边的第 17 位是 0;因此,0.2 会被截断/四舍五入为前一个 TeX 数字,即
0.001100110011011 (二进制形式)
(与上面写的精确值 0.2 进行比较。)总而言之,TeX 操作的数字不是 0.2,而是一个略小于 0.2 的数字。
机制\foreach
编译
\documentclass{article}
\usepackage{pgffor}
\begin{document}
\noindent
\foreach \x in {1,1.1,...,2}{\x\\ } \\
\foreach \x in {1,1.2,...,2}{\x\\ }
\end{document}
你会得到
为了理解这个输出,你需要了解\foreach
一下点符号有效。当\foreach
遇到...
列表参数中的元素时,如\foreach \i in {x,y,...,z}
,它会计算差值d
= y
-x
以填充“缺失值”。根据 TikZ/PGF 手册 (p.505),
列表读数部分
x,y,...,z
被替换为x
,x + d
,x + 2d
,x + 3d
, ... ,x + md
其中最后的点是语义点,而不是句法点。该值m
是最大数字,使得x + md
<=z
ifd
为正数,或x + md
>=z
ifd
为负数。
在这种情况下\foreach \x in {1,1.1,...,2}
,前两项之间的差异是TeX 编号 0.1
如上所述,它略大于实数0.1。因此, <=m
的最大值是 9。结果,循环访问的最终值不是 2,而是一个略大于1+0.1*m
2
数学数1.9.
在这种情况下\foreach \x in {1,1.2,...,2}
,前两项之间的差异是TeX 编号 0.2
如上所述,略小于实数0.2。因此, <=m
的最大值是 5。结果,循环访问的最终值不完全是 2,而是一个仅比 2 略小的 TeX 数字,因此与 相比,在这种情况下获得的输出更令人满意。1+0.2*m
2
\foreach \x in {1,1.1,...,2}
参考
pgffor
的\foreach
命令:TikZ/PGF 手册,第 56 条- TeX 中的定点运算:http://www.tug.org/TUGboat/tb28-3/tb90beebe.pdf
- 使用非整数的危险为了循环:http://en.wikipedia.org/wiki/Control_flow#Count-controlled_loops
- PGF 引擎:TikZ/PGF 手册,第 61-62 节
答案2
这两个命令的行为完全相同。舍入误差是第一种情况下“遗漏”终端值的原因。此检查
\documentclass{report}
\usepackage{tikz}
\begin{document}
\foreach \x in {1,1.1,...,2}
{\number\x\ }
\foreach \x in {1,1.2,...,2}
{\number\x\ }
\end{document}
结果是
1 1.1 1.20001 1.30002 1.40002 1.50003 1.60004 1.70004 1.80005 1.90005
1 1.2 1.4 1.59999 1.79999 1.99998
因此,下一个值将大于2
第一种情况。
经验法则是:永远不要在循环中使用非整数作为步长值。
答案3
是的,舍入误差,但它不能解释一切。\foreach
不使用省略号列表的上限(...)
,而是依赖于通过增量(增量)将计算值与上限进行比较。\newforeach
强制执行上限,相信用户希望将上限用作明确的点/值。
\documentclass{article}
\usepackage{tikz}
\usepackage{loops}[2013/05/01]
\begin{document}
\begin{tikzpicture}
\draw[dashed] (1,-1) -- (1,2) node[above] {$1$};
\draw[dashed] (2,-1) -- (2,2) node[above] {$2$};
\newforeach \x in {1,1.1,...,2}{
\draw[fill = yellow] (\x,1) circle (0.05);
}
\newforeach \x in {1,1.2,...,2}{
\draw[fill = red] (\x,0) circle (0.05);
}
\end{tikzpicture}
\end{document}