如何在bash中使用浮点数算术?

342

我正在尝试在Bash脚本中计算两个图像宽度的商,但是Bash给出了0作为结果:

RESULT=$(($IMG_WIDTH/$IMG2_WIDTH))
我已经学习了Bash指南,知道应该使用bc。在网上的所有例子中,他们都使用bc。在echo中,我尝试将相同的内容放入我的SCALE中,但它没有起作用。
这是我在教程中找到的例子:
echo "scale=2; ${userinput}" | bc 

我该如何让Bash输出一个像 0.5 这样的浮点数?


11
对于每个在脚本中尝试使用浮点数算术的人,你需要问自己一个问题:_我真的需要使用浮点数算术吗?_有时候可以完全不用。例如,请参考BashFAQ/022的最后一部分。 - gniourf_gniourf
1
可以教授bash,例如浮点结果的整数除法。 - Cyrus
24个回答

319

你做不到。Bash只支持整数;你必须委托使用像bc这样的工具。


14
如何将类似 bc 这样的工具委派给 RESULT 变量以获得答案? - Medya Gh
2
所以你的意思是像这样 VAR=$(echo "scale=2; $(($IMG_WIDTH/$IMG2_WIDTH))" | bc) 吗? - Medya Gh
74
@Shevin VAR=$(echo "scale=2; $IMG_WIDTH/$IMG2_WIDTH" | bc)VAR=$(bc <<<"scale=2;$IMG_WIDTH/$IMG2_WIDTH"),而不使用 $(( ))(双括号);$(( )) 会在执行命令之前被 bash 展开。 - Nahuel Fouilleul
5
没错,但是awk通常更可能已经在系统中安装好了。 - CMCDragonkai
10
@NahuelFouilleul,你的答案最好。应该独立成为一个答案,并被接受为答案。这一行代码非常有用:VAR=$(bc <<<"scale=2;$IMG_WIDTH/$IMG2_WIDTH")。需注意保持原文意思不变,使翻译更加通俗易懂。 - David
显示剩余7条评论

287

你可以这样做:

bc <<< 'scale=2; 100/3'
33.33

更新 20130926:你可以使用:

bc -l <<< '100/3' # saves a few hits
33.33333333333333333333

17
@AndreasSpindler 这是一个有点老的帖子,但如果有人想知道,可以通过应用比例命令来更改,例如 bc -l <<< 'scale=2; 100/3' - Martie
如果你希望在bash中使用scale=0获取一个整数以供以后使用,请注意。从v1.06.95开始,由于某种原因,当输入数字具有小数部分时,bc会忽略scale变量。也许这在文档中有提到,但我找不到了。尝试:echo $(bc -l <<< 'scale=0; 1*3.3333') - Greg Bell
1
@GregBell 人手册上说:“除非特别说明,否则结果的比例是所涉及表达式的最大比例。”并且对于“/”运算符有一个额外的注释:“结果的比例是变量比例的值。” - psmith
2
谢谢@psmith。有趣的是,在除法中它说“结果的精度是变量精度的值”,但乘法不是这样。我的更好的例子:bc <<< 'scale=1; 1*3.00001' 精度是5,原因不明,bc <<< 'scale=1; 1/3.000001' 精度为1。有趣的是,除以1可以解决问题:bc <<< 'scale=1; 1*3.00001/1' 精度为1。 - Greg Bell
1
为了避免“语法错误:重定向意外”,请使用echo 'scale=2; 100/3' | bc而不是bc <<< 'scale=2; 100/3' - M Imam Pratama
显示剩余2条评论

217

bash

正如其他人所指出的,bash不支持浮点数运算,尽管您可以使用一些固定的小数位技巧来模拟,例如使用两个小数位:

echo $(( 100 * 1 / 3 )) | sed -e 's/..$/.&/;t' -e 's/.$/.0&/'

输出:

.33

请参考Nilfred的回答,该回答提供了一种类似但更简洁的方法。

备选方案

除了上述的bcawk方案外,还有以下方案:

clisp

clisp -x '(/ 1.0 3)'

使用清理过的输出:

clisp --quiet -x '(/ 1.0 3)'

或通过 stdin

echo '(/ 1.0 3)' | clisp --quiet | tail -n1

dc

echo 2k 1 3 /p | dc

天才CLI计算器

echo 1/3.0 | genius

ghostscript

echo 1 3 div = | gs -dNODISPLAY -dQUIET | sed -n '1s/.*>//p' 

gnuplot

echo 'pr 1/3.' | gnuplot

Imagemagick

convert xc: -format '%[fx:1/3]' info:

或通过 stdin

echo 1/3 | { convert xc: -format "%[fx:$(cat)]" info:; }

jq

jq -n 1/3

或者通过 stdin

echo 1/3 | jq -nf /dev/stdin

ksh

echo 'print $(( 1/3. ))' | ksh

lua

lua -e 'print(1/3)'

或通过标准输入:

echo 'print(1/3)' | lua

最大值

echo '1/3,numer;' | maxima

输出已经清理:

echo '1/3,numer;' | maxima --quiet | sed -En '2s/[^ ]+ [^ ]+ +//p'

node

echo 1/3 | node -p

octave

echo 1/3 | octave

perl

echo print 1/3 | perl

python2

echo print 1/3. | python2

python3

echo 'print(1/3)' | python3

R

echo 1/3 | R --no-save

输出已经清理干净:

echo 1/3 | R --vanilla --quiet | sed -n '2s/.* //p'

ruby

echo puts 1/3.0 | ruby

单位

units 1/3

使用紧凑输出:

units --com 1/3

wcalc

echo 1/3 | wcalc

清理后的输出:

echo 1/3 | wcalc | tr -d ' ' | cut -d= -f2

zsh

print $(( 1/3. ))

或通过 stdin

echo 'print $(( 1/3. ))' | zsh

其他资料来源

Stéphane Chazelas 在 UL 上回答了一个类似的问题。点击查看。


14
好的回答。我知道这是在问题发布几年后才发布的,但它更值得成为被采纳的答案。 - Brian Cline
5
如果您有可用的zsh,那么考虑使用zsh编写脚本而不是Bash可能是值得的。 - Andrea Corbellini
不错的列表。遗憾的是,第一个“bash”方法有一个缺陷。如果结果少于两个数字(=模拟小数位),那么“sed”将无法进行替换。例如对于“1/50”,echo $(( 100*1/50)) | sed 's/..$/.&/'打印出2而不是0.02 - Socowi
@Socowi:确实,在这种情况下需要进行第二次替换,请查看更新版本。 - Thor
3
同时也要强调的是,bc不支持浮点数。它使用固定点数,这是一个重大区别。 - Katastic Voyage
显示剩余4条评论

46

稍微改进了marvin的回答:

RESULT=$(awk "BEGIN {printf \"%.2f\",${IMG_WIDTH}/${IMG2_WIDTH}}")

bc并不总是作为预装软件包提供。


5
awk脚本需要一个“exit”来防止它从输入流中读取。我还建议使用awk的“-v”标志来防止倾斜牙签综合症。因此:RESULT=$(awk -v dividend="${IMG_WIDTH}" -v divisor="${IMG2_WIDTH}" 'BEGIN {printf "%.2f", dividend/divisor; exit(0)}') - aecolley
3
一种更加冗长的方法是从输入流中读取参数: RESULT=$(awk '{printf("result= %.2f\n",$1/$2)}' <<<" $IMG_WIDTH $IMG2_WIDTH "。将结果保留两位小数。 - jmster
1
bc 是 POSIX 的一部分,通常预先安装。 - fuz
1
这个在我使用 Windows 7 的 Git Bash 中有效...谢谢 :) - The Beast

41

你可以通过加上-l选项(大写字母L)来使用bc

RESULT=$(echo "$IMG_WIDTH/$IMG2_WIDTH" | bc -l)

5
如果我在系统中不加上“-l”,bc 就无法进行浮点数计算。 - starbeamrainbowlabs

34
作为bc的替代方案,您可以在脚本中使用awk。
例如:
echo "$IMG_WIDTH $IMG2_WIDTH" | awk '{printf "%.2f \n", $1/$2}'

以上中," %.2f "告诉printf函数以小数点后两位返回浮点数。我使用echo将变量作为字段输入,因为awk可以正常处理它们。"$1"和"$2"是指输入到awk的第一个和第二个字段。

您可以使用以下方式将结果存储为其他变量:

RESULT = `echo ...`

太好了!谢谢。这对于没有bc的嵌入式环境非常有帮助。你为我节省了一些交叉编译的时间。 - enthusiasticgeek

27

在浮点数出现之前,使用的是固定小数位的逻辑:

IMG_WIDTH=100
IMG2_WIDTH=3
RESULT=$((${IMG_WIDTH}00/$IMG2_WIDTH))
echo "${RESULT:0:-2}.${RESULT: -2}"
33.33

最后一行是bash命令,如果没有使用bash,请尝试使用以下代码代替:

IMG_WIDTH=100
IMG2_WIDTH=3
INTEGER=$(($IMG_WIDTH/$IMG2_WIDTH))
DECIMAL=$(tail -c 3 <<< $((${IMG_WIDTH}00/$IMG2_WIDTH)))
RESULT=$INTEGER.$DECIMAL
echo $RESULT
33.33

这段代码背后的原理是:在进行除法运算之前将数值乘以100,以获得2位小数。


2
技术术语为定点算术 - milahu
为了处理小结果(例如IMG2_WIDTH=3000)并防止出错,在bash中的echo之前添加以下行:[[ ${#RESULT} -lt 2 ]] && RESULT="0$RESULT" - Bryan Roach
为了处理小结果(例如IMG2_WIDTH=3000)并防止出错,在echo之前添加以下代码(在bash中):[[ ${#RESULT} -lt 2 ]] && RESULT="0$RESULT" - Bryan Roach

20

现在尝试zsh是完美的时机,它几乎是bash的超集,具有许多额外的不错特性,包括浮点数运算。以下是您在zsh中的示例:

% IMG_WIDTH=1080
% IMG2_WIDTH=640
% result=$((IMG_WIDTH*1.0/IMG2_WIDTH))
% echo $result
1.6875

这篇帖子可能对你有所帮助:bash-是否值得在日常使用中切换到zsh?


4
我非常喜欢 zsh,已经使用了 4 年了。但重点是交互式使用。需要 zsh 的脚本通常在不同的设备上不太便携,因为它通常不是标准的(公认的)。很遗憾(公正地说,也许这没关系;OP 没有说清楚它将如何使用)。 - Brian Cline

7

如何在Bash中进行浮点数计算:

与使用bc命令的 "here strings" (<<<) 不同,就像最受欢迎的示例之一所做的那样,这是我最喜欢的bc浮点数示例,直接来自bc手册的EXAMPLES部分(请参阅手册页man bc)。

在我们开始之前,请知道一个关于π的方程式: pi = 4*atan(1)。下面的a()bc数学函数atan()

  1. 这是如何将浮点数计算结果存储到bash变量中的方法--在本例中存储到名为pi的变量中。请注意,scale=10在此处设置精度为10位小数。此后的任何小数位都会被截断

     pi=$(echo "scale=10; 4*a(1)" | bc -l)
    
  2. 现在,为了让一行代码同时打印出该变量的值,只需将echo命令作为后续命令添加到末尾,如下所示。请注意,指定了10位小数的截断

     pi=$(echo "scale=10; 4*a(1)" | bc -l); echo $pi
     3.1415926532
    
  3. 最后,我们来加入一些四舍五入。这里我们将使用printf函数将结果保留4位小数。请注意,3.14159...现在四舍五入为3.1416。由于我们进行了四舍五入,因此不再需要使用scale=10来截断到10位小数,因此我们将删除该部分。以下是最终解决方案:

     pi=$(printf %.4f $(echo "4*a(1)" | bc -l)); echo $pi
     3.1416
    

这是另一个非常棒的应用程序和演示,展示了上述技术:测量和打印运行时间。

(另请参见我在此处的其他答案)。

请注意,dt_min0.01666666666... 被舍入为 0.017

start=$SECONDS; sleep 1; end=$SECONDS; dt_sec=$(( end - start )); dt_min=$(printf %.3f $(echo "$dt_sec/60" | bc -l)); echo "dt_sec = $dt_sec; dt_min = $dt_min"
dt_sec = 1; dt_min = 0.017

相关:


1
扎实的答案,附有详细的示例和用例,并使用printf进行四舍五入。 - downvoteit

6
如果您找到了偏好的变体,还可以将其包装到一个函数中。
这里我将一些bashism包装到div函数中:
一行代码:
function div { local _d=${3:-2}; local _n=0000000000; _n=${_n:0:$_d}; local _r=$(($1$_n/$2)); _r=${_r:0:-$_d}.${_r: -$_d}; echo $_r;}

或多行:

function div {
  local _d=${3:-2}
  local _n=0000000000
  _n=${_n:0:$_d}
  local _r=$(($1$_n/$2))
  _r=${_r:0:-$_d}.${_r: -$_d}
  echo $_r
}

现在你已经拥有了这个函数

div <dividend> <divisor> [<precision=2>]

并且像这样使用它

> div 1 2
.50

> div 273 123 5
2.21951

> x=$(div 22 7)
> echo $x
3.14

更新 我添加了一个小脚本,用于在bash中提供浮点数的基本运算:

使用方法:

> add 1.2 3.45
4.65
> sub 1000 .007
999.993
> mul 1.1 7.07
7.7770
> div 10 3
3.
> div 10 3.000
3.333

以下是脚本:

#!/bin/bash
__op() {
        local z=00000000000000000000000000000000
        local a1=${1%.*}
        local x1=${1//./}
        local n1=$((${#x1}-${#a1}))
        local a2=${2%.*}
        local x2=${2//./}
        local n2=$((${#x2}-${#a2}))
        local n=$n1
        if (($n1 < $n2)); then
                local n=$n2
                x1=$x1${z:0:$(($n2-$n1))}
        fi
        if (($n1 > $n2)); then
                x2=$x2${z:0:$(($n1-$n2))}
        fi
        if [ "$3" == "/" ]; then
                x1=$x1${z:0:$n}
        fi
        local r=$(($x1"$3"$x2))
        local l=$((${#r}-$n))
        if [ "$3" == "*" ]; then
                l=$(($l-$n))
        fi
        echo ${r:0:$l}.${r:$l}
}
add() { __op $1 $2 + ;}
sub() { __op $1 $2 - ;}
mul() { __op $1 $2 "*" ;}
div() { __op $1 $2 / ;}

1
本地变量 _d=${3:-2} 更简单。 - KamilCuk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接