【问题标题】:How do I use floating-point arithmetic in bash?如何在 bash 中使用浮点运算?
【发布时间】:2012-09-25 04:24:52
【问题描述】:

我试图在 Bash 脚本中划分两个图像宽度,但 bash 给了我0 作为结果:

RESULT=$(($IMG_WIDTH/$IMG2_WIDTH))

我确实研究了 Bash 指南,我知道我应该使用 bc,在互联网上的所有示例中,他们使用 bc。在echo 中,我尝试将相同的内容放入我的SCALE 中,但没有成功。

这是我在教程中找到的示例:

echo "scale=2; ${userinput}" | bc 

我怎样才能让 Bash 给我一个像 0.5 这样的浮点数?

【问题讨论】:

标签: bash floating-point arithmetic-expressions


【解决方案1】:

股息=除数×商+余数

让我们只计算商和余数。 以及将这些字符串连接到一个变量中。

bar=1234 \
&& divisor=1000 \
    && foo=$(printf "%s.%s" $(( bar / divisor )) $(( bar % divisor ))) \
    && printf "bar is %d miliseconds or %s seconds\n" $bar $foo

输出:bar is 1234 seconds or 1.234 miliseconds

【讨论】:

    【解决方案2】:

    你需要输出有多精确?如果您的用例已经可以接受通过分箱的近似值,您甚至可以更进一步并利用 POSIX 退出代码 [0:256)(所有其他整数都返回到该范围)。

    例如:在 gawk/nawk/mawk-1 中,它已经让我的纪元秒数下降到整数级别,但我想扩展它以提取接近毫秒的精度,但又不过分迂腐,我运行这个命令一个 POSIX 外壳

    exit $(( 10#` gdate +%5N ` * 256 / 100000 ))
    

    直接将代表gnu-date的0.xxxxxx输出的5位整数分配到256个bin中的1个中,然后在awk获得system()调用的退出代码,即选择的bin时撤消256 #。我发现这种方法的开销比使用完整的 getline 调用要低。

    此方法还直接将输出捕获到 POSIX 退出代码中,而不需要额外的终端打印输出。

    (shell 算术 auto floors it to integer 如果这样写而不是 * 0.0256)。放在一起成为一个 awk 函数,它会像这样。 10# 是强制 base-10 以防止 posix shell 将“01733”解释为八进制数。

    function msecs() {     # n x 2**-8 = n divided by 256
    
        return 2^-8 * \
               system( "exit \44\50\50 "      \
                       " 10\43\140 gdate \53"  \
                       "%5N\140 \52 " \
                       "256 \57 100000 \51\51" )
    }
    
    • 对于我自己的代码,我应用了另外 0.6% 的理发来解决 shell 开销。

    【讨论】:

      【解决方案3】:

      重击

      正如其他人所指出的,bash 不支持浮点运算,尽管您可以使用一些固定的十进制技巧来伪造它,例如两位小数:

      echo $(( 100 * 1 / 3 )) | sed -e 's/..$/.&/;t' -e 's/.$/.0&/'
      

      输出:

      .33
      

      请参阅Nilfred's answer 了解类似但更简洁的方法。

      替代方案

      除了提到的bcawk 替代方案之外,还有以下替代方案:

      剪辑

      clisp -x '(/ 1.0 3)'
      

      清理后的输出:

      clisp --quiet -x '(/ 1.0 3)'
      

      或通过stdin:

      echo '(/ 1.0 3)' | clisp --quiet | tail -n1
      

      直流

      echo 2k 1 3 /p | dc
      

      天才cli计算器

      echo 1/3.0 | genius
      

      鬼脚本

      echo 1 3 div = | gs -dNODISPLAY -dQUIET | sed -n '1s/.*>//p' 
      

      gnuplot

      echo 'pr 1/3.' | gnuplot
      

      Imagemagick

      convert xc: -format '%[fx:1/3]' info:
      

      或通过stdin

      echo 1/3 | { convert xc: -format "%[fx:$(cat)]" info:; }
      

      jq

      jq -n 1/3
      

      或通过stdin

      echo 1/3 | jq -nf /dev/stdin
      

      ksh

      echo 'print $(( 1/3. ))' | ksh
      

      lua

      lua -e 'print(1/3)'
      

      或通过标准输入:

      echo 'print(1/3)' | lua
      

      最大值

      echo '1/3,numer;' | maxima
      

      清理后的输出:

      echo '1/3,numer;' | maxima --quiet | sed -En '2s/[^ ]+ [^ ]+ +//p'
      

      节点

      echo 1/3 | node -p
      

      八度

      echo 1/3 | octave
      

      perl

      echo print 1/3 | perl
      

      python2

      echo print 1/3. | python2
      

      python3

      echo 'print(1/3)' | python3
      

      R

      echo 1/3 | R --no-save
      

      清理后的输出:

      echo 1/3 | R --vanilla --quiet | sed -n '2s/.* //p'
      

      红宝石

      echo puts 1/3.0 | ruby
      

      单位

      units 1/3
      

      紧凑的输出:

      units --com 1/3
      

      wcalc

      echo 1/3 | wcalc
      

      清理后的输出:

      echo 1/3 | wcalc | tr -d ' ' | cut -d= -f2
      

      zsh

      print $(( 1/3. ))
      

      或通过stdin:

      echo 'print $(( 1/3. ))' | zsh
      

      其他来源

      Stéphane Chazelasanswered a similar question 在 UL 上。

      【讨论】:

      • 很好的答案。我知道它是在问题发布几年后发布的,但更值得被接受。
      • 如果你有 zsh 可用,那么值得考虑用 zsh 而不是 Bash 编写你的脚本
      • 不错的清单。尤其是echo 1/3 | node -p 很短。
      • 不错的清单。可悲的是,第一个 bash 方法有一个缺陷。如果结果少于两位数(= 模拟小数位),则 sed 不会进行替换。 1 / 50 的示例:echo $(( 100*1/50)) | sed 's/..$/.&/' 打印 2 而不是 0.02
      • @Socowi:确实,在这种情况下需要第二次替换,请参阅更新版本。
      【解决方案4】:

      我知道它很旧,但太诱人了。所以,答案是:你不能……但你可以。让我们试试这个:

      $IMG_WIDTH=1024
      $IMG2_WIDTH=2048
      
      $RATIO="$(( IMG_WIDTH / $IMG2_WIDTH )).$(( (IMG_WIDTH * 100 / IMG2_WIDTH) % 100 ))"
      

      像这样,您在点后得到 2 位数字,在纯 bash 中被截断(称为四舍五入)(无需启动其他进程)。当然,如果你只需要一个数字,你乘以 10 并取模 10。

      这是做什么的:

      • 首先$((...)) 进行整数除法;
      • 第二个$((...)) 对大 100 倍的东西进行整数除法,基本上将您的 2 位数字移到该点的左侧,然后 (%) 通过取模只得到这 2 位数字。

      奖励曲目bc 版本 × 1000 在我的笔记本电脑上用了 1.8 秒,而纯 bash 用了 0.016 秒。

      【讨论】:

      • 当“第二”部分的结果小于 10 时,您的“解决方案”将不起作用。例如,尝试使用IMG_WIDTH=103IMG2_WIDTH=100
      【解决方案5】:

      正如其他人所指出的,bash 没有内置的浮点运算符。

      您可以在 bash 中实现浮点,即使不使用 bcawk 等计算器程序或任何外部程序.

      我在我的项目shellmath 中正是这样做的,分三个基本步骤:

      1. 将数字分解为整数和小数部分
      2. 使用内置的整数运算符分别处理各个部分,同时注意位值和进位
      3. 重新组合结果

      作为预告,我添加了一个演示脚本,它使用以 x=0 为中心的泰勒级数计算 e

      如果您有时间,请查看。欢迎您的反馈!

      【讨论】:

      • 这听起来像是一个有趣的练习,但它实用吗?更喜欢在 shell 脚本中本地执行数据处理任务而不是使用外部程序的通常目标是避免启动新进程的开销。但这与 shell 对于许多此类任务的基本缓慢性背道而驰。在什么情况下你的 shell 实现优于bc(例如)?
      • 感谢您的提问。我已经在 shellmath README 中添加了一些关于此的信息。我相信这是实用的,因为我在timingData.txt 中进行了计时实验。首先,我一直小心使用高效的脚本编写实践(项目中提供了详细信息),但我认为真正的关键是您可以运行shellmath 而无需分叉子shell。 shellmath 是一系列将其结果写入 shell 变量的函数。 OTOH,由于bc 是一个可执行文件,您必须对其进行子shell 才能捕获结果。
      【解决方案6】:

      你可以这样做:

      bc <<< 'scale=2; 100/3'
      33.33
      

      更新 20130926:你可以使用:

      bc -l <<< '100/3' # saves a few hits
      33.33333333333333333333
      

      【讨论】:

      • @AndreasSpindler 有点老帖子,但如果有人想知道,可以通过应用 scale 命令来更改,例如。 bc -l &lt;&lt;&lt; 'scale=2; 100/3'
      • 请注意您是否希望通过使用 scale=0 获得一个整数以供以后在 bash 中使用。从 v1.06.95 开始,由于某种原因,当输入数字有小数部分时,bc 会忽略比例变量。也许这是在文档中,但我找不到它。试试: echo $(bc -l
      • @GregBell 手册页上写着Unless specifically mentioned the scale of the result is the maximum scale of the expressions involved. 还有一个额外的注释/ 操作员:The scale of the result is the value of the variable scale.
      • 谢谢@psmith。有趣的是,对于 / 它说“结果的比例是变量比例的值”,但对于乘法却不是这样。我更好的例子:bc &lt;&lt;&lt; 'scale=1; 1*3.00001' 由于某种原因,比例实际上是 5,bc &lt;&lt;&lt; 'scale=1; 1/3.000001' 比例是 1。有趣的是,除以 1 会直接:bc &lt;&lt;&lt; 'scale=1; 1*3.00001/1' 比例是 1
      • 为什么bc 命令需要三个左尖括号&lt;&lt;&lt;?我以前从未见过这种情况。 &lt;&lt;&lt; 是什么意思?还有什么时候用?更新:我在这里发布了这个问题:stackoverflow.com/questions/58478964/…
      【解决方案7】:

      如何在bash中进行浮点计算:

      而不是像most-upvoted examples 那样在bc 命令中使用“here strings”(&lt;&lt;&lt;),这是我最喜欢的bc 浮点示例,就在EXAMPLES 部分bc 手册页(参见 man bc 手册页)。

      在我们开始之前,知道 pi 的方程是:pi = 4*atan(1)a() 下面是bc atan() 的数学函数。

      1. 这是如何将浮点计算的结果存储到 bash 变量中 - 在本例中存储到名为 pi的变量中。请注意,scale=10 在这种情况下将精度的小数位数设置为 10。此位置之后的任何十进制数字都将被截断

         pi=$(echo "scale=10; 4*a(1)" | bc -l)
        
      2. 现在,为了有一行代码也打印出这个变量的值,只需将echo 命令添加到末尾作为后续命令,如下所示。请注意 截断 在小数点后 10 位,按照命令:

         pi=$(echo "scale=10; 4*a(1)" | bc -l); echo $pi
         3.1415926532
        
      3. 最后,让我们进行一些舍入。在这里,我们将使用printf 函数四舍五入到小数点后四位。请注意,3.14159... 现在轮到3.1416。由于我们是四舍五入,我们不再需要使用scale=10 来截断到小数点后 10 位,所以我们将删除该部分。这是最终的解决方案:

         pi=$(printf %.4f $(echo "4*a(1)" | bc -l)); echo $pi
         3.1416
        

      这是上述技术的另一个非常棒的应用和演示:测量和打印运行时。

      (另见my other answer here)。

      请注意,dt_min0.01666666666... 舍入到0.017

      start=$SECONDS; sleep 1; end=$SECONDS; dt_sec=$(( end - start )); dt_min=$(printf %.3f $(echo "$dt_sec/60" | bc -l)); echo "dt_sec = $dt_sec; dt_min = $dt_min"
      dt_sec = 1; dt_min = 0.017
      

      相关:

      【讨论】:

      • 通过详细示例和用例以及使用 printf 进行循环的可靠答案
      【解决方案8】:

      对于那些试图用已接受的答案计算百分比但精度下降的人:

      如果你运行这个:

      echo "scale=2; (100/180) * 180" | bc
      

      你只得到99.00,它正在失去精度。

      如果你这样运行:

      echo "result = (100/180) * 180; scale=2; result / 1" | bc -l
      

      现在你得到99.99

      因为您仅在打印时进行缩放。

      参考here

      【讨论】:

        【解决方案9】:

        ** bash/shell 中的注入安全浮点数学运算 **

        注意:此答案的重点是为在 bash(或其他 shell)中执行数学运算提供注入安全解决方案的想法。当然,可以使用相同的方法,稍作调整以执行高级字符串处理等

        提出的大多数解决方案都是使用外部数据(变量、文件、命令行、环境变量)即时构建小型脚本。外部输入可用于向引擎注入恶意代码,其中很多

        以下是使用各种语言执行基本数学计算的比较,其中结果为浮点数。它计算 A + B * 0.1(作为浮点数)。

        所有解决方案都尝试避免创建极难维护的动态脚本,而是使用静态程序,并将参数传递给指定的变量。他们将安全地处理带有特殊字符的参数 - 减少代码注入的可能性。例外是不提供输入/输出设施的“BC”

        例外是'bc',它不提供任何输入/输出,所有数据都来自标准输入中的程序,所有输出都进入标准输出。所有计算都在沙箱中执行,不允许副作用(打开文件等)。理论上,注射是设计安全的!

        A=5.2
        B=4.3
        
        # Awk: Map variable into awk
        # Exit 0 (or just exit) for success, non-zero for error.
        #
        awk -v A="$A" -v B="$B" 'BEGIN { print A + B * 0.1 ; exit 0}'
        
        # Perl
        perl -e '($A,$B) = @ARGV ; print $A + $B * 0.1' "$A" "$B"
        
        # Python 2
        python -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print a+b*0.1' "$A" "$B"
        
        # Python 3
        python3 -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print(a+b*0.1)' "$A" "$B"
        
        # BC
        bc <<< "scale=1 ; $A + $B * 0.1"
        

        【讨论】:

        • 对于带有任意参数的python3:python3 -c 'import sys ; *a, = map(float, sys.argv[1:]) ; print(a[0] + a[1]*0.1 + a[2])' "$A" "$B" "4200.0" ==> 4205.63
        【解决方案10】:

        如果您找到了您偏好的变体,您也可以将它包装到一个函数中。

        在这里,我将一些 bashism 包装到 div 函数中:

        一个班轮:

        function div { local _d=${3:-2}; local _n=0000000000; _n=${_n:0:$_d}; local _r=$(($1$_n/$2)); _r=${_r:0:-$_d}.${_r: -$_d}; echo $_r;}
        

        或多行:

        function div {
          local _d=${3:-2}
          local _n=0000000000
          _n=${_n:0:$_d}
          local _r=$(($1$_n/$2))
          _r=${_r:0:-$_d}.${_r: -$_d}
          echo $_r
        }
        

        现在你有了函数

        div <dividend> <divisor> [<precision=2>]
        

        并像使用它

        > div 1 2
        .50
        
        > div 273 123 5
        2.21951
        
        > x=$(div 22 7)
        > echo $x
        3.14
        

        更新 我添加了一个小脚本,它为您提供 bash 浮点数的基本操作:

        用法:

        > add 1.2 3.45
        4.65
        > sub 1000 .007
        999.993
        > mul 1.1 7.07
        7.7770
        > div 10 3
        3.
        > div 10 3.000
        3.333
        

        这里是脚本:

        #!/bin/bash
        __op() {
                local z=00000000000000000000000000000000
                local a1=${1%.*}
                local x1=${1//./}
                local n1=$((${#x1}-${#a1}))
                local a2=${2%.*}
                local x2=${2//./}
                local n2=$((${#x2}-${#a2}))
                local n=$n1
                if (($n1 < $n2)); then
                        local n=$n2
                        x1=$x1${z:0:$(($n2-$n1))}
                fi
                if (($n1 > $n2)); then
                        x2=$x2${z:0:$(($n1-$n2))}
                fi
                if [ "$3" == "/" ]; then
                        x1=$x1${z:0:$n}
                fi
                local r=$(($x1"$3"$x2))
                local l=$((${#r}-$n))
                if [ "$3" == "*" ]; then
                        l=$(($l-$n))
                fi
                echo ${r:0:$l}.${r:$l}
        }
        add() { __op $1 $2 + ;}
        sub() { __op $1 $2 - ;}
        mul() { __op $1 $2 "*" ;}
        div() { __op $1 $2 / ;}
        

        【讨论】:

        • local _d=${3:-2} 更简单
        【解决方案11】:

        使用计算。这是我发现的最简单的 示例:

        计算 1+1

         2
        

        计算 1/10

         0.1
        

        【讨论】:

          【解决方案12】:

          这里是 awk 命令:-F = 字段分隔符 == +

          echo "2.1+3.1" |  awk -F "+" '{print ($1+$2)}'
          

          【讨论】:

            【解决方案13】:

            虽然您不能在 Bash 中使用浮点除法,但您可以使用定点除法。您需要做的就是将整数乘以 10 的幂,然后除掉整数部分并使用模运算得到小数部分。根据需要四舍五入。

            #!/bin/bash
            
            n=$1
            d=$2
            
            # because of rounding this should be 10^{i+1}
            # where i is the number of decimal digits wanted
            i=4
            P=$((10**(i+1)))
            Pn=$(($P / 10))
            # here we 'fix' the decimal place, divide and round tward zero
            t=$(($n * $P / $d + ($n < 0 ? -5 : 5)))
            # then we print the number by dividing off the interger part and
            # using the modulo operator (after removing the rounding digit) to get the factional part.
            printf "%d.%0${i}d\n" $(($t / $P)) $(((t < 0 ? -t : t) / 10 % $Pn))
            

            【讨论】:

              【解决方案14】:

              好吧,在浮点数之前是使用固定小数逻辑的时代:

              IMG_WIDTH=100
              IMG2_WIDTH=3
              RESULT=$((${IMG_WIDTH}00/$IMG2_WIDTH))
              echo "${RESULT:0:-2}.${RESULT: -2}"
              33.33
              

              最后一行是 bashim,如果不使用 bash,请尝试以下代码:

              IMG_WIDTH=100
              IMG2_WIDTH=3
              INTEGER=$(($IMG_WIDTH/$IMG2_WIDTH))
              DECIMAL=$(tail -c 3 <<< $((${IMG_WIDTH}00/$IMG2_WIDTH)))
              RESULT=$INTEGER.$DECIMAL
              echo $RESULT
              33.33
              

              代码背后的基本原理是:在除以前乘以 100 得到 2 个小数。

              【讨论】:

                【解决方案15】:

                现在是尝试 zsh 的最佳时机,zsh 是一个(几乎)bash 超集,具有许多其他不错的功能,包括浮点数学。这是您在 zsh 中的示例:

                % IMG_WIDTH=1080
                % IMG2_WIDTH=640
                % result=$((IMG_WIDTH*1.0/IMG2_WIDTH))
                % echo $result
                1.6875
                

                这篇文章可能对你有所帮助:bash - Worth switching to zsh for casual use?

                【讨论】:

                • 我是 zsh 的忠实粉丝,并且在过去的 4 年里一直在使用它,但这里非常强调交互式使用。需要 zsh 的脚本通常不会在多种机器上非常便携,因为它通常不是标准的,可悲的是(公平地说,也许没关系;OP 并没有说明它将如何使用)。
                【解决方案16】:

                您可以通过-l 选项(L 字母)使用 bc

                RESULT=$(echo "$IMG_WIDTH/$IMG2_WIDTH" | bc -l)
                

                【讨论】:

                • 如果我的系统中没有包含-l,bc 不会进行浮点数学运算。
                【解决方案17】:

                在某些情况下您不能使用 bc,因为它可能根本不存在,例如在某些精简版的 busybox 或嵌入式系统中。在任何情况下,限制外部依赖总是一件好事,因此您始终可以在除以(分子)的数字上加零,这与乘以 10 的幂相同(您应该根据您需要的精度),这将使除法输出一个整数。一旦你有了那个整数,就把它当作一个字符串,并定位小数点(从右到左移动它)的次数等于你乘以分子的十次方。这是一种仅使用整数来获得浮点结果的简单方法。

                【讨论】:

                • 即使 Busybox 也有 Awk。也许这里应该有一个更突出的 Awk 答案。
                【解决方案18】:

                改进一点马文的答案:

                RESULT=$(awk "BEGIN {printf \"%.2f\",${IMG_WIDTH}/${IMG2_WIDTH}}")
                

                bc 并不总是作为安装包提供。

                【讨论】:

                • awk 脚本需要一个exit 以防止它从输入流中读取。我还建议使用 awk 的 -v 标志来防止倾斜牙签综合症。所以:RESULT=$(awk -v dividend="${IMG_WIDTH}" -v divisor="${IMG2_WIDTH}" 'BEGIN {printf "%.2f", dividend/divisor; exit(0)}')
                • 一个更尴尬的方法是从输入流中读取参数:RESULT=$(awk '{printf("result= %.2f\n",$1/$2)}' &lt;&lt;&lt;" $IMG_WIDTH $IMG2_WIDTH "
                • bc 是 POSIX 的一部分,通常是预安装的。
                • 这对我在 Windows 7 中使用 git bash 有用...谢谢 :)
                【解决方案19】:

                这不是真正的浮点数,但如果您想要设置多个结果的东西,那么一次调用 bc...

                source /dev/stdin <<<$(bc <<< '
                d='$1'*3.1415926535897932384626433832795*2
                print "d=",d,"\n"
                a='$1'*'$1'*3.1415926535897932384626433832795
                print "a=",a,"\n"
                ')
                
                echo bc radius:$1 area:$a diameter:$d
                

                计算半径为$1的圆的面积和直径

                【讨论】:

                  【解决方案20】:

                  作为 bc 的替代方法,您可以在脚本中使用 awk。

                  例如:

                  echo "$IMG_WIDTH $IMG2_WIDTH" | awk '{printf "%.2f \n", $1/$2}'
                  

                  在上面,“%.2f”告诉 printf 函数返回一个小数点后两位的浮点数。我使用 echo 将变量作为字段通过管道传递,因为 awk 在它们上正常运行。 “ $1 ”和“ $2 ”指的是输入到 awk 的第一个和第二个字段。

                  您可以使用以下方法将结果存储为其他变量:

                  RESULT = `echo ...`
                  

                  【讨论】:

                  • 太棒了!谢谢。这对于不存在 bc 的嵌入式环境很有帮助。你为我节省了一些交叉编译时间。
                  【解决方案21】:

                  你不能。 bash only 处理整数;您必须委托给bc等工具。

                  【讨论】:

                  • 如何委托像 bc 这样的工具将答案放入 RESULT 变量中?
                  • 所以你的意思是像 VAR=$(echo "scale=2; $(($IMG_WIDTH/$IMG2_WIDTH))" | bc) ?
                  • @Shevin VAR=$(echo "scale=2; $IMG_WIDTH/$IMG2_WIDTH" | bc)VAR=$(bc &lt;&lt;&lt;"scale=2;$IMG_WIDTH/$IMG2_WIDTH") 没有 $(( )) (双括号);在执行命令之前由 bash 扩展
                  • 是的,但 awk 通常更有可能已经安装在系统中。
                  • @NahuelFouilleul 你在这里有最好的答案。真的应该是自己的答案,并被接受为答案。这一行非常有用:VAR=$(bc
                  猜你喜欢
                  • 2014-01-10
                  • 2015-12-26
                  • 2016-08-23
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-09-24
                  • 1970-01-01
                  相关资源
                  最近更新 更多