Bash 中的浮点数运算

作者:Mitch Frazier

仔细想想,令人惊讶的是,有很多编程任务不需要使用浮点数。 如果您是嵌入式系统程序员,那么在 C 程序中使用“double”可能会被解雇。 如果您编写 PHP 或 JavaScript,请快速回答,它们甚至支持浮点数吗? Bash 就是一种不支持浮点数的语言,但让我们不要因此而停下脚步...

为 bash 添加浮点功能的明显选择是bc. bc引用手册页

任意精度计算器语言
顺便说一句,请注意引文中的最后一个词:“语言”。 是的bc实际上是一种编程语言,它包含if语句和while循环等。 我说是顺便说一句,因为它与我们今天要做的事情在很大程度上无关,但并非完全无关。

为了在我们的 bash 脚本中使用bc,我们将把它打包成几个函数

    float_eval EXPRESSION
and
    float_cond CONDITIONAL-EXPRESSION
这两个函数都期望一个浮点表达式,float_eval将表达式的评估结果写入标准输出,float_cond假设该表达式是一个条件表达式,如果表达式为真,则将返回/状态代码设置为零,如果为假,则设置为一。

用法非常简单

  float_eval '12.0 / 3.0'
  if float_cond '10.0 > 9.0'; then
    echo 'As expected, 10.0 is greater than 9.0'
  fi
  a=12.0
  b=3.0
  c=$(float_eval "$a / $b")

函数的代码如下

#!/bin/bash
#
# Floating point number functions.

#####################################################################
# Default scale used by float functions.

float_scale=2


#####################################################################
# Evaluate a floating point number expression.

function float_eval()
{
    local stat=0
    local result=0.0
    if [[ $# -gt 0 ]]; then
        result=$(echo "scale=$float_scale; $*" | bc -q 2>/dev/null)
        stat=$?
        if [[ $stat -eq 0  &&  -z "$result" ]]; then stat=1; fi
    fi
    echo $result
    return $stat
}


#####################################################################
# Evaluate a floating point number conditional expression.

function float_cond()
{
    local cond=0
    if [[ $# -gt 0 ]]; then
        cond=$(echo "$*" | bc -q 2>/dev/null)
        if [[ -z "$cond" ]]; then cond=0; fi
        if [[ "$cond" != 0  &&  "$cond" != 1 ]]; then cond=0; fi
    fi
    local stat=$((cond == 0))
    return $stat
}


# Test code if invoked directly.
if [[ $(basename $0 .sh) == 'float' ]]; then
    # Use command line arguments if there are any.
    if [[ $# -gt 0 ]]; then
        echo $(float_eval $*)
    else
        # Turn off pathname expansion so * doesn't get expanded
        set -f
        e="12.5 / 3.2"
        echo $e is $(float_eval "$e")
        e="100.4 / 4.2 + 3.2 * 6.5"
        echo $e is $(float_eval "$e")
        if float_cond '10.0 > 9.3'; then
            echo "10.0 is greater than 9.3"
        fi
        if float_cond '10.0 < 9.3'; then
            echo "Oops"
        else
            echo "10.0 is not less than 9.3"
        fi
        a=12.0
        b=3.0
        c=$(float_eval "$a / $b")
        echo "$a / $b" is $c
        set +f
    fi
fi

# vim: tabstop=4: shiftwidth=4: noexpandtab:
# kate: tab-width 4; indent-width 4; replace-tabs false;

函数的工作是通过将参数提供给bc:

   result=$(echo "scale=$float_scale; $*" | bc -q 2>/dev/null)
默认情况下bc输出的结果小数点右边没有数字,也没有小数点。 要更改此设置,您必须更改bc的内置变量之一scale。 这就是bc的“语言”功能发挥作用的地方,在bc中,就像在 C 中一样,语句用分号分隔。 我们设置bcscale变量,方法是在传递给bc的表达式前加上scale=$float_scale;。 这将bc中的 scale 设置为 bash 全局变量float_scale的值,默认设置为 2(脚本顶部附近)。

这里的主要陷阱与“*”、“<”和“>”在 bash 中具有其他含义有关。 您可以通过引用表达式来消除“<”和“>”的问题,但这仅适用于使用单引号的“*”,这意味着您不能在表达式中包含 bash 变量。 另一种选择是用 "set -f" 和 "set +f" 包围您的代码,以关闭路径名/通配符扩展。

如果您将脚本保存为float.sh并直接运行它,它将执行底部的测试代码

  $ sh float.sh
  12.5 / 3.2 is 3.90
  100.4 / 4.2 + 3.2 * 6.5 is 44.70
  10.0 is greater than 9.3
  10.0 is not less than 9.3
  12.0 / 3.0 is 4.00

您可能提出的一个未解答的问题是:“我为什么要这样做?” 下次我将向您展示一个您可以将其用于实际用途的地方。

Mitch Frazier 是 Emerson Electric Co. 的一名嵌入式系统程序员。自 2000 年代初以来,Mitch 一直是Linux Journal 的贡献者和朋友。

加载 Disqus 评论