bc
不喜欢用科学记数法(又名指数记数法)表示的数字。
$ echo "3.1e1*2" | bc -l
(standard_in) 1: parse error
但我需要使用它来处理以这种表示法表示的一些记录。有没有办法让bc
理解指数符号?如果没有,我该怎么做才能将它们翻译成bc
可以理解的格式?
不幸的是,bc不支持科学记数法。
但是,它可以转换为 bc 可以处理的格式,使用 sed 中 POSIX 的扩展正则表达式:
sed -E 's/([+-]?[0-9.]+)[eE]+?(-?)([0-9]+)/(1*10^23)/g' <<<"$value"
您可以将"e"(或"e+",如果指数为正)替换为"*10^",BC 会立即理解。即使指数为负数或数字随后乘以另一个幂,这也有效,并允许跟踪有效数字。
如果您需要坚持使用基本正则表达式 (BRE),则应使用此方法:
sed 's/([+-]{0,1}[0-9]*.{0,1}[0-9]{1,})[eE]+{0,1}(-{0,1})([0-9]{1,})/(1*10^23)/g' <<<"$value"
来自评论:
简单的 bash 模式匹配不起作用(谢谢@mklement0),因为没有办法匹配 e+ 并同时保持 - 来自 e- 。
一个正常工作的perl解决方案(谢谢@mklement0)
$ perl -pe 's/([-d.]+)e(?:+|(-))?(d+)/($1*10^$2$3)/gi' <<<"$value"
感谢 @jwpat7 和 @Paul Tomblin 澄清了 sed 语法的各个方面,以及改进答案的@isaac和@mklement0。
编辑:
多年来,答案发生了很大变化。上面的答案是截至2018年5月17日的最新迭代。这里报告的先前尝试是纯 bash 的解决方案(通过 @ormaaj)和 sed(通过 @me)的解决方案,至少在某些情况下失败。我将把它们留在这里只是为了理解评论,这些评论包含比这个答案更好的解释所有这些的复杂性。
value=${value/[eE]+*/*10^} ------> Can not work.
value=`echo ${value} | sed -e 's/[eE]+*/\*10\^/'` ------> Fail in some conditions
让我尝试总结现有的答案,并在下面对每个答案进行评论:
-
(a)如果你确实需要使用
bc
进行任意精度的计算——就像OP所做的那样——使用OP自己的聪明方法,该方法在文本上将科学记数法重新格式化为bc
理解的等效表达式。 -
如果可能失去精度不是问题,
- (b)考虑使用
awk
或perl
作为bc
替代方案;两者都天生理解科学记数法,如JWPat7对AWK的回答所示。 - (c)考虑使用
printf '%.<precision>f'
简单地文本转换为常规浮点表示(小数,不带e
/E
)(Ormaaj在已删除的帖子中提出的解决方案)。
- (b)考虑使用
(a) 将科学记数法重新格式化为等同bc
表达
方式这种解决方案的优点是保留了精度:文本表示被转换为bc
可以理解的等效文本表示,并且bc
本身能够进行任意精度计算。
请参阅OP自己的答案,其更新的形式现在能够将包含指数表示法的多个数字的整个表达式转换为等效的bc
表达式。
(b) 使用awk
或perl
代替bc
作为计算器
注意:以下方法假定使用对awk
和perl
中的双精度浮点值的内置支持。 正如浮点算术固有的那样,
"给定任何固定数量的位,大多数具有实数的计算将产生无法使用那么多位精确表示的数量。因此,浮点计算的结果通常必须四舍五入,以便重新适应其有限表示形式。这种舍入误差是浮点计算的特征。(http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html)
可是
GNUawk 提供了支持任意精度算术的选项 - 参见 https://www.gnu.org/software/gawk/manual/html_node/Gawk-and-MPFR.html;但是,发行版可能包含也可能不包含该支持 - 通过检查
gawk --version
的输出来验证支持GNU MPFR
和GNU MP
。
如果支持可用,则必须在给定调用中使用-M
(--bignum
)激活它。Perl 通过
Math::BigFloat
包提供可选的任意精度十进制支持 - 参见 https://metacpan.org/pod/Math::BigFloat
awk
awk
天生理解十进制指数(科学)符号。
(通常应仅使用十进制表示形式,因为awk
实现在是否支持具有其他基数的数字文本方面有所不同。
awk 'BEGIN { print 3.1e1 * 2 }' # -> 62
如果使用默认的print
函数,则OFMT
变量通过printf
格式字符串控制输出格式;(POSIX强制)默认值为%.6g
,表示6个有效数字,其中特别包括整数部分中的数字。
请注意,如果科学记数法中的数字作为输入提供(而不是awk程序的文字部分),则必须添加+0
以强制将其转换为默认输出格式,如果单独与print
一起使用:
根据您的语言环境和您使用的awk
实现,您可能需要将小数点(.
) 替换为适合区域设置的基数字符,例如德语语言环境中的,
;适用于 BSDawk
、mawk
和带有--posix
选项的 GNUawk
。
awk '{ print $1+0 }' <<<'3.1e1' # -> 31; without `+0`, output would be the same as input
修改变量OFMT
会更改默认输出格式(对于带有小数部分的数字;(有效)整数总是这样输出)。
或者,使用具有显式输出格式的printf
函数:
awk 'BEGIN { printf "%.4f", 3.1e1 * 2.1234 }' # -> 65.8254
佩尔
perl
天生也理解十进制指数(科学)符号。
注意:与awk不同,Perl默认在所有类似POSIX的平台上都可用;此外,它不像awk那么轻量级。
但是,它提供了比awk更多的功能,例如本机理解十六进制和八进制整数。
perl -le 'print 3.1e1 * 2' # -> 62
我不清楚Perl的默认输出格式是什么,但它似乎是%.15g
的。 与awk一样,您可以使用printf
来选择所需的输出格式:
perl -e 'printf "%.4fn", 3.1e1 * 2.1234' # -> 65.8254
(c) 使用printf
将科学记数法转换为小数部分
如果您只想将科学记数法(例如,1.2e-2
)转换为小数部分(例如,0.012
),printf '%f'
可以为您做到这一点。 请注意,您将通过浮点运算将一个文本表示转换为另一个文本表示,这受到与awk
和perl
方法相同的舍入误差的影响。
printf '%.4f' '1.2e-2' # -> '0.0120'; `.4` specifies 4 decimal digits.
可以使用awk;例如,
awk '{ print +$1, +$2, +$3 }' <<< '12345678e-6 0.0314159e2 54321e+13'
生成(通过 awk 的默认格式 %.6g)输出,如12.3457 3.14159 543210000000000000
而像下面两个这样的命令会产生每个命令后面显示的输出,因为该文件edata
包含稍后显示的数据。
$ awk '{for(i=1;i<=NF;++i)printf"%.13g ",+$i; printf"n"}' < edata`
31 0.0312 314.15 0
123000 3.1415965 7 0.04343 0 0.1
1234567890000 -56.789 -30
$ awk '{for(i=1;i<=NF;++i)printf"%9.13g ",+$i; printf"n"}' < edata
31 0.0312 314.15 0
123000 3.1415965 7 0.04343 0 0.1
1234567890000 -56.789 -30
$ cat edata
3.1e1 3.12e-2 3.1415e+2 xyz
123e3 0.031415965e2 7 .4343e-1 0e+0 1e-1
.123456789e13 -56789e-3 -30
另外,关于使用sed
的解决方案,最好通过正则表达式[eE]+*
,最好在e
的同时删除45e+3
等加号,而不是在单独的sed
表达式中。 例如,在我的 Linux 机器上使用 GNU sed 版本 4.2.1 和 bash 版本 4.2.24,命令sed 's/[eE]+*/*10^/g' <<< '7.11e-2 + 323e+34'
sed 's/[eE]+*/*10^/g' <<< '7.11e-2 + 323e+34' | bc -l
产生输出7.11*10^-2 + 323*10^34
3230000000000000000000000000000000000.07110000000000000000
您还可以定义一个调用awk的bash函数(一个好的名字是等号"="):
= ()
{
local in="$(echo "$@" | sed -e 's/[/(/g' -e 's/]/)/g')";
awk -v CONVFMT=%.15g 'BEGIN {print '"$in"' ""}' < /dev/null
}
然后,您可以在 shell 中使用所有类型的浮点数学运算。请注意,这里使用方括号而不是圆括号,因为后者必须通过引号保护免受 bash 的影响。
> = 1+sin[3.14159] + log[1.5] - atan2[1,2] - 1e5 + 3e-10
-99999.058179847
或者在脚本中分配结果
a=$(= 1+sin[4])
echo $a # 0.243198
幸运的是,有printf,它可以完成格式化工作:
上面的例子:
printf "%.12f * 2n" 3.1e1 | bc -l
或浮点比较:
n=8.1457413437133669e-02
m=8.1456839223809765e-02
n2=`printf "%.12f" $n`
m2=`printf "%.12f" $m`
if [ $(echo "$n2 > $m2" | bc -l) == 1 ]; then
echo "n is bigger"
else
echo "m is bigger"
fi
管道版本的 OP 接受答案
$ echo 3.82955e-5 | sed 's/[eE]+*/*10^/'
3.82955*10^-5
将输入管道连接到 OP 接受的 sed 命令会给出额外的反斜杠,例如
$ echo 3.82955e-5 | sed 's/[eE]+*/\*10\^/'
3.82955*10^-5
我设法用一点技巧做到了。你可以做这样的事情——
scientific='4.8844221e+002'
base=$(echo $scientific | cut -d 'e' -f1)
exp=$(($(echo $scientific | cut -d 'e' -f2)*1))
converted=$(bc -l <<< "$base*(10^$exp)")
echo $converted
>> 488.4422100
试试这个(在CFD输入数据的例子中找到这个,用于处理m4:)
T0=4e-5
deltaT=2e-6
m4 <<< "esyscmd(perl -e 'printf (${T0} + ${deltaT})')"
试试这个:(使用 bash)
printf "scale=20n0.17879D-13n" | sed -e 's/D/*10^/' | bc
或者这个:
num="0.17879D-13"; convert="`printf "scale=20n$numn" | sed -e 's/D/*10^/' | bc`" ; echo $convert
.00000000000001787900
num="1230.17879"; convert="`printf "scale=20n$numn" | sed -e 's/D/*10^/' | bc`" ; echo $convert
1230.17879
如果你有正指数,你应该使用这个:
num="0.17879D+13"; convert="`printf "scale=20n$numn" | sed -e 's/D+/*10^/' -e 's/D/*10^/' | bc`" ; echo $convert
1787900000000.00000
最后一个将处理抛给它的每一个数字。如果您有以"e"或"E"作为指数的数字,则可以调整"sed"。
您可以选择所需的比例。
从浮点的角度来看,科学表示之间存在差异(例如1.1E2
),及其看似等效的数字基数 10 计算 (1.1*10^2
)。主要原因是某些数字无法准确表示为二进制数。因此,计算中会引入浮点误差(参见浮点数学是否被破坏?
$ awk 'BEGIN{OFMT="%.17f"; print 1.1e2; print 1.1*10**2}'
110
110.00000000000001421
然后,解决方案是更改浮点数的格式,而不是将其转换为计算。正如其他帖子中提到的,printf
是这里的解决方案,但是必须小心小数字和大数字,如示例所示(基于此):
v=3.2e-3
printf -- "%.12f" "$v"
0.003200000000
$ v=3.2e-13
$ printf -- "%.12f" "$v"
0.000000000000
因此,通过将精度定义为参数来将指数的信息传输到printf
会很好。以下转换执行此操作
$ printf -- "%.*f" $((17-${v#*[eE]})) "$v"
这考虑到您需要 17 位精度才能准确表示双精度浮点数,并且它利用了printf
将负精度转换为默认精度的事实。 以下是一些示例:
for v in 1.2345678901234567e{-2,+2,-10,+10,-20,+20}; do
printf -- "%.*fn" $((17-${v#*[eE]})) "${v}"
done
0.0123456789012345670
123.456789012345670
0.000000000123456789012345670
12345678901.2345670
0.0000000000000000000123456789012345670
123456789012345670000.000000
这是我的小perlCalc
bash函数:
perlCalc ()
{
set -- ${@/^/**}
set -- ${@/[/(}
set -- ${@/]/)}
perl -le "print $*"
}
例:
$ perlCalc 1+sin[3.14159] + log[1.5] - atan2[1,2] - 1e5 + 3e-10
-99999.058179847
它返回与 python3 相同的结果:
$ python3 -c "from math import *;print(1+sin(3.14159) + log(1.5) - atan2(1,2) - 1e5 + 3e-10)"
-99999.058179847