Как сравнить два числа с плавающей точкой в ​​Bash?

Я очень стараюсь сравнить два числа с плавающей точкой в ​​сценарии 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, которая может быть "электронного формата".:(

Любая помощь, советы приветствуются!

22 ответа

Удобнее

Это может быть сделано более удобно, используя числовой контекст Bash:

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

объяснение

Передача через основную команду калькулятора bc возвращает 1 или 0.

Опция -l эквивалентно --mathlib; он загружает стандартную математическую библиотеку.

Заключение всего выражения в двойные скобки (( )) переведет эти значения соответственно в true или false.

Пожалуйста, убедитесь, что bc Базовый пакет калькулятор установлен.

Bash обрабатывает только целые числа, но вы можете использовать bc команда следующим образом:

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

Обратите внимание, что знак экспоненты должен быть в верхнем регистре

Лучше использовать 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 в сочетании с условием bash if, awk выведет 1 или 0, и они будут интерпретированы выражением if со значением true или false.

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

Решение Pure Bash для сравнения поплавков без экспоненциальных обозначений, начальных или конечных нулей:

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

Порядок логических операторов имеет значение. Целочисленные части сравниваются как числа, а дробные части преднамеренно сравниваются как строки. С помощью этого метода переменные делятся на целые и дробные части.

Не будет сравнивать числа с целыми числами (без точки).

Будьте осторожны при сравнении чисел, которые являются версиями пакета, например, проверка, если 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

Решение, поддерживающее научную нотацию с показателями в верхнем и нижнем регистре (например, 12.00e4):

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

Конечно, если вам не нужна действительно арифметика с плавающей точкой, просто арифметика, например, в значениях доллара, где всегда есть ровно две десятичных цифры, вы можете просто отбросить точку (эффективно умножая на 100) и сравнить полученные целые числа.

if [[ ${num1/.} < ${num2/.} ]]; then
    ...

Это, очевидно, требует, чтобы вы были уверены, что оба значения имеют одинаковое количество десятичных знаков.

Пожалуйста , проверьте ниже отредактированный код:-

#!/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

это хорошо работает

awk и инструменты, как это (я смотрю на тебя sed...) следует отправлять на свалку старых проектов с кодом, который все слишком боятся трогать, поскольку он был написан на языке, никогда не читающем.

Или вы - относительно редкий проект, которому нужно расставить приоритеты оптимизации использования процессора над оптимизацией обслуживания кода... в таком случае продолжайте.

Если нет, то почему бы просто не использовать что-то читаемое и явное, такое как python? Ваши коллеги по программированию и будущие я будут вам благодарны Ты можешь использовать python встроенный в Bash, как и все остальные.

num1=3.17648E-22
num2=1.5
if python -c "import sys; sys.exit(0 if float($num1) < float($num2) else 1)"; then
    echo "yes, $num1 < $num2"
else
    echo "no, $num1 >= $num2"
fi
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

Я опубликовал это как ответ на /questions/50141074/45-sintaksicheskaya-oshibka-nevernyij-arifmeticheskij-operator-marker-oshibki-5/50141080#50141080 когда он был закрыт как дубликат этого вопроса, так что здесь это так, как это применимо и здесь:

Для простоты и ясности просто используйте 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...

Я использовал ответы отсюда и поместил их в функцию, вы можете использовать это так:

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

Этот скрипт может помочь, где я проверяю, если установлен 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 older version of grails."                                                   
else                                                                                                                                                                                                                       
    echo "Hurray, you have the latest version" 
fi

Используйте оболочку korn, в 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

Используя bashj ( https://sourceforge.net/projects/bashj/), мутанта bash с поддержкой java, вы просто пишете (и его легко читать):

#!/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 /java предлагает гораздо больше...

Как насчет этого? =D

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

Очень простое perl-решение:

      $ num1=3.2E8
$ num2=2.5E9
$ perl -e "print $num2 > $num1? \"true\" : \"false\", \"\n\";"
true
$ perl -e "print $num2 < $num1? \"true\" : \"false\", \"\n\";"
false

Это доказывает, что Perl действительно понимает нотацию «E» для научного числового представления:

      $ perl -e "print $num1, \"\n\";"
320000000

Если вам нужен статус «если» в вашем сценарии оболочки, используйте команду выхода в Perl:

      $ if perl -e "exit ($num1 > $num2? 0 : 1);"; then echo true; else echo false; fi
false

Обратите внимание, что в сценарии оболочки команда, возвращающая 0, считается успешной и проходит условие «если» (поэтому выполняется условие «если»). Любые другие ненулевые возвращаемые значения означают сбой.

Просто замените echo с printf (понимает числа с плавающей запятой):

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

Однострочное решение

Предположим, у вас есть две переменные A а также B,

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

Есть один простой подход, который немного быстрее, чем 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

Конечно, это не работает для равных чисел.

Вот gawk+GMPподход, основанный на учете более широкого диапазона потенциальных входных данных:

       echo " 5.65e-23 3.14e-22\n
        5.65 3.14e-2203\n
        3.145678 3.145679\n
        3.25353E+9293 325353e9288\n
        3.14159e+200000000001 3.1415899999999999999999E200000000001\n
        100000 100000.0\n
             4096 4096" \
                         \
 | gawk -v PREC=9999999 -nMbe '
  
   NF+=OFS=sprintf(" %s ",
          (+($!_=sprintf("%24s",$!_)<+$NF) \
     ? "<" \
        : (+$NF<+$!_) \
     ? ">" \
        : (int(+$!_)==(__=int(+$NF)))*\
          (__==+$NF)*index($!_,$NF  )  \
     ? "=" \
         : "\342\211\210")' | ecp 
 
                5.65e-23 < 3.14e-22
                    5.65 > 3.14e-2203
                3.145678 < 3.145679
           3.25353E+9293 ≈ 325353e9288
   3.14159e+200000000001 ≈ 3.1415899999999999999999E200000000001
                  100000 ≈ 100000.0
                    4096 = 4096
 

Для более четких случаев он даст вам окончательный ответ

  • меньше, чем <,
  • лучше чем >, или же
  • точно равно =(чисто целочисленные случаи, пока)

Когда это относительно неоднозначно, он выводит Unicodeперсонаж U+2248 ≈ ALMOST EQUAL TOвместо того, чтобы пытаться решить это любой ценой.

Большую часть времени вам не понадобится PREC10-миллионный; что-то типа PREC = 32767достаточно хорош для большинства сценариев, с которыми вы сталкиваетесь на типичной основе.

Другие вопросы по тегам