48

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

$ echo "3.1e1*2" | bc -l
(standard_in) 1: parse error

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

4

9 回答 9

37

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

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

sed -E 's/([+-]?[0-9.]+)[eE]\+?(-?)([0-9]+)/(\1*10^\2\3)/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^\2\3)/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
于 2012-10-14T13:19:01.317 回答
22

让我试着总结一下现有的答案,并在下面对每个答案进行评论

  • (a)如果您确实需要使用bc任意精度计算- 就像 OP 一样 - 使用OP 自己的聪明方法,它在文本上将科学记数法重新格式化为可以理解的等效bc表达式

  • 如果潜在的精度损失不是问题

    • (b) 考虑使用awkperl作为bc替代方案正如jwpat7对 awk 的回答所示,两者都天生就理解科学记数法。
    • (c) 考虑使用printf '%.<precision>f'简单地以文本方式转换为常规浮点表示(小数,不带e/ Eormaaj在已删除的帖子中提出的解决方案)。

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

该解决方案的优点是保留了精度:将文本表示转换为可以理解的等效文本表示,并且本身能够进行任意精度的计算。bcbc

请参阅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

如果使用默认print函数,该OFMT变量通过printf格式字符串的方式控制输出格式;(POSIX 强制)默认值为%.6g,表示 6 个有效数字,其中特别包括整数部分中的数字

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

根据您的语言环境和awk您使用的实现,您可能必须将小数点 ( ) 替换适合.语言环境的基数字符,例如,在德语语言环境中;适用于 BSD awk, mawk, 和 GNUawk--posix选项。

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

perl太自然地理解十进制指数(科学)表示法。

注意:Perl 与 awk 不同,默认情况下并非在所有类似 POSIX 的平台上都可用;此外,它不如 awk 轻量级
但是,它提供了比 awk 更多的功能,例如本机理解十六进制和八进制整数

perl -le 'print 3.1e1 * 2'  # -> 62

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

perl -e 'printf "%.4f\n", 3.1e1 * 2.1234' # -> 65.8254

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

如果您只是想将科学记数法(例如1.2e-2)转换为小数部分(例如0.012),printf '%f'可以为您完成。请注意,您将通过浮点算术将一种文本表示形式转换为一种表示形式,这与方法存在相同的舍入误差awkperl

printf '%.4f' '1.2e-2' # -> '0.0120'; `.4` specifies 4 decimal digits.
于 2015-03-04T03:10:19.867 回答
12

为此可以使用 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,最好通过 regex删除表单45e+3中的加号,而不是单独的表达式。例如,在我的 GNU sed 版本 4.2.1 和 bash 版本 4.2.24 的 linux 机器上,命令 会产生输出e[eE]+*sed
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

于 2012-10-14T16:15:08.323 回答
7

您还可以定义一个调用 awk 的 bash 函数(一个好的名称应该是等号“=”):

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

然后你可以在 shell 中使用所有类型的浮点数学。请注意,这里使用方括号而不是圆括号,因为后者必须通过引号保护免受 bash 的影响。

> = 1+sin[3.14159] + log[1.5] - atan2[1,2] - 1e5 + 3e-10
0.94182

或在脚本中分配结果

a=$(= 1+sin[4])
echo $a   # 0.243198
于 2013-10-08T15:25:09.273 回答
3

幸运的是,有 printf 来完成格式化工作:

上面的例子:

printf "%.12f * 2\n" 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
于 2016-03-31T13:30:22.153 回答
1

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
于 2018-04-13T08:57:39.220 回答
1

我设法用一点技巧做到了。你可以做这样的事情 -

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
于 2018-10-20T16:14:24.340 回答
0

试试这个(在使用 m4 处理的 CFD 输入数据的示例中找到了这个:)

T0=4e-5
deltaT=2e-6
m4 <<< "esyscmd(perl -e 'printf (${T0} + ${deltaT})')"
于 2013-11-15T12:44:39.500 回答
0

试试这个:(使用 bash)

printf "scale=20\n0.17879D-13\n" | sed -e 's/D/*10^/' | bc

或这个:

 num="0.17879D-13"; convert="`printf \"scale=20\n$num\n\" | sed -e 's/D/*10^/' | bc`" ; echo $convert
.00000000000001787900
num="1230.17879"; convert="`printf \"scale=20\n$num\n\" | sed -e 's/D/*10^/' | bc`" ; echo $convert
1230.17879

如果你有正指数,你应该使用这个:

num="0.17879D+13"; convert="`printf \"scale=20\n$num\n\" | sed -e 's/D+/*10^/' -e 's/D/*10^/' | bc`" ; echo $convert
1787900000000.00000

最后一个将处理扔给它的每个数字。如果您有以“e”或“E”为指数的数字,则可以调整“sed”。

你可以选择你想要的规模。

于 2014-10-23T18:19:18.123 回答