如何与Shell脚本中的浮点数进行比较


Answers:


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.50.06)就无法解决此问题。您最好使用已经理解十进制表示法的工具。
吉尔(Gilles)'所以

感谢Gilles,对其进行了更新,使其比早期版本更通用。
ata

请注意,它表示1.00000000000000000000000001大于2
斯特凡Chazelas

斯特凡是正确的。之所以如此,是因为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 / R脚本(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.

这是一个bashhack ...它将整数前导0加到整数中,使字符串从左到右的比较有意义。此段特定代码要求 minval实际上都具有一个小数点和至少一个十进制数字。

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),则可以使用Shell的内置算术,该算术支持浮点数。

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符号

$ 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等中使用,请将最小值与值之一进行比较。如果最小值等于该值,则与text相比,它小于另一个值:

$ 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

只需使用kshksh93精确地)或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已经错过了建议。保持我的回答只是为了清楚地说明在开头问题中发布的脚本,可以在shell开关外部使用而无需进行任何更改。

Edit2:请注意,ksh93要求变量内容与您的语言环境一致,即与法国语言环境一致,必须使用逗号而不是点:

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

一个更可靠的解决方案是在脚本的开头设置语言环境,以确保无论用户的语言环境如何,该语言环境都可以工作:

...
export LC_ALL=C
min=12.45
val=10.35
...

请注意,以上ksh93脚本仅在小数点分隔符所在的语言环境中起作用.(因此,在小数点分隔符所在的世界的一半中无效,)。zsh没有那个问题。
斯特凡Chazelas

确实,对答案进行了编辑以澄清这一点。
jlliagre 2014年

如果用户设置了LC_NUMERIC,则设置将不起作用LC_ALL,这也意味着数字将不会以用户首选的格式显示(或输入)。请参阅unix.stackexchange.com/questions/87745/what-does-lc-all-c-do/…,以获得可能更好的方法。
斯特凡Chazelas

@StéphaneChazelas解决了LC_NUMERIC问题。给定OP脚本语法,我假设他首选的分隔符仍然是.
jlliagre 2014年

是的,但是重要的是脚本用户的语言环境,而不是脚本作者的语言环境。作为脚本作者,您应该考虑本地化及其副作用。
斯特凡Chazelas

1
min=$(echo "${min}sa ${val}d la <a p" | dc)

使用该dc计算器,以s撕开值$min寄存器aduplicates的值$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
能否请您评论一下答案并添加一些解释
罗密欧·尼诺夫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.