bash循环,增量为0.02


11

我想在bash中以0.02为增量尝试进行for循环

for ((i=4.00;i<5.42;i+=0.02))
do
commands
done

但这没用。


9
Bash不执行浮点数学运算。
jordanm 2015年

1
可以通过增加bc,但是在4.52上停止可能会比较棘手。使用@roaima建议,在步骤2中使用辅助变量,并使用i=$(echo $tmp_var / 100 | bc)
Archemar


5
通常不希望将float用作循环索引。您每次迭代都会积累错误。
isanae

Answers:


18

阅读bash 手册页将提供以下信息:

for (( expr1 ; expr2 ; expr3 )) ; do list ; done

首先,expr1根据以下在算术评估中所述的规则对算术表达式进行求值。[...]

然后我们得到这个部分

算术评估

该Shell允许在某些情况下对算术表达式进行求值(请参阅letand declare内置命令和算术扩展)。以固定宽度的整数进行评估,不检查溢出[...]

因此可以清楚地看到,您不能使用for具有非整数值的循环。

一种解决方案可能就是将所有循环组件乘以100,然后在以后使用它们的地方允许这样做,如下所示:

for ((k=400;k<542;k+=2))
do
    i=$(bc <<<"scale=2; $k / 100" )    # when k=402 you get i=4.02, etc.
    ...
done

我认为这是最好的解决方案,k=400;k<542;k+=2它还避免了潜在的浮点运算麻烦。
惠更斯

1
请注意,对于循环中的每个迭代,您都会创建一个管道(以读取的输出bc),派生一个进程,创建一个临时文件(用于here-string),bc在其中执行(这意味着加载可执行文件和共享库,初始化它们),等待它并进行清理。运行bc一次以执行循环会更有效率。
斯特凡Chazelas

@StéphaneChazelas是的,同意。但是,如果这是瓶颈,那么我们可能还是用错误的语言编写了代码。OOI效率较低(!)?i=$(bc <<< "scale...")i=$(echo "scale..." | bc)
roaima

1
根据我的快速测试,管道版本在zsh(<<<来自)中更快,bash并且ksh。请注意,切换到其他shell bash比使用其他语法会带来更好的性能提升。
斯特凡Chazelas

(和大部分炮弹的支持<<<(zsh的,mksh,ksh93的,佳日)也支持浮点算术(zshksh93yash))。
斯特凡Chazelas

18

避免在Shell中循环。

如果要算术,请使用awkbc

awk '
  BEGIN{
    for (i = 4.00; i < 5.42; i+ = 0.02)
      print i
  }'

要么

bc << EOF
for (i = 4.00; i < 5.42; i += 0.02)  i
EOF

请注意awk(与相对bc)与您的处理器double浮点数表示形式(可能是IEEE 754类型)一起使用。结果,由于这些数字是这些十进制数字的二进制近似值,因此您可能会有些意外:

$ gawk 'BEGIN{for (i=0; i<=0.3; i+=0.1) print i}'
0
0.1
0.2

如果添加OFMT="%.17g",则可以看到丢失的原因0.3

$ gawk 'BEGIN{OFMT="%.17g"; for (i=0; i<=0.5; i+=0.1) print i}'
0
0.10000000000000001
0.20000000000000001
0.30000000000000004
0.40000000000000002
0.5

bc 做任意精度,所以没有这种问题。

请注意,在默认情况下(除非你修改与输出格式OFMT或使用printf有明确的格式规范),awk用途%.6g,用于显示浮点数,因此会切换到1E6及以上浮点数上述1,000,000,截断高数的小数部分(100000.02将显示为100000)。

如果您确实确实需要使用Shell循环,例如由于您想为该循环的每次迭代运行特定的命令,请使用具有浮点运算支持的Shell(例如)zshyashksh93使用上述一个命令生成值列表(或者,seq如果有的话)并循环其输出。

喜欢:

unset -v IFS # configure split+glob for default word splitting
for i in $(seq 4 0.02 5.42); do
  something with "$i"
done

要么:

seq 4 0.02 5.42 | while IFS= read i; do
  something with "$i"
done

除非您突破处理器浮点数的限制,否则seq与上述awk版本相比,可以更优雅地处理浮点近似所引起的错误。

如果您没有seq(一个GNU命令),则可以使用以下函数来使它更可靠:

seq() { # args: first increment last
  bc << EOF
    for (i = $1; i <= $3; i += $2) i
EOF
}

这样对诸如此类的东西会更好seq 100000000001 0.000000001 100000000001.000000005。但是请注意,如果要将数字传递给不支持数字的命令,则具有任意高精度的数字将无济于事。


我感谢使用awk!+1
潘迪2015年

为什么unset IFS在第一个示例中需要?
user1717828 2015年

@ user1717828,理想情况下,通过split + glob调用,我们希望拆分换行符。我们可以做到这一点,IFS=$'\n'但并非在所有shell中都有效。或者,IFS='<a-litteral-newline-here>'但这不是很清晰。或者,我们可以拆分单词(空格,制表符,换行符),就像您使用默认值$ IFS所获得的一样,或者如果您未设置IFS并且也可以在此处使用。
斯特凡Chazelas

@ user1717828:我们不需要弄乱IFS,因为我们知道seq的输出没有需要避免分裂的空格。大部分用于确保您意识到该示例取决于IFS,这对于其他列表生成命令可能很重要。
彼得·科德斯

1
@PeterCordes,在那里,所以我们不需要对IFS预先设置的内容进行任何假设。
斯特凡Chazelas


0

正如其他人所建议的,您可以使用bc:

i="4.00"

while [[ "$(bc <<< "$i < 5.42")" == "1" ]]; do
    # do something with i
    i="$(bc <<< "$i + 0.02")"
done
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.