如何让bc处理科学(又称指数)表示法中的数字?

Fer*_*isi 38 bash numeric floating-accuracy bc

bc 不喜欢用科学记数法表示的数字(又称指数表示法).

$ echo "3.1e1*2" | bc -l
(standard_in) 1: parse error
Run Code Online (Sandbox Code Playgroud)

但是我需要用它来处理用这种表示法表达的一些记录.有没有办法bc了解指数表示法?如果没有,我该怎么做才能将它们翻译成bc可以理解的格式?

Fer*_*isi 32

不幸的是,bc不支持科学记数法.

但是,它可以转换为bc可以处理的格式,使用扩展的正则表达式,如 sed中的POSIX:

sed -E 's/([+-]?[0-9.]+)[eE]\+?(-?)([0-9]+)/(\1*10^\2\3)/g' <<<"$value"
Run Code Online (Sandbox Code Playgroud)

你可以用"*10 ^"替换"e"(或"e +",如果指数是正数),bc会立即理解.即使指数为负数或者数字随后乘以另一个幂,这也可以工作,并且允许跟踪有效数字.

如果你需要坚持基本的正则表达式(BRE),那么应该使用它:

sed 's/\([+-]\{0,1\}[0-9]*\.\{0,1\}[0-9]\{1,\}\)[eE]+\{0,1\}\(-\{0,1\}\)\([0-9]\{1,\}\)/(\1*10^\2\3)/g' <<<"$value"
Run Code Online (Sandbox Code Playgroud)

来自评论:

编辑:

这些年来,答案发生了很大变化.上面的答案是截至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
Run Code Online (Sandbox Code Playgroud)


mkl*_*nt0 18

让我试着总结一下现有的答案,并在下面给出评论:

  • (一)如果你确实需要使用bc任意 -精密的计算 -因为OP不-使用OP自己聪明的做法,其文本上重新格式化科学记数法的等价表达bc理解.

  • 如果可能丢失的精度不是一个问题,

    • (b)考虑使用awkperl作为bc替代品 ; 两者都原则上理解科学记数法,正如jwpat7对awk 回答所证明的那样.
    • (c)考虑使用printf '%.<precision>f'简单的文本转换为常规浮点表示(小数部分,不带e/ E)(由ormaaj在自删除的帖子中提出的解决方案).

(a)将科学记数法重新格式化为等效bc 表达式

这种解决方案的优点是保持精度:文本表示被转换为可以理解的等效文本表示bc,并且bc本身能够进行任意精度计算.

请参阅OP自己的答案,其更新后的表单现在能够将包含指数表示法中多个数字的整个表达式转换为等效bc表达式.


(b)使用awkperl代替bc计算器

注意:以下方法假定使用的为双精度浮点值的内置支持awkperl.正如浮点运算中固有的那样,
"给定任意固定数量的位,大多数带有实数的计算都会产生无法使用那么多位精确表示的数量.因此,浮点计算的结果通常必须舍入到为了适应它的有限表示.这种舍入误差是浮点计算的特征." (http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html)

那说,

AWK

awk本地理解十进制指数(科学)表示法.
(通常应该只使用十进制表示,因为awk实现方式不同,它们是否支持带有其他基数的数字文字.)

awk 'BEGIN { print 3.1e1 * 2 }'  # -> 62
Run Code Online (Sandbox Code Playgroud)

如果使用默认print函数,则OFMT变量通过printf格式字符串控制输出格式; (POSIX-mandated)默认值是%.6g,表示6 位有效数字,尤其包括整数部分中的数字.

请注意,如果科学记数法中的数字作为输入提供(与awk程序的文字部分相对),则必须添加+0以强制它为默认输出格式,如果它本身用于print:

根据您所在位置和awk使用的实施,您可能需要更换小数点(.)与区域相适应的基数字符,如,在德文场所; 适用于BSD awk,mawk以及awk带有--posix选项的GNU .

awk '{ print $1+0 }' <<<'3.1e1' # -> 31; without `+0`, output would be the same as input
Run Code Online (Sandbox Code Playgroud)

修改变量会OFMT更改默认输出格式(对于带小数部分的数字;(有效)整数总是这样输出).
或者,使用具有显式输出格式的printf函数:

awk 'BEGIN { printf "%.4f", 3.1e1 * 2.1234 }' # -> 65.8254
Run Code Online (Sandbox Code Playgroud)

Perl的

perl 太原生地理解十进制指数(科学)表示法.

注意:Perl与awk不同,默认情况下并不适用于所有类似POSIX的平台 ; 而且,它不像awk那么轻巧.
但是,它提供了比awk更多的功能,例如本机地理解十六进制和八进制整数.

perl -le 'print 3.1e1 * 2'  # -> 62
Run Code Online (Sandbox Code Playgroud)

我不清楚Perl的默认输出格式是什么,但似乎是%.15g.与awk一样,您可以使用printf选择所需的输出格式:

perl -e 'printf "%.4f\n", 3.1e1 * 2.1234' # -> 65.8254
Run Code Online (Sandbox Code Playgroud)

(c)printf用于将科学记数法转换为小数分数

如果您只是想将科学记数法(例如1.2e-2)转换为小数(例如0.012),printf '%f'可以为您做到这一点.请注意,您将通过浮点运算将一个文本表示转换为另一个文本表示,这与算法方法具有相同的舍入误差awkperl.

printf '%.4f' '1.2e-2' # -> '0.0120'; `.4` specifies 4 decimal digits.
Run Code Online (Sandbox Code Playgroud)


Jam*_*at7 12

人们可以使用awk; 例如,

awk '{ print +$1, +$2, +$3 }' <<< '12345678e-6 0.0314159e2 54321e+13'
Run Code Online (Sandbox Code Playgroud)

生成(通过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
Run Code Online (Sandbox Code Playgroud)

此外,使用关于解决方案sed,它可能是最好删除的加号,像的形式45e+3在同一时间为e,通过正则表达式[eE]+*,而不是在一个单独的sed表达.例如,在我的带有GNU sed版本4.2.1和bash版本4.2.24的linux机器上,命令
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


小智 7

你也可以定义一个调用awk的bash函数(一个好名字就是等号"="):

= ()
{
    local in="$(echo "$@" | sed -e 's/\[/(/g' -e 's/\]/)/g')";
    awk 'BEGIN {print '"$in"'}' < /dev/null
}
Run Code Online (Sandbox Code Playgroud)

然后你可以在shell中使用所有类型的浮点数学.请注意,此处使用方括号而不是圆括号,因为后者必须通过引号来保护bash.

> = 1+sin[3.14159] + log[1.5] - atan2[1,2] - 1e5 + 3e-10
0.94182
Run Code Online (Sandbox Code Playgroud)

或者在脚本中分配结果

a=$(= 1+sin[4])
echo $a   # 0.243198
Run Code Online (Sandbox Code Playgroud)