Як порівняти номер із плаваючою комою у сценарії оболонки


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.
Stéphane Chazelas

Стефан має рацію. Це так через обмеження бітів у кількості представлення bash. Звичайно, якщо ви хочете більше страждань, ви можете скористатись власним представництвом .... :)
ата

35

Баш не розуміє арифметику з плаваючою комою. Він розглядає числа, що містять десяткову точку, як рядки.

Використовуйте замість 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

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

Операція порівняння

Для реалізації порівняння <("менше, ніж"), щоб його можна було використовувати в ifтощо, порівняйте мінімум з одним із значень. Якщо мінімум дорівнює значенню, порівняно з текстом , воно менше іншого значення:

$ 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
Дейв

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
...

Зауважте, що вищевказаний скрипт 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

0

Чому б не використовувати старе, добре expr ?

Приклад синтаксису:

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

Для справжніх виразів код виходу expr дорівнює 0, а рядок '1' надсилається до stdout. Зворотне для помилкового виразів.

Я перевірив це за допомогою GNU та FreeBSD 8 expr.


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

-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
Чи можете ви прокоментувати свою відповідь та додати пояснення
Ромео Нінов
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.