【问题标题】:How can I compare two floating point numbers in Bash?如何在 Bash 中比较两个浮点数?
【发布时间】:2012-01-29 01:21:08
【问题描述】:

我正在努力比较 Bash 脚本中的两个浮点数。我有两个变量,例如

let num1=3.17648e-22
let num2=1.5

现在,我只想对这两个数字做一个简单的比较:

st=`echo "$num1 < $num2" | bc`
if [ $st -eq 1]; then
  echo -e "$num1 < $num2"
else
  echo -e "$num1 >= $num2"
fi

不幸的是,我在正确处理可能是“电子格式”的 num1 时遇到了一些问题。

【问题讨论】:

标签: bash floating-point comparison numbers


【解决方案1】:

更方便

这可以使用 Bash 的数字上下文更方便地完成:

if (( $(echo "$num1 > $num2" |bc -l) )); then
  …
fi

说明

通过基本的计算器命令 bc 进行管道化返回 1 或 0。

选项-l等价于--mathlib;它加载标准数学库。

将整个表达式括在双括号 (( )) 之间会将这些值分别转换为真或假。

请确保已安装bc 基本计算器包。

警告:指数符号应写为*10^;不是E,也不是e

例如:

$ echo "1*10^3==1000" |bc
1

$ echo "1E3==1000" |bc
0

克服bc 限制的策略是discussed here

【讨论】:

  • stackoverflow.com/questions/8654051/… 相同的问题,例如$ echo "1.1+2e+02" | bc (standard_in) 1: 语法错误
  • @MohitArora 请确保您已安装 bc 计算器包。
  • 我得到一个 0: not found 声明 if (( $(echo "$TOP_PROCESS_PERCENTAGE &gt; $THRESHOLD" | bc -l) )); then
  • 对于所有得到“找不到命令”的人,请记住,您需要将bc 括在反引号或$() 中,然后括在(( )) ... 即(( $(bc -l&lt;&lt;&lt;"$a&gt;$b") )) 中,而不是(( bc -l&lt;&lt;&lt;"$a&gt;$b" )).
  • 3.44E6 根据bc -l3.4546 相同。如果你想要3.44*10^6 而不是使用字符串3.44*10^63440000
【解决方案2】:

Bash 只处理整数数学,但您可以使用bc 命令,如下所示:

$ num1=3.17648E-22
$ num2=1.5
$ echo $num1'>'$num2 | bc -l
0
$ echo $num2'>'$num1 | bc -l
1

注意指数符号必须大写。

【讨论】:

  • 是的,但要解决不正确的计算,需要在科学数字符号中使用大写“e”符号并使用 -l 标志来 bc 编程以进行预定义的数学例程
  • 您应该在回答中指出这一点,而不是仅仅发布一个非常相似的解决方案而不提及重要的差异。
  • 这不是一个非常相似的解决方案。 Alrusdi 的解决方案使用bc 工具,这是我向任何 BASH 程序员推荐的。 BASH 是无类型语言。是的,它可以进行整数运算,但是对于浮点,您必须使用一些外部工具。 BC 是最好的,因为它就是为此而生的。
  • 由于他试图在 if 语句中使用它,我会证明这一点。如果 [ $(... | bc -l) == 1 ] ;然后...
【解决方案3】:

对于非整数数学,最好使用AWK。你可以使用这个 Bash 实用函数:

numCompare() {
   awk -v n1="$1" -v n2="$2" 'BEGIN {printf "%s " (n1<n2?"<":">=") " %s\n", n1, n2}'
}

并将其称为:

numCompare 5.65 3.14e-22
5.65 >= 3.14e-22

numCompare 5.65e-23 3.14e-22
5.65e-23 < 3.14e-22

numCompare 3.145678 3.145679
3.145678 < 3.145679

【讨论】:

  • 我喜欢这个答案,人们倾向于回避 awk esp 初学者,他们似乎认为这比实际更难,我认为人们会被花括号和看似语言混合的语法吓倒(乍看上去)。而且由于 awk 几乎可以保证在目标系统上也存在,就像 bc 一样(不确定哪个,如果有的话,永远没有安装)。我喜欢 bash 脚本,但是没有浮点,甚至没有小数点后 2 位(我想有人可以为此编写一个“假”包装器),真的很烦人......
  • 在 shell 脚本中使用 awkbc 是自古以来的标准做法,我想说一些功能从未添加到 shell 中,因为它们在 awk、bc 和其他 Unix 工具中可用.不需要在 shell 脚本中保持纯洁性。
  • @WanderingMind 一种方法是将 0 或 1 传递给 exit,以便 Awk 以适当的、机器可读的方式将结果传递回 shell。 if awk -v n1="123.456" -v n2="3.14159e17" 'BEGIN { exit (n1 &lt;= n2) }' /dev/null; then echo bigger; else echo not; fi ... 虽然注意条件是如何反转的(退出状态 0 表示 shell 成功)。
  • 考虑这个答案的人,帮自己一个忙,不要打扰awk。如果您认为 awk 是答案,只需使用内联 python 即可。你的同事和未来的自己会感谢你的。
  • 为什么只是python。您在许多 Linux/Unix 系统上默认安装了 perl.. 甚至 php
【解决方案4】:

一个纯 Bash 解决方案,用于比较没有指数符号、前导零或尾随零的浮点数:

if [ ${FOO%.*} -eq ${BAR%.*} ] && [ ${FOO#*.} \> ${BAR#*.} ] || [ ${FOO%.*} -gt ${BAR%.*} ]; then
  echo "${FOO} > ${BAR}";
else
  echo "${FOO} <= ${BAR}";
fi

逻辑运算符matters的顺序。整数部分作为数字进行比较,而小数部分有意作为字符串进行比较。使用this method 将变量分为整数部分和小数部分。

它不会将浮点数与整数(不带点)进行比较。

【讨论】:

    【解决方案5】:

    您可以将 AWK 与 Bash if 条件结合使用:

    if awk "BEGIN {exit !($d1 >= $d2)}"; then
        echo "yes"
    else
        echo "no"
    fi
    

    【讨论】:

    • 使用 awk 非常棒,因为它能够处理浮点数,但我个人更喜欢 synthax if (( $(echo $d1 $d2 | awk '{if ($1 &gt; $2) print 1;}') )); then echo "yes"; else echo "no"; fi
    • 这不起作用。无论如何,awk 都会以 0 状态退出。将print 替换为exit 即可。
    • @Otheus 您需要将其替换为exit !,否则它会返回相反的结果。我适当地编辑了答案。
    【解决方案6】:

    比较包版本的数字时要小心,例如检查grep 2.20 是否大于版本 2.6:

    $ awk 'BEGIN { print (2.20 >= 2.6) ? "YES" : "NO" }'
    NO
    
    $ awk 'BEGIN { print (2.2 >= 2.6) ? "YES" : "NO" }'
    NO
    
    $ awk 'BEGIN { print (2.60 == 2.6) ? "YES" : "NO" }'
    YES
    

    我用这样一个 shell/AWK 函数解决了这样的问题:

    # Get version of GNU tool
    toolversion() {
        local prog="$1" operator="$2" value="$3" version
    
        version=$($prog --version | awk '{print $NF; exit}')
    
        awk -vv1="$version" -vv2="$value" 'BEGIN {
            split(v1, a, /\./); split(v2, b, /\./);
            if (a[1] == b[1]) {
                exit (a[2] '$operator' b[2]) ? 0 : 1
            }
            else {
                exit (a[1] '$operator' b[1]) ? 0 : 1
            }
        }'
    }
    
    if toolversion grep '>=' 2.6; then
       # Do something awesome
    fi
    

    【讨论】:

    • 在基于 Debian 的系统上,dpkg --compare-versions 通常很有用。它具有比较内置的 Debian 软件包版本的完整逻辑,这比 x.y 更复杂。
    • @NeilMayhew 正是我所需要的。
    【解决方案7】:

    当然,如果您不需要真正的浮点运算,只需在例如美元值总是正好有两位小数,你可以去掉点(实际上乘以 100)并比较得到的整数。

    if [[ $((10#${num1/.})) < $((10#${num2/.})) ]]; then
        ...
    

    这显然要求您确保两个值的小数位数相同。

    【讨论】:

      【解决方案8】:

      请检查下面的编辑代码:

      #!/bin/bash
      
      export num1=(3.17648*e-22)
      export num2=1.5
      
      st=$((`echo "$num1 < $num2"| bc`))
      if [ $st -eq 1 ]
        then
          echo -e "$num1 < $num2"
        else
          echo -e "$num1 >= $num2"
      fi
      

      这很好用。

      【讨论】:

      • 重新"编辑后的代码":可以加个对原文的引用吗?是为了rmil's answer吗? (但没有“编辑:”、“更新:”或类似的 - 答案应该看起来好像是今天写的。)
      • 问题中有一段代码如上编辑
      【解决方案9】:

      支持所有可能符号的解决方案,包括具有大写和小写指数的科学记数法(例如,12.00e4):

      if (( $(bc -l <<< "${value1/e/E} < ${value2/e/E}") ))
      then
          echo "$value1 is smaller than $value2"
      fi 
      

      【讨论】:

        【解决方案10】:

        我使用了这里的答案并将它们放入一个函数中。你可以这样使用它:

        is_first_floating_number_bigger 1.5 1.2
        result="${__FUNCTION_RETURN}"
        

        一旦调用,echo $result 在这种情况下将是 1,否则 0

        功能:

        is_first_floating_number_bigger () {
            number1="$1"
            number2="$2"
        
            [ ${number1%.*} -eq ${number2%.*} ] && [ ${number1#*.} \> ${number2#*.} ] || [ ${number1%.*} -gt ${number2%.*} ];
            result=$?
            if [ "$result" -eq 0 ]; then result=1; else result=0; fi
        
            __FUNCTION_RETURN="${result}"
        }
        

        或带有调试输出的版本:

        is_first_floating_number_bigger () {
            number1="$1"
            number2="$2"
        
            echo "... is_first_floating_number_bigger: comparing ${number1} with ${number2} (to check if the first one is bigger)"
        
            [ ${number1%.*} -eq ${number2%.*} ] && [ ${number1#*.} \> ${number2#*.} ] || [ ${number1%.*} -gt ${number2%.*} ];
            result=$?
            if [ "$result" -eq 0 ]; then result=1; else result=0; fi
        
            echo "... is_first_floating_number_bigger: result is: ${result}"
        
            if [ "$result" -eq 0 ]; then
                echo "... is_first_floating_number_bigger: ${number1} is not bigger than ${number2}"
            else
                echo "... is_first_floating_number_bigger: ${number1} is bigger than ${number2}"
            fi
        
            __FUNCTION_RETURN="${result}"
        }
        

        只需将函数保存在单独的.sh 文件中,然后像这样包含它:

        . /path/to/the/new-file.sh
        

        【讨论】:

          【解决方案11】:

          AWK 和类似的工具(我盯着你sed...)应该被扔进旧项目的垃圾箱,因为它是在阅读中编写的,所以每个人都不敢碰- 从不语言。

          或者你是一个相对罕见的项目,需要优先考虑 CPU 使用优化而不是代码维护优化......在这种情况下,继续。

          不过,如果不是,则只需使用可读且明确的东西,例如 Python。你的程序员同事和未来的自己会感谢你的。您可以像使用其他所有代码一样将 Python 代码与 Bash 内联。

          num1=3.17648E-22
          num2=1.5
          if python -c "exit(0 if $num1 < $num2 else 1)"; then
              echo "yes, $num1 < $num2"
          else
              echo "no, $num1 >= $num2"
          fi
          

          【讨论】:

          • @Witiko 我的原始版本有点刻薄。
          • 更简洁:使用not(...) 而不是0 if ... else 1
          • 如果您将 awk 和 sed(我在看您的 CivFan)放到历史的垃圾箱中,那么您就是一个糟糕的系统管理员,并且您输入了太多代码。 (而且我喜欢并使用 Python,所以这与此无关)。 -1 表示错位的恶作剧。这些工具在系统域中占有一席之地,无论是否使用 Python。
          • 有趣的是,我最终得到了优秀的 Perl! awk '${print $5}' ptpd_log_file | perl -ne '$_ &gt; 0.000100 &amp;&amp; print' &gt; /tmp/outfile。十分简单。每种语言都有它的位置。
          • 不要将 awk 与 seds 语法古怪混为一谈。与 python 不同,awk 是每个 UNIX 安装的必备实用程序,而 python -c "import sys; sys.exit(0 if float($num1) &lt; float($num2) else 1)" 的 awk 等效项就是 awk "BEGIN{exit ($num1 &gt; $num2 ? 0 : 1)}"
          【解决方案12】:

          为了简单明了,只需使用 AWK 进行计算,因为它是一个标准的 Unix 工具,因此可能与 bc 一样出现,并且在语法上更易于使用。

          对于这个问题:

          $ cat tst.sh
          #!/bin/bash
          
          num1=3.17648e-22
          num2=1.5
          
          awk -v num1="$num1" -v num2="$num2" '
          BEGIN {
              print "num1", (num1 < num2 ? "<" : ">="), "num2"
          }
          '
          
          $ ./tst.sh
          num1 < num2
          

          对于作为此问题的副本而关闭的其他问题:

          $ cat tst.sh
          #!/bin/bash
          
          read -p "Operator: " operator
          read -p "First number: " ch1
          read -p "Second number: " ch2
          
          awk -v ch1="$ch1" -v ch2="$ch2" -v op="$operator" '
          BEGIN {
              if ( ( op == "/" ) && ( ch2 == 0 ) ) {
                  print "Nope..."
              }
              else {
                  print ch1 '"$operator"' ch2
              }
          }
          '
          
          $ ./tst.sh
          Operator: /
          First number: 4.5
          Second number: 2
          2.25
          
          $ ./tst.sh
          Operator: /
          First number: 4.5
          Second number: 0
          Nope...
          

          当它作为这个问题的副本被关闭时,我将它作为对 '4.5: syntax error: invalid arithmetic operator (error token is ".5")' - but the code still seems to work. Why? 的答案发布,所以这里也适用于此。

          【讨论】:

            【解决方案13】:
            num1=0.555
            num2=2.555
            
            
            if [ `echo "$num1>$num2"|bc` -eq 1 ]; then
                   echo "$num1 is greater then $num2"
            else
                   echo "$num2 is greater then $num1"
            fi
            

            【讨论】:

            • 解释一下。例如,它与以前的答案有何不同,想法/要点是什么?请通过editing (changing) your answer 回复,而不是在 cmets 中(without "Edit:"、"Update:" 或类似的 - 答案应该看起来像是今天写的)。
            【解决方案14】:

            这个脚本可能有助于我检查安装的Grails 版本是否高于最低要求。

            #!/bin/bash
            
            min=1.4
            current=`echo $(grails --version | head -n 2 | awk '{print $NF}' | cut -c 1-3)`
            
            if [ 1 -eq `echo "${current} < ${min}" | bc` ]
            then
                echo "Yo, you have an older version of Grails."
            else
                echo "Hurray, you have the latest version"
            fi
            

            【讨论】:

              【解决方案15】:

              使用KornShell。在 Bash 中,您可能需要单独比较小数部分:

              #!/bin/ksh
              X=0.2
              Y=0.2
              echo $X
              echo $Y
              
              if [[ $X -lt $Y ]]
              then
                   echo "X is less than Y"
              elif [[ $X -gt $Y ]]
              then
                   echo "X is greater than Y"
              elif [[ $X -eq $Y ]]
              then
                   echo "X is equal to Y"
              fi
              

              【讨论】:

              • 问题是许多发行版都没有安装 ksh,如果你的脚本要被其他人使用,他们往往不喜欢安装额外的东西,特别是当它只是一个脚本时那应该是用 bash 编写的——有人会认为他们不需要另一个 shell 来做这件事,这破坏了首先使用 bash 脚本的全部原因——当然我们也可以用 C++ 编写它,但是为什么呢?
              • 没有安装 ksh 的发行版有哪些?
              • @piokuc 例如,Ubuntu 桌面和服务器。我会说这是一个非常重要的......
              • 另外,问题特别要求在 bash 中工作的解决方案。这可能有很好的理由。比如说,它是大型应用程序的一部分,将所有内容迁移到 ksh 是不可行的。或者它运行在嵌入式平台上,安装另一个 shell 确实是个问题。
              【解决方案16】:

              使用这个:

              VAL_TO_CHECK="1.00001"
              if [ $(awk '{printf($1 >= $2) ? 1 : 0}' <<<" $VAL_TO_CHECK 1 ") -eq 1 ] ; then
                  echo "$VAL_TO_CHECK >= 1"
              else
                  echo "$VAL_TO_CHECK < 1"
              fi
              

              【讨论】:

              • Awk 脚本应该简单地用exit 0 来报告真相,exit 1 来返回false;那么您可以简化为非常优雅的if awk 'BEGIN { exit (ARGV[1] &gt;= ARGV[2]) ? 0 : 1 }' "$VAL_TO_CHECK" 1; then...(如果您将 Awk 脚本封装在 shell 函数中,则更加优雅)。
              【解决方案17】:

              使用 bashj,一个支持 Java 的 Bash 变种,您只需编写(而且它易于阅读):

              #!/usr/bin/bashj
              
              #!java
              static int doubleCompare(double a,double b) {return((a>b) ? 1 : (a<b) ? -1 : 0);}
              
              #!bashj
              num1=3.17648e-22
              num2=1.5
              comp=j.doubleCompare($num1,$num2)
              if [ $comp == 0 ] ; then echo "Equal" ; fi
              if [ $comp == 1 ] ; then echo "$num1 > $num2" ; fi
              if [ $comp == -1 ] ; then echo "$num2 > $num1" ; fi
              

              当然,bashj Bash/Java 混合提供了更多...

              【讨论】:

                【解决方案18】:

                有一种简单的方法,它比 AWK 快一点,并且不需要安装 bc。它利用sort 对浮点数进行排序的能力:

                A=1280.4
                B=9.325
                LOW=$(sort -n <<< "$A"$'\n'"$B" | head -1)
                if [[ "$LOW" == "$A" ]]; then
                    echo "A <= B"
                else
                    echo "A >= B"
                fi
                

                当然,它不适用于相等的数字

                【讨论】:

                  【解决方案19】:

                  只需将echo 替换为printf(它可以理解浮点数):

                  st=$(  printf '%50G < %50G\n' "$num1" "$num2" | bc -l  )
                  

                  【讨论】:

                  • 什么是 printf?内置的?外部命令?
                  【解决方案20】:

                  单线解决方案

                  假设你有两个变量AB

                  echo "($A > $B) * $B + ($A < $B) * $A" | bc
                  

                  【讨论】:

                    猜你喜欢
                    • 2018-08-15
                    • 2011-08-24
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-02-15
                    • 1970-01-01
                    • 1970-01-01
                    • 2020-02-23
                    相关资源
                    最近更新 更多