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;

私の質問は次のとおりです。 このように答えを表示するために2番目の小数を丸める方法は?:41.41


「printf "%0.2f \ n" 41.445 "は動作するが、" printf "%0.2f \ n" 41.435およびprintf "%0.2f \ n" 41.455 "は動作するため、奇妙に感じます。0.445でさえ、独自のケース(12.04オン)作品ではなく
ルイス・アルバラード

8
私見では、誰もが満足にこの質問に答えていないかもしれないので、bc缶が要求された(または私はこの記事を見つけたとき、私が求めていた、少なくとも問題)である、されているものを達成しませラウンド小数に使用する方法bc(で呼ばれるように起こっていますbash)。
アダム・カッツ

私はこの質問が古いことを知っていますが、bc:github.com/gavinhoward/bcの私自身の実装をプッシュするコメントに抵抗することはできません。そのためには、r()関数の組み込みライブラリにあるため、を使用できますbc -le "r($float/1.18, 2)"
ギャビンハワード

回答:


31

バッシュラウンド関数:

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
アクエリアスパワー

テストコンソールで動作する例を取得できませんでした!「デバッグ」を行うと、たとえば$2 = 3を使用する必要があることがわかりましたecho $(env printf %.3f $(echo "scale=3;((1000*$1)+0.5)/1000" | bc))。printfの前にenvに注意してください!これは、他の場所からコピーアンドペーストしているものを理解することが常に重要であることを再び教えてくれます。
構文エラー14年

@Aquarius Powerインスピレーションをありがとう!負の数と正の数の両方で機能する上記のスクリプトのバージョンを分岐させました。
構文エラー14年

これは不必要に複雑であり、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、ロケールに応じて:を提供します。
トーステンブロンガー

19

Bash / awkの丸め:

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

Pythonがある場合は、次のようなものを使用できます。

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

先端をありがとう、しかしそれは私が必要とするものを解決しません。bashを使用するだけでそれを行う必要があります...
blackedx

1
pythonコマンドは読みやすく、簡単なスクリプトに適しています。また、ラウンド関数に「、3」などを追加することにより、任意の桁の丸めをサポートします。ありがとう
エル

echo "$float" |awk '{printf "%.2f", $1/1.18}'質問の要求された計算を、要求された100分の1の精度で実行します。これbcは、質問の呼び出しと同じくらい「bashを使用する」ことです。
アダム・カッツ

2
Pythonの場合はpython -c "print(round($num))"、whereのようなものを使用できますnum=4.678923。stdinをいじくる必要はありません。次のようにn桁に丸めることもできますpython -c "print(round($num, $n))"
6

私はそのソリューションとかなりきちんと何かをするために管理し、私の問題は、私は次のように思いついたように、この場合には、私はいつもラウンド、私は必要な取得していない、0.5であった:echo 5 | python -c "print int(round((float(raw_input())/2)-0.5))"(切り上げとなります場合は+ -ラウンドダウン)。
ヤロン

4

これが純粋なBCソリューションです。丸め規則:+/- 0.5で、ゼロから切り捨てます。

探しているスケールを$ result_scaleに入れます。あなたの数学は、bcコマンドリストの$ MATHにあるはずです。

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 ここで説明されているよう :)
アクエリアスパワー

これを「ベストアンサー」にノミネートします。
マークタムスキー

2

私はそれが古い質問であることを知っていますが、「if」やブランチのない純粋な「bc」ソリューションがあります:

#!/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' 5またはbcr '0.666666' 2->(式の後にスケールが続く)のように使用します

bc(C / C ++など)では、計算に論理式を混在させることが許可されているため、これが可能です。式((t>0)-(t<0))/2)は 't'の符号に応じて+/- 0.5に評価されるため、丸めに正しい値を使用します。


これはきれいに見えbcr "5 / 2" 0ますが、2ではなくを返します3。何か不足していますか?
ブルージェイ

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()自由に使える組み込み関数はないので(コーディングしない限り)、引数が負の場合は引数を単純に否定します。

さらに、をパラメーターとして使用することは非常に面倒であることがわかりました(私のソリューションのため、被除数と除数に個別にアクセスできる必要があります)。これが、私のスクリプトに追加の3番目のパラメーターがある理由です。


最近の編集についての@muru:代わりにherestrings echo .. |、シェル演算、何のポイントecho $()ですか?これは簡単に説明できます。ヒア文字列には常に書き込み可能な/tmpディレクトリが必要です!また、私のソリューションは読み取り専用環境でも動作します。たとえば、デフォルトで/は常に書き込み可能ではない緊急ルートシェルなどです。だから私がそれをそのようにコーディングしたのには正当な理由がありました。
構文エラー

ここにひも、同意します。しかし、echo $()いつ必要になるのでしょうか?それとインデントが私の編集を促し、ヒアストリングがちょうど起こりました。
ムル

@muruまあ、その最も意味のないケースはecho $()修正されるのが遅れていたので、実際には最後から2番目の行でした(笑)ヘッドアップをありがとう。少なくとも、これは今ではずっと良く見えます、私は否定できません。とにかく、私はこのロジックが大好きでした。たくさんのブール型のもの、余分な「if」が少ない(確かにコードが50%爆発する)。
構文エラー

1

bc関数内で1つの値だけを丸める方法に対する純粋な答えを探していますが、ここに純粋な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..."

基本的に、これは小数点を慎重に抽出し、すべてを1,000億(10¹⁰、10**10in bash)で乗算し、精度と丸めを調整し、実際の除算を実行して適切な大きさに分割し、小数点を再挿入します。

ステップバイステップ:

embiggen()関数は、に引数の切り捨て整数フォームを割り当てる$intとのドットの後に番号を保存します$fraction。小数桁数はに記載されてい$precisionます。数学は、10¹⁰を連結して乗算$int$fraction、それを調整して精度に一致させます(たとえば、embiggen 48.8610¹⁰×4886/100になり、488,600,000,000になります488600000000)。

100分の1の最終精度が必要なので、最初の数値に100を掛け、丸めのために5を加算してから、2番目の数値を除算します。この割り当てにより$answer、最終的な回答の100倍になります。

次に、小数点を追加する必要があります。最後の2桁$int$answer除外するために新しい値を割り当ててからecho、ドットと既に処理されて$answerいる$int値を除外します。(これがコメントのように見える構文強調表示バグを気にしないでください)

(バシズム:べき乗はPOSIXではないため、これはバシズムです。純粋なPOSIXソリューションでは、10の累乗を使用するのではなく、ループをゼロに追加する必要があります。


zshシェルとして使用する主な理由の1つは、浮動小数点演算をサポートしていることです。この質問の解決策は、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の文字列を出力します。フレーム= 1/75秒。

(フレームは、スタジオで使用されるSMPTEコードから取得されます。)


A: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は、スケールを小数点以下2桁に制限します。

これを何度も行う必要がある場合、この方法は選択した答えよりもはるかに効率的です:

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.