如何在bash中使用bc舍入小数?


45

我想要使​​用bash脚本的一个简单示例:

#!/bin/bash
echo "Insert the price you want to calculate:"
read float
echo "This is the price without taxes:"
echo "scale=2; $float/1.18" |bc -l
read -p "Press any key to continue..."
bash scriptname.sh

假设价格是:48.86答案将是:41.406779661(41.40实际上是因为我正在使用scale=2;

我的问题是: 如何舍入第二位小数以这种方式显示答案?:41.41


我觉得很奇怪,因为“ printf“%0.2f \ n” 41.445“现在可以工作,但是” printf“%0.2f \ n” 41.435和printf“%0.2f \ n” 41.455“可以。即使您自己的案例也可以使用(在12.04上),但不能使用.445
Luis Alvarado

8
恕我直言,没有人满意地回答了这个问题,也许是因为bc无法实现所要求的内容(或者至少是我找到这篇文章时我所问的问题),即如何使用十进制小数bc(碰巧由调用bash)。
亚当·卡兹

我知道这个问题很旧,但是我无法抗拒发表自己的bc实现的评论:github.com/gavinhoward/bc。为此,它在具有该r()功能的内置库中,因此您可以使用bc -le "r($float/1.18, 2)"
加文·霍华德

Answers:


31

一个bash回合函数:

round()
{
echo $(printf %.$2f $(echo "scale=$2;(((10^$2)*$1)+0.5)/(10^$2)" | bc))
};

在您的代码示例中使用:

#!/bin/bash
# the function "round()" was taken from 
# http://stempell.com/2009/08/rechnen-in-bash/

# the round function:
round()
{
echo $(printf %.$2f $(echo "scale=$2;(((10^$2)*$1)+0.5)/(10^$2)" | bc))
};

echo "Insert the price you want to calculate:"
read float
echo "This is the price without taxes:"
#echo "scale=2; $float/1.18" |bc -l
echo $(round $float/1.18 2);
read -p "Press any key to continue..."

祝你好运:o)


1
顺便说一句,如果数字为负,我们必须使用-0.5
Aquarius Power

无法在测试控制台上运行该示例!“调试”一点表明,例如,$2 = 3我必须使用echo $(env printf %.3f $(echo "scale=3;((1000*$1)+0.5)/1000" | bc))。胸怀ENV的printf之前!这将再次告诉您,了解您正在从其他位置复制粘贴的内容始终很重要。
语法错误

@Aquarius Power感谢您的灵感!我现在分叉了上述脚本的一个版本,该版本可以同时使用负数和正数。
2014年

这不必要地复杂,并且需要进行各种额外的运算才能得出migas和zuberuber提供的更简单的答案。
亚当·卡兹

@AquariusPower +1是的,负数是不同的。为了测试负数(非整数!),我尝试了一下,并确定了if [ 回显“ $ 1/1” | bc` -gt 0]`-是否有更优雅的检查方法(将字符串解析为“-”除外)?
罗伯特·G

30

最简单的解决方案:

printf %.2f $(echo "$float/1.18" | bc -l)

2
就像在上面的评论(askubuntu.com/a/179949/512213)中指出的那样,不幸的是,对于负数,这将表现不正确。
罗伯特·G

2
@RobertG:为什么?此解决方案不使用+0.5。尝试一下printf "%.2f\n" "$(bc -l <<<"48.86/1.18")" "$(bc -l <<<"-48.86/1.18")",您将得到41.41-41.41
musiphil

1
也可以使用管道:echo "$float/1.18" | bc -l | xargs printf %.2f
甜点

此答案中的解决方案为我提供了:bash: printf: 1.69491525423728813559: invalid number,具体取决于语言环境。
Torsten Bronger

19

重击/ awk舍入:

echo "23.49" | awk '{printf("%d\n",$1 + 0.5)}'  

如果您有python,则可以使用以下代码:

echo "4.678923" | python -c "print round(float(raw_input()))"

感谢您的提示,但不能解决我的需要。我必须使用bash来做...
blackedx 2012年

1
python命令更具可读性,适用于快速脚本。通过在舍入函数中添加例如“,3”,还支持任意数字舍入。谢谢
Elle

echo "$float" |awk '{printf "%.2f", $1/1.18}'将执行问题所要求的数学运算到所要求的百分之一精度。这bc与问题中的调用一样“使用bash” 。
亚当·卡兹

2
对于Python,您可以只使用python -c "print(round($num))"where num=4.678923。无需对stdin感到厌烦。您也可以像这样舍入到n位数字:python -c "print(round($num, $n))"
2015年

我设法用该解决方案进行了一些整洁的工作,我的问题是0.5,在这种情况下,我并非总是能得到所需的回合,因此我想出了以下几点:(echo 5 | python -c "print int(round((float(raw_input())/2)-0.5))"当+会向上取整-向下取整)。
Yaron

4

这是一个纯粹的BC解决方案。舍入规则:+/- 0.5,从零开始舍入。

将您要查找的比例尺放入$ result_scale;中。您的数学应该是$ MATH在bc命令列表中的位置:

bc <<MATH
h=0
scale=0

/* the magnitude of the result scale */
t=(10 ^ $result_scale)

/* work with an extra digit */
scale=$result_scale + 1

/* your math into var: m */
m=($MATH)

/* rounding and output */
if (m < 0) h=-0.5
if (m > 0) h=0.5

a=(m * t + h)

scale=$result_scale
a / t
MATH

1
很有意思!MATH=-0.34;result_scale=1;bc <<MATH,#ObjectiveAndClear:5的解释这里 :)
水瓶座功率

我为此提名“最佳答案”。
Marc Tamsky

2

我知道这是一个老问题,但是我有一个纯的“ bc”解决方案,没有“ if”或分支:

#!/bin/sh
bcr()
{
    echo "scale=$2+1;t=$1;scale-=1;(t*10^scale+((t>0)-(t<0))/2)/10^scale" | bc -l
}

bcr '2/3' 5bcr '0.666666' 2-> 使用它(表达式后跟小数位数)

这是可能的,因为在bc(如C / C ++)中,允许在计算中混合逻辑表达式。该表达式的((t>0)-(t<0))/2)取值取决于't'的正负号,因此使用正确的值进行舍入。


这看起来很整洁,但bcr "5 / 2" 0返回2而不是3。我想念什么吗?
blujay

1
#!/bin/bash
# - loosely based on the function "round()", taken from 
# http://stempell.com/2009/08/rechnen-in-bash/

# - inspired by user85321 @ askubuntu.com (original author)
#   and Aquarius Power

# the round function (alternate approach):

round2()
{
    v=$1
    vorig=$v
    # if negative, negate value ...
    (( $(bc <<<"$v < 0") == 1 )) && v=$(bc <<<"$v * -1")
    r=$(bc <<<"scale=$3;(((10^$3)*$v/$2)+0.5)/(10^$3)")

    # ... however, since value was only negated to get correct rounding, we 
    # have to add the minus sign again for the resulting value ...

    (( $(bc <<< "$vorig < 0") == 1 )) && r=$(bc <<< "$r * -1")
    env printf %.$3f $r
};

echo "Insert the price you want to calculate:"
read float
echo "This is the price without taxes:"
round2 $float 1.18 2
echo && read -p "Press any key to continue..."

它实际上很简单:不需要为负数显式添加硬编码的“ -0.5”变体。从数学上讲,我们将只计算参数的绝对值,并且仍然像往常一样加上 0.5。但是由于(不幸的是)我们没有内置abs()函数可供使用(除非我们编写了一个函数),因此如果该参数为负数,我们将对其反。

此外,使用作为参数被证明非常麻烦(因为对于我的解决方案,我必须能够分别访问除数和除数)。这就是为什么我的脚本还有其他第三个参数的原因。


@muru关于您最近的编辑:echo .. |shell算术代替herestrings ,这是什么意思echo $()这很容易解释:这里的字符串将始终需要可写/tmp目录!我的解决方案也将在只读环境,如紧急的root shell哪里工作,/不是默认总是可写的。因此,以这种方式进行编码是有充分的理由的。
语法错误

我同意。但是何时echo $()需要?那和缩进促使我进行了编辑,这里的字符串刚刚发生。
muru 2015年

@muru早该解决的最没有意义的事echo $()实际上是倒数第二行,哈哈。感谢您的单挑。至少现在这看起来确实好多了,我不能否认。无论如何,我喜欢这种逻辑。很多布尔值的东西,多余的“ if”(肯定会使代码炸毁50%)。
syntaxerror

1

我仍在寻找关于bc如何在函数中仅舍入一个值的纯净bash答案,但这是一个纯净答案:

#!/bin/bash

echo "Insert the price you want to calculate:"
read float
echo "This is the price without taxes:"

embiggen() {
  local int precision fraction=""
  if [ "$1" != "${1#*.}" ]; then  # there is a decimal point
    fraction="${1#*.}"       # just the digits after the dot
  fi
  int="${1%.*}"              # the float as a truncated integer
  precision="${#fraction}"   # the number of fractional digits
  echo $(( 10**10 * $int$fraction / 10**$precision ))
}

# round down if negative
if [ "$float" != "${float#-}" ]
  then round="-5000000000"
  else round="5000000000"
fi

# calculate rounded answer (sans decimal point)
answer=$(( ( `embiggen $float` * 100 + $round ) / `embiggen 1.18` ))

int=${answer%??}  # the answer as a truncated integer

echo $int.${answer#$int}  # reassemble with correct precision

read -p "Press any key to continue..."

基本上,这会仔细地提取小数点,然后将所有内容乘以1000亿(10 10**10英寸,英寸bash),调整精度和舍入,执行实际的除法,除以适当的大小,然后重新插入小数点。

一步步:

embiggen()函数将其参数的截断整数形式分配给$int,并将数字保存在中的点之后$fraction。小数位数在中注明$precision。数学乘以10 10通过的级联$int$fraction,然后调节其到精确匹配(例如embiggen 48.86变为10 10×百分之四千八百八十六并返回488600000000其为4886亿)。

我们希望最终精度为百分之一,因此我们将第一个数字乘以100,为取整目的加5,然后将第二个数字除。这项任务$answer使我们得到最终答案的一百倍。

现在我们需要添加小数点。我们为新$int值分配一个$answer不包括其最后两位数字的值,然后给echo它加上一个点和$answer不包含在内$int的已被处理的值。(不要担心语法突出显示错误,使它看起来像注释)

(Bashism:幂运算不是POSIX,所以这是bashism。纯POSIX解决方案将要求循环添加零而不是使用10的幂。而且,“ embiggen”是一个完美的单词。)


zsh用作外壳程序的主要原因之一是它支持浮点数学运算。这个问题的解决方案非常简单zsh

printf %.2f $((float/1.18))

(我很乐意看到有人对此答案添加注释,并在中启用了浮点运算bash,但我很确定这种功能尚不存在。)


1

如果您有结果,例如考虑2.3747888

您要做的就是:

d=$(echo "(2.3747888+0.5)/1" | bc); echo $d

这会正确舍入数字示例:

(2.49999 + 0.5)/1 = 2.99999 

小数点被删除bc,因此四舍五入为2


1

我必须计算音频文件集合的总持续时间。

所以我必须:

A.获取每个文件的持续时间(未显示

B.总计所有持续时间(它们分别以NNN.NNNNNN(fp)秒为单位)

C.分开的小时,分​​钟,秒,亚秒。

D.输出字符串HR:MIN:SEC:FRAMES,其中frame = 1/75 sec。

(帧来自工作室中使用的SMPTE代码。)


答:使用ffprobe持续时间行并将其解析为fp编号(未显示)

B:

 # add them up as a series of strings separated by "+" and send it to bc

arr=( "${total[@]}" )  # copy array

# IFS is "Internal Field Separator"
# the * in arr[*] means "all of arr separated by IFS" 
# must have been made for this
IFS='+' sum=$(echo "scale=3; ${arr[*]} "| bc -l)# (-l= libmath for fp)
echo $sum 

C:

# subtract each amount of time from tt and store it    
tt=$sum   # tt is a running var (fp)


hrs=$(echo "$tt / 3600" | bc)

tt=$(echo "$tt - ( $hrs * 3600 )" | bc )

min=$(echo "$tt / 60" | bc )

tt=$(echo "$tt - ($min *60)" | bc )

sec=$(echo "$tt/1" | bc )

tt=$(echo "$tt - $sec" | bc )

frames=$(echo "$tt * 75"  | bc ) # 75 frames /sec 
frames=$(echo "$frames/1" | bc ) # truncate to whole #

D:

#convert to proper format with printf (bash builtin)        
hrs=$(printf "%02d\n" $hrs)  # format 1 -> 01 

min=$(printf "%02d\n" $min)

sec=$(printf "%02d\n" $sec)

frames=$(printf "%02d\n" $frames)

timecode="$hrs:$min:$sec:$frames"

# timecode "01:13:34:54"

1

这是脚本的缩写版本,已固定为提供所需的输出:

#!/bin/bash
float=48.86
echo "You asked for $float; This is the price without taxes:"
echo "scale=3; price=$float/1.18 +.005; scale=2; price/1 " | bc

请注意,四舍五入到最接近的整数等效于加.5并取整,或四舍五入(对于正数)。

同样,比例因子在操作时适用;因此(这些是bc命令,您可以将其粘贴到终端中):

float=48.86; rate=1.18; 
scale=2; p2=float/rate
scale=3; p3=float/rate
scale=4; p4=float/rate
print "Compare:  ",p2, " v ", p3, " v ", p4
Compare:  41.40 v 41.406 v 41.4067

# however, scale does not affect an entered value (nor addition)
scale=0
a=.005
9/10
0
9/10+a
.005

# let's try rounding
scale=2
p2+a
41.405
p3+a
41.411
(p2+a)/1
41.40
(p3+a)/1
41.41

1

要求的纯BC实施

define ceil(x) { auto os,xx;x=-x;os=scale;scale=0 xx=x/1;if(xx>x).=xx-- scale=os;return(-xx) }

如果将其放在一个名为functions.bc的文件中,则可以使用

echo 'ceil(3.1415)' | bc functions.bc

http://phodd.net/gnu-bc/code/funcs.bc上找到的bc实现代码


0
bc() {
        while :
        do
                while IFS='$\n' read i
                do
                        /usr/bin/bc <<< "scale=2; $i" | sed 's/^\./0&/'
                done
        done
}

0

您可以使用awk,不需要管道:

$> PRICE=48.86
$> awk -vprice=$PRICE "BEGIN{printf(\"%.2f\n\",price/1.18+0.005)}"
41.41

与环境变量预先设定的价格PRICE=<val>

  • 然后调用awk- $PRICE将作为awk变量进入awk price

  • 然后将其四舍五入到+.005的最接近的100位。

  • printf格式选项%.2f将小数位数限制为两位小数。

如果您必须执行很多操作,则此方法比所选答案要有效得多:

time for a in {1..1000}; do echo $(round 48.86/1.18 2) > /dev/random; done
real    0m8.945s
user    0m6.067s
sys 0m4.277s

time for a in {1..1000}; do awk -vprice=$PRICE "BEGIN{printf(\"%.2f\n\",price/1.18+0.005)}">/dev/random; done
real    0m2.628s
user    0m1.462s
sys 0m1.275s
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.