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


156

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

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


2
Под «электронным форматом» я имею в виду экспоненциальную нотацию (также называемую научной нотацией)
Джонас

Ответы:


181

Удобнее

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

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

объяснение

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

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

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

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

Это одинаково работает для поплавков в научном формате, при условии, что используется заглавная буква E, напримерnum1=3.44E6


1
Та же проблема, что и для stackoverflow.com/questions/8654051/… например, $ echo "1.1 + 2e + 02" | bc (standard_in) 1: синтаксическая ошибка
Nemo

1
@MohitArora Пожалуйста, убедитесь, что у вас установлен bcкалькулятор.
Серж

1
Я получаю 0: not foundс заявлением if (( $(echo "$TOP_PROCESS_PERCENTAGE > $THRESHOLD" | bc -l) )); then.
Стефан

1
Всем тем, кто получает «команда не найдена», помните, что вам нужно заключить bcв кавычки или $()затем в (( ))... то есть (( $(bc -l<<<"$a>$b") ))и нет (( bc -l<<<"$a>$b" )).
Нормализовать

@Nemo Пишите числа в научной записи заглавными буквами E, и все синтаксические ошибки исчезнут.
Серж

100

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

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

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


3
да, но для обхода неправильных вычислений необходимо
ввести заглавную букву

2
Вы должны указать это в своем ответе, вместо того, чтобы просто публиковать очень похожее решение, и не упоминать о существенных различиях.
Даниэль Перссон

4
Это не очень похожее решение. Решение Alrusdi использует этот bcинструмент, и это то, что я бы порекомендовал любому программисту BASH. BASH - это язык без шрифта. Да, он может выполнять целочисленную арифметику, но для плавающей запятой вы должны использовать какой-то внешний инструмент. BC - лучший, потому что для этого он и создан.
DejanLekic

8
Поскольку он пытается использовать его в выражении if, я бы это показал. if [$ (... | bc -l) == 1]; тогда ...
Роберт Джейкобс

27

Лучше использовать 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

2
Мне нравится этот ответ, люди, как правило, уклоняются от новичков в awk esp, они, кажется, думают, что это сложнее, чем на самом деле, я думаю, что люди запуганы фигурными скобками и, казалось бы, смешанным синтаксисом языка (с первого взгляда). А поскольку awk в значительной степени гарантированно присутствует и в целевой системе, точно так же, как bc (не уверен, какой из них, если он вообще есть, НЕ установлен). Я люблю скрипты bash, но отсутствие плавающей запятой, даже скудных 2 десятичных знака (я думаю, кто-то может написать «поддельную» обертку для этого), действительно раздражает ...
osirisgothra

2
Использование awkи bcв сценариях оболочки является стандартной практикой с древних времен, я бы сказал, что некоторые функции никогда не добавлялись в оболочки, потому что они доступны в awk, bc и других инструментах Unix. Нет необходимости в чистоте в сценариях оболочки.
piokuc

1
@WanderingMind Один из способов сделать это - передать 0 или 1, чтобы exitAwk передавал результат обратно в оболочку правильным, машиночитаемым способом. if awk -v n1="123.456" -v n2="3.14159e17" 'BEGIN { exit (n1 <= n2) }' /dev/null; then echo bigger; else echo not; fi... хотя обратите внимание, как условие инвертируется (состояние выхода 0 означает успех для оболочки).
tripleee

1
Почему просто python. Вы perlустановили по умолчанию во многих системах Linux / Unix .. даже phpтакже
anubhava

1
Это awkрешение является более надежным в моем случае, чем решение, bcкоторое возвращает неправильные результаты по причине, которую я не получил.
MBR

22

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

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

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

Не будет сравнивать числа с плавающей точкой (без точки).


15

Вы можете использовать awk в сочетании с условием bash if, awk выведет 1 или 0, и они будут интерпретированы выражением if со значением true или false .

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

Использование awk великолепно, так как он способен обрабатывать числа с плавающей запятой, но я лично предпочитаю синтаксif (( $(echo $d1 $d2 | awk '{if ($1 > $2) print 1;}') )); then echo "yes"; else echo "no"; fi
Дэвид Георг Рейхельт

7

будьте осторожны при сравнении чисел, являющихся версиями пакета, например, проверка, если 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.
Нил Мэйхью

5

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

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

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


3

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

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

3

Я публиковал это как ответ на https://stackoverflow.com/a/56415379/1745001 когда он был закрыт, как дубликат этого вопроса, так что здесь это так, как и здесь:

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

@DudiBoy нет, это понятный, простой, переносимый awk-код или неочевидный, неясный, зависящий от оболочки shell + bc-код.
Эд Мортон

3

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

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

Если нет, то почему бы просто не использовать что-то читаемое и явное, например python? Ваши коллеги по программированию и будущие я будут вам благодарны. Вы можете использовать pythoninline с 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 Моя оригинальная версия была немного странной.
CivFan

Еще более кратко: используйте not(...)вместо0 if ... else 1
Нил Мэйхью

1
Если вы отправляете awk и sed (я смотрю на вас CivFan) на свалку истории, вы паршивый системный администратор и набираете слишком много кода. (А я люблю и использую Python, так что дело не в этом). -1 за неуместную скупость. В системном домене есть место для этих инструментов, Python или нет.
Майк С,

1
Интересно, что я закончил с хорошим старым Perl! awk '${print $5}' ptpd_log_file | perl -ne '$_ > 0.000100 && print' > /tmp/outfile, Очень просто. Каждый язык имеет свое место.
Майк С

1
Не смешивайся с синтапической болтовней. В отличие от python, awk является обязательной утилитой для каждой установки UNIX, а эквивалент awk python -c "import sys; sys.exit(0 if float($num1) < float($num2) else 1)"просто awk "BEGIN{exit ($num1 > $num2 ? 0 : 1)}".
Эд Мортон

2

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

2
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

2

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

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

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


2

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

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

1

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

2
проблема в том, что многие дистрибутивы не поставляются с установленным ksh, и если ваш скрипт будет использоваться другими, им, как правило, не нравится устанавливать дополнительные компоненты, особенно когда это просто скрипт, который должен быть написан на bash - можно было бы подумать, что им не нужна ДРУГАЯ оболочка для этого, что подрывает основную причину использования сценария bash, в первую очередь, - конечно, мы могли бы также написать код на C ++, но почему?
Osirisgothra

Какие дистрибутивы поставляются без установленного ksh?
piokuc

1
@piokuc, например, Ubuntu Desktop & Server. Я бы сказал, что это довольно важный ...
Олли

Кроме того, вопрос специально просит решение, которое работает в Bash. Для этого могут быть действительно веские причины. Скажем, это часть большого приложения, и перенести все в ksh невозможно. Или он работает на встроенной платформе, где установка другой оболочки действительно является проблемой.
Олли

1

Используя 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 предлагает гораздо больше ...


0

Как насчет этого? = 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

1
Скрипт Awk должен просто exit 0сообщать правду и exit 1возвращать ложь; тогда вы можете упростить до удивительно элегантного if awk 'BEGIN { exit (ARGV[1] >= ARGV[2]) ? 0 : 1 }' "$VAL_TO_CHECK" 1; then... (еще более элегантного, если вы инкапсулируете скрипт Awk в функцию оболочки).
tripleee
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.