【问题标题】:How to get bc to handle numbers in scientific (aka exponential) notation?如何让 bc 以科学(又名指数)表示法处理数字?
【发布时间】:2012-10-04 15:48:44
【问题描述】:

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

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

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

【问题讨论】:

    标签: bash numeric floating-accuracy bc


    【解决方案1】:

    很遗憾,bc 不支持科学记数法。

    但是,可以将其翻译成 bc 可以处理的格式,使用 sed 中的extended regex as per 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 pattern 匹配无法正常工作(感谢 @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),至少在某些情况下会失败。我将它们保留在这里只是为了理解 cmets,其中包含比这个答案更好的对所有这些复杂性的解释。

    value=${value/[eE]+*/*10^}  ------> Can not work.
    value=`echo ${value} | sed -e 's/[eE]+*/\\*10\\^/'` ------> Fail in some conditions
    

    【讨论】:

    • 两个连续的 bash 替换将起作用(即 v=${v/e/*10^}; v=${v/^+/^}),前提是结果未用于优先级高于 * 的表达式中。
    • 在指数的上标为负数时,必须在bc中指定scale,否则可能会得到意想不到的0
    【解决方案2】:

    让我尝试总结现有的答案,在下面的每个答案上都有 cmet

    • (a) 如果您确实需要使用 bc 进行任意-精度计算 - 就像 OP 一样 - 使用 OP's own clever approach,它以文本形式将科学记数法重新格式化为bc 能够理解的等效表达式

    • 如果可能会丢失精度不是问题

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

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

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

    参见OP's own answer,其更新后的形式现在能够将包含多个指数表示法的整个表达式转换为等效的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 个有效数字,特别是 包括整数部分中的数字。 p>

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

    根据您的语言环境和您使用的awk 实现,您可能需要将小数 (.) 替换为适合语言环境的基数字符,例如@ 987654363@ 在德国语言环境中;适用于 BSD awkmawk 和带有 --posix 选项的 GNU awk

    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.
    

    【讨论】:

    【解决方案3】:

    为此可以使用 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]+* 删除45e+3 等表单中的加号,同时删除e,而不是单独的@987654329 @ 表达。例如,在我的 GNU sed 版本 4.2.1 和 bash 版本 4.2.24 的 linux 机器上,命令
    sed 's/[eE]+*/*10^/g' &lt;&lt;&lt; '7.11e-2 + 323e+34'
    sed 's/[eE]+*/*10^/g' &lt;&lt;&lt; '7.11e-2 + 323e+34' | bc -l
    产生输出
    7.11*10^-2 + 323*10^34
    3230000000000000000000000000000000000.07110000000000000000

    【讨论】:

    • 嗯,所以 awk 可以正确处理有效数字。这太有趣了。我能看到的唯一缺点是,这样你必须为你的数字设置一个最大精度,如果超过这个精度会使脚本无法正常工作。如果有办法强制 awk 使用任意精度,那将是完美的。我更喜欢你的 sed 命令版本而不是我自己的版本,我忘记了 * 的可能性。
    • @Ferdinando,是的,awk 有你提到的缺点,它的实数通常是双精度数,分辨率为 16 位;例如,awk '{printf"%.40g",+$1}' &lt;&lt;&lt; 12345678901234567891234567890123456e-20 产生 123456789012.345672607421875
    • bc 的绝佳替代品,如果不担心可能丢失精度;请注意,在awk 中将某些内容强制转换为数字的可移植 方法是附加+0,而不是附加+。例如,虽然awk '{ print +$1 }' &lt;&lt;&lt;1e-1mawkgawk 中工作正常(输出0.1),但它在BSD awk 中not(在OS X 上使用;输出未修改的输入)。相比之下,awk '{ print $1+0 }' &lt;&lt;&lt;1e-1 应该适用于 all awk 实现。
    【解决方案4】:

    您还可以定义一个调用 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
    

    【讨论】:

    • 我非常喜欢这个解决方案,只要我没有发现任何陷阱。我必须经常用科学记数法做基本的算术,到目前为止这很有魅力。现在我已经在我的 bash_profile 中定义了你的函数并将它命名为 scmath。使用 = 符号对我来说似乎有点危险
    【解决方案5】:

    幸运的是,有 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
    

    【讨论】:

      【解决方案6】:

      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
      

      【讨论】:

        【解决方案7】:

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

        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
        

        【讨论】:

          【解决方案8】:

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

          T0=4e-5
          deltaT=2e-6
          m4 <<< "esyscmd(perl -e 'printf (${T0} + ${deltaT})')"
          

          【讨论】:

            【解决方案9】:

            试试这个:(使用 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”。

            你可以选择你想要的比例。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-11-27
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多