如何像 MS Excel 一样在 bash 中四舍五入到小数点后两位?

如何像 MS Excel 一样在 bash 中四舍五入到小数点后两位?

我花了几个小时搜索如何在 BASH 中对“浮动数字”进行舍入,但找不到任何正确的!解决方案:( 如果我将这些数字放入 Excel,那么我将在四舍五入到小数点后两位后收到正确的结果:

3.314 -> 3.31
3.315 -> 3.32
8.124 -> 8.12
8.125 -> 8.13

如何在BASH中得到准确的结果?我尝试使用printfandawk但没有得到相同的结果

prompt> printf '%.*f\n' 2 8.125
8.12
prompt> echo '8.125' | awk '{printf("%.2f\n", $1)}'
8.12

答案1

printf '%.2f\n' 8.125

8.125将使用标准strtod()strtold()C 函数之类的方法将十进制数的文本表示形式转换为内部二进制表示形式,获得一个doublelong double二进制浮点数,其内部表示形式将取决于 C 编译器和计算机体系结构。

然后该数字将通过命令传递printfprintf()标准函数(或其将结果返回为字符串的变体),该函数会将其转换回文本十进制表示形式并进行舍入。四舍五入到最接近的四舍五入,半舍入到偶数(也称为银行家四舍五入)。

您在学校学到的方法存在偏差,因为它往往会平均高估(对于正数)。半数为偶数时,8.125和均8.115四舍五入为8.122甚至),一位向下舍入,一位向上舍入。对于大量数字,将一半数字向下舍入,另一半向上舍入,就不会引入系统偏差。

现在,这只适用于可以用二进制精确表示的数字。

碰巧,8.125 可以用二进制表示。那是 2 3 + 2 -3。 8.135 不能。最接近的 IEEE 754 双精度二进制数是 8.1349999999999997868371792719699442386627197265625(转换回十进制时),因此它将转换为 8.13,因为它最接近,而不是偶数 (8.14)。

因此,要做你想做的事(甚至进行适当的银行四舍五入),你需要一些不能以二进制工作但以十进制工作的东西。因此,任何在下面使用的东西,printf()包括printf实用程序awk's printf(),或者更一般地与处理二进制数的处理器浮点算术功能一起工作,包括numfmt在内,都是不可能的。

你可以使用python3例如decimal模块(如由 @marcelm 显示) 或perlMath::BigFloat模块它是bfround()功能。默认情况下,他们这样做银行家四舍五入:

$ printf '%s\n' 1.1{0..5}5 | perl -MMath::BigFloat -lne 'print "$_ -> " . Math::BigFloat->new($_)->bfround(-2)'
1.105 -> 1.10
1.115 -> 1.12
1.125 -> 1.12
1.135 -> 1.14
1.145 -> 1.14
1.155 -> 1.16

但你可以切换到common 模式这与你在学校学到的相符:

$ printf '%s\n' {-,}1.1{0..5}5 +825e-3 0xAAp-6 0x1p100 nan Infinity | 
   perl -MMath::BigFloat -lne '
     print "$_ -> " . Math::BigFloat->new($_)->bfround(-2,common)'
-1.105 -> -1.11
-1.115 -> -1.12
-1.125 -> -1.13
-1.135 -> -1.14
-1.145 -> -1.15
-1.155 -> -1.16
1.105 -> 1.11
1.115 -> 1.12
1.125 -> 1.13
1.135 -> 1.14
1.145 -> 1.15
1.155 -> 1.16
+825e-3 -> 0.83
0xAAp-6 -> 2.66
0x1p100 -> 1267650600228229401496703205376.00
nan -> NaN
Infinity -> inf

(显示使用不同符号表示的额外数字)。

请注意, likepython3的方法与numfmt某些printf实用程序实现不同,它不区分区域设置。特别是,十进制基数字符将用于.输入和输出,即使在否则为,或 的语言环境中也是如此٫

答案2

如果您的系统使用 GNU coreutils 那么数字命令应该可用,并允许您在“向上”、“向下”、“从零开始”(默认)、“向零”或“最近”舍入之间进行选择。

例如(此处在十进制基数字符为 的区域设置中.):

$ numfmt --round=nearest --format %.2f << EOF
3.314
3.315
8.124
8.125
EOF
3.31
3.32
8.12
8.13

答案3

舍入为整数的标准方法是添加 ½ 并截断:

printf '%s\n' 1.5 2.5 3.5 4.5 |
    awk '{ print int($1 +.5) }'

要实现两位小数乘以 100、截断并再次除法:

printf '%s\n' 3.314 3.315 8.124 8.125 |
    awk '{ print int(100*$1 +.5)/100 }'

或者

printf '%s\n' 3.314 3.315 8.124 8.125 |
    awk '{ printf "%0.2f\n", int(100*$1 +.5)/100 }'

答案4

使用Python

python3 -c 'import decimal, sys; print(round(decimal.Decimal(sys.argv[1]), ndigits=2))' 3.314

PythonDecimal精确地表示以 10 为基数的十进制数,精度不受限制,而没有大多数其他答案所遇到的浮点精度问题。

decimal的默认上下文Pythonround()将半值舍入为偶数(称为银行家舍入),这是通常首选的舍入方法,因为它不会引入偏差。

示例输出:

3.314      ->  3.31
3.315      ->  3.32
8.124      ->  8.12
8.125      ->  8.12
10000.195  ->  10000.20

您还可以按照您想要的行为从 0 舍入一半:

$ python3 -c 'import sys; from decimal import *; getcontext().rounding = ROUND_HALF_UP; print(round(Decimal(sys.argv[1]),2))' 8.125
8.13

这会将 8.125 舍入为 8.13,将 -8.125 舍入为 -8.13。

getcontect().prec如果您需要处理可能舍入为 10 26或更大(或 -10 26或更小)的数字,请增加 的值(默认为 28)。

decimal文档了解详情。


1 由下式确定语境prec//EminEmax具有非常大(如果不是无限)的最大值

相关内容