Как сравнить с числом с плавающей точкой в ​​сценарии оболочки

22

Я хочу сравнить два числа с плавающей точкой в ​​сценарии оболочки. Следующий код не работает:

#!/bin/bash   
min=12.45
val=10.35    
if (( $val < $min )) ; then    
  min=$val
fi
echo $min 
Ричард Уильямс
источник

Ответы:

5

Вы можете проверить отдельно целую и дробную части:

#!/bin/bash
min=12.45
val=12.35    
if (( ${val%%.*} < ${min%%.*} || ( ${val%%.*} == ${min%%.*} && ${val##*.} < ${min##*.} ) )) ; then    
    min=$val
fi
echo $min

Как сказано в комментариях fered, это работает, только если оба числа имеют дробные части, а обе дробные части имеют одинаковое количество цифр. Вот версия, которая работает для целых или дробных и любого оператора bash:

#!/bin/bash
shopt -s extglob
fcomp() {
    local oldIFS="$IFS" op=$2 x y digitx digity
    IFS='.' x=( ${1##+([0]|[-]|[+])}) y=( ${3##+([0]|[-]|[+])}) IFS="$oldIFS"
    while [[ "${x[1]}${y[1]}" =~ [^0] ]]; do
        digitx=${x[1]:0:1} digity=${y[1]:0:1}
        (( x[0] = x[0] * 10 + ${digitx:-0} , y[0] = y[0] * 10 + ${digity:-0} ))
        x[1]=${x[1]:1} y[1]=${y[1]:1} 
    done
    [[ ${1:0:1} == '-' ]] && (( x[0] *= -1 ))
    [[ ${3:0:1} == '-' ]] && (( y[0] *= -1 ))
    (( ${x:-0} $op ${y:-0} ))
}

for op in '==' '!=' '>' '<' '<=' '>='; do
    fcomp $1 $op $2 && echo "$1 $op $2"
done
ата
источник
4
Это не может быть исправлено без большой работы (попробуйте сравнить 0.5и 0.06). Вам лучше использовать инструмент, который уже понимает десятичную запись.
Жиль "ТАК - перестань быть злым"
Спасибо Жиль, обновил его, чтобы работать более широко, чем в более ранней версии.
ата
Обратите внимание, что это говорит о том, что 1.00000000000000000000000001больше, чем 2.
Стефан Шазелас
Стефан прав. Это так из-за ограничений по битам в числовом представлении bash. Конечно, если вы хотите больше страданий, вы можете использовать свое собственное представление .... :)
ata
35

Bash не понимает арифметику с плавающей точкой. Числа, содержащие десятичную точку, обрабатываются как строки.

Используйте вместо этого awk или bc.

#!/bin/bash

min=12.45
val=10.35

if [ 1 -eq "$(echo "${val} < ${min}" | bc)" ]
then  
    min=${val}
fi

echo "$min"

Если вы собираетесь выполнять много математических операций, возможно, лучше использовать Python или Perl.

Даниэль Нэслунд
источник
12

Вы можете использовать пакет num-utils для простых манипуляций ...

Для более серьезной математики, смотрите эту ссылку ... Он описывает несколько вариантов, например.

  • R / Rscript (GNU R статистические вычисления и графическая система)
  • октава (в основном Matlab-совместимый)
  • bc (язык калькулятора произвольной точности GNU bc)

Пример numprocess

echo "123.456" | numprocess /+33.267,%2.33777/
# 67.0395291239087  

A programs for dealing with numbers from the command line

The 'num-utils' are a set of programs for dealing with numbers from the
Unix command line. Much like the other Unix command line utilities like
grep, awk, sort, cut, etc. these utilities work on data from both
standard in and data from files.

Includes these programs:
 * numaverage: A program for calculating the average of numbers.
 * numbound: Finds the boundary numbers (min and max) of input.
 * numinterval: Shows the numeric intervals between each number in a sequence.
 * numnormalize: Normalizes a set of numbers between 0 and 1 by default.
 * numgrep: Like normal grep, but for sets of numbers.
 * numprocess: Do mathematical operations on numbers.
 * numsum: Add up all the numbers.
 * numrandom: Generate a random number from a given expression.
 * numrange: Generate a set of numbers in a range expression.
 * numround: Round each number according to its value.

Вот bashхак ... Он добавляет начальные 0 к целому числу, чтобы сделать сравнение строк слева направо значимым. Этот конкретный фрагмент кода требует, чтобы и min, и val фактически имели десятичную точку и хотя бы одну десятичную цифру.

min=12.45
val=10.35

MIN=0; VAL=1 # named array indexes, for clarity
IFS=.; tmp=($min $val); unset IFS 
tmp=($(printf -- "%09d.%s\n" ${tmp[@]}))
[[ ${tmp[VAL]} < ${tmp[MIN]} ]] && min=$val
echo min=$min

выход:

min=10.35
Peter.O
источник
10

Для простых вычислений с числами с плавающей запятой (+ - * / и сравнения) вы можете использовать awk.

min=$(echo 12.45 10.35 | awk '{if ($1 < $2) print $1; else print $2}')

Или, если у вас ksh93 или zsh (не bash), вы можете использовать встроенную арифметику вашей оболочки, которая поддерживает числа с плавающей запятой.

if ((min>val)); then ((val=min)); fi

Для более сложных вычислений с плавающей запятой посмотрите bc . На самом деле он работает с числами с произвольной точностью.

Для работы с таблицами чисел найдите R ( пример ).

Жиль "ТАК - перестань быть злым"
источник
6

Использовать числовую сортировку

У команды sortесть опция -g( --general-numeric-sort), которую можно использовать для сравнения значений <«меньше чем» или >«больше чем» путем нахождения минимума или максимума.

Эти примеры находят минимум:

$ printf '12.45\n10.35\n' | sort -g | head -1
10.35

Поддерживает электронную нотацию

Он работает с довольно общими обозначениями чисел с плавающей запятой, как с E-Notation

$ printf '12.45E-10\n10.35\n' | sort -g | head -1
12.45E-10

Обратите внимание на то E-10, что первое число 0.000000001245действительно меньше 10.35.

Можно сравнить с бесконечностью

Стандарт с плавающей запятой, IEEE754 , определяет некоторые специальные значения. Для этих сравнений самые интересные INFдля бесконечности. Существует также отрицательная бесконечность; Оба хорошо определены значения в стандарте.

$ printf 'INF\n10.35\n' | sort -g | head -1
10.35
$ printf '-INF\n10.35\n' | sort -g | head -1
-INF

Чтобы найти максимальное использование sort -grвместо того sort -g, чтобы изменить порядок сортировки:

$ printf '12.45\n10.35\n' | sort -gr | head -1
12.45

Операция сравнения

Чтобы реализовать <сравнение («меньше чем»), чтобы его можно было использовать в ifetc, сравните минимум с одним из значений. Если минимум равен значению, сравнивается как текст , он меньше другого значения:

$ a=12.45; b=10.35                                    
$ [ "$a" = "$(printf "$a\n$b\n" | sort -g | head -1)" ]
$ echo $?
1
$ a=12.45; b=100.35                                    
$ [ "$a" = "$(printf "$a\n$b\n" | sort -g | head -1)" ]
$ echo $?                                              
0
Volker Siegel
источник
Хороший совет! Мне очень нравится ваше понимание того, что проверка a == min(a, b)такая же как a <= b. Стоит отметить, что это не проверяет строго меньше, чем, хотя. Если вы хотите сделать это, вы должны проверить a == min(a, b) && a != max(a, b), другими словамиa <= b and not a >= b
Дейв
3

Просто используйте ksh( ksh93точно) или zsh, которые изначально поддерживают арифметику с плавающей запятой:

$ cat test.ksh
#!/bin/ksh 
min=12.45
val=10.35    
if (( $val < $min )) ; then    
  min=$val
fi
echo "$min"
$ ./test.ksh
10.35

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

Edit2: обратите внимание, что ksh93 требуется, чтобы содержимое переменной было согласовано с вашей локалью, то есть с французской локалью, вместо точки должна использоваться запятая:

...
min=12,45
val=10,35
...

Более надежное решение - установить языковой стандарт в начале скрипта, чтобы он работал независимо от языкового стандарта пользователя:

...
export LC_ALL=C
min=12.45
val=10.35
...
jlliagre
источник
Обратите внимание, что приведенный выше скрипт ksh93 работает только в локалях, где есть десятичный разделитель .(поэтому не в половине мира, где есть десятичный разделитель ,). zshне имеет этой проблемы.
Стефан Шазелас
Действительно, ответ отредактирован, чтобы прояснить этот момент.
Jlliagre
Установка LC_NUMERIC не будет работать, если пользователь установил LC_ALL, это также означает, что числа не будут отображаться (или вводиться) в предпочтительном формате пользователя. См. Unix.stackexchange.com/questions/87745/what-does-lc-all-c-do/… для потенциально лучшего подхода.
Стефан Шазелас
@ StéphaneChazelas исправил ошибку LC_NUMERIC. Учитывая синтаксис OP-сценария, я предполагаю, что его предпочтительный разделитель в .любом случае.
jlliagre
Да, но важен язык пользователя скрипта, а не язык автора скрипта. Как автор сценария, вы должны учитывать локализацию и ее побочные эффекты.
Стефан Шазелас
1
min=$(echo "${min}sa ${val}d la <a p" | dc)

Это использует dcкалькулятор, чтобы порвать sзначение для $minв регистре aи dпомещает значение $valна вершину его основного стека выполнения. Затем он lпомещает содержимое aна вершину стека, и в этот момент он выглядит следующим образом:

${min} ${val} ${val}

<Выскакивает две верхние записи из стека и сравнивает их. Таким образом, стек выглядит так:

${val}

Если верхняя запись была меньше, чем вторая, она помещает содержимое aв верхнюю часть, поэтому стек выглядит так:

${min} ${val}

Иначе это ничего не делает, и стек все еще выглядит так:

${val} 

Тогда это просто p набирает запись верхнего стека.

Итак, для вашей проблемы:

min=12.45
val=12.35
echo "${min}sa ${val}d la <a p" | dc

###OUTPUT

12.35

Но:

min=12.45
val=12.55
echo "${min}sa ${val}d la <a p" | dc

###OUTPUT

12.45
mikeserv
источник
0

Почему бы не использовать старое, хорошее expr ?

Пример синтаксиса:

if expr 1.09 '>' 1.1 1>/dev/null; then
    echo 'not greater'
fi

Для истинных выражений код выхода expr равен 0, а строка '1' отправляется на стандартный вывод. Обратное для ложного выражений.

Я проверил это с GNU и FreeBSD 8 expr.

sam_pan_mariusz
источник
GNU expr поддерживает только арифметическое сравнение целых чисел. Ваш пример использует лексикографическое сравнение, которое не сработает на отрицательных числах. Например, expr 1.09 '<' -1.1напечатает 1и выйдет с 0(успехом).
Адриан Гюнтер
0

Чтобы проверить, в порядке ли два (возможно, дробных) числа, sort(разумно) переносимо:

min=12.45
val=12.55
if { echo $min ; echo $val ; } | sort -n -c 2>/dev/null
then
  echo min is smallest
else
  echo val is smallest
fi

Однако, если вы действительно хотите сохранить минимальное значение обновленным, тогда вам не нужно if. Сортируйте числа и всегда используйте первый (наименьший):

min=12.45
val=12.55
smallest=$({ echo $min ; echo $val ; } | sort -n | head -n 1)
echo $smallest
min=$smallest
Дэвид Джонс
источник
0

Обычно я делаю подобные вещи со встроенным кодом Python:

#!/bin/sh

min=12.45
val=10.35

python - $min $val<<EOF
if ($min > $val):
        print $min
else: 
        print $val
EOF
dganesh2002
источник
-1
$ min=12.45
$ val=10.35
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 12.45
$ val=13
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 13
沙漠 之 子
источник
3
Можете ли вы прокомментировать свой ответ и добавить некоторые объяснения
Ромео Нинов