Я хочу сравнить два числа с плавающей точкой в сценарии оболочки. Следующий код не работает:
#!/bin/bash
min=12.45
val=10.35
if (( $val < $min )) ; then
min=$val
fi
echo $min
Я хочу сравнить два числа с плавающей точкой в сценарии оболочки. Следующий код не работает:
#!/bin/bash
min=12.45
val=10.35
if (( $val < $min )) ; then
min=$val
fi
echo $min
Ответы:
Вы можете проверить отдельно целую и дробную части:
#!/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
1.00000000000000000000000001
больше, чем 2
.
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.
Вы можете использовать пакет num-utils для простых манипуляций ...
Для более серьезной математики, смотрите эту ссылку ... Он описывает несколько вариантов, например.
Пример 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
Для простых вычислений с числами с плавающей запятой (+ - * / и сравнения) вы можете использовать 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 . На самом деле он работает с числами с произвольной точностью.
У команды 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
Чтобы реализовать <
сравнение («меньше чем»), чтобы его можно было использовать в if
etc, сравните минимум с одним из значений. Если минимум равен значению, сравнивается как текст , он меньше другого значения:
$ 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
a == min(a, b)
такая же как a <= b
. Стоит отметить, что это не проверяет строго меньше, чем, хотя. Если вы хотите сделать это, вы должны проверить a == min(a, b) && a != max(a, b)
, другими словамиa <= b and not a >= b
Просто используйте 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
...
.
(поэтому не в половине мира, где есть десятичный разделитель ,
). zsh
не имеет этой проблемы.
LC_ALL
, это также означает, что числа не будут отображаться (или вводиться) в предпочтительном формате пользователя. См. Unix.stackexchange.com/questions/87745/what-does-lc-all-c-do/… для потенциально лучшего подхода.
.
любом случае.
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
Почему бы не использовать старое, хорошее expr
?
Пример синтаксиса:
if expr 1.09 '>' 1.1 1>/dev/null; then
echo 'not greater'
fi
Для истинных выражений код выхода expr равен 0, а строка '1' отправляется на стандартный вывод. Обратное для ложного выражений.
Я проверил это с GNU и FreeBSD 8 expr.
expr 1.09 '<' -1.1
напечатает 1
и выйдет с 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
Обычно я делаю подобные вещи со встроенным кодом Python:
#!/bin/sh
min=12.45
val=10.35
python - $min $val<<EOF
if ($min > $val):
print $min
else:
print $val
EOF
$ min=12.45
$ val=10.35
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 12.45
$ val=13
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 13
0.5
и0.06
). Вам лучше использовать инструмент, который уже понимает десятичную запись.