Bash'da kayan nokta bölümünü nasıl kullanabilirim?


242

Bir Bash betiğinde iki görüntü genişliğini bölmeye çalışıyorum, ancak bash bana 0sonuç veriyor :

RESULT=$(($IMG_WIDTH/$IMG2_WIDTH))

Bash rehberini inceledim ve kullandıkları bctüm örneklerde kullanmam gerektiğini biliyorum bc. İçinde echoaynı şeyi koymaya çalıştım SCALEama işe yaramadı.

İşte öğreticilerde bulduğum örnek:

echo "scale=2; ${userinput}" | bc 

Bash'in bana bir şamandıra vermesini nasıl sağlayabilirim 0.5?


9
Senaryonuzda kayan nokta aritmetiği yapmaya çalışan herkes için bir yorum, kendinize sorun: gerçekten kayan nokta aritmetiğine ihtiyacım var mı? bazen gerçekten olmadan iyi geçinebilirsin. Bkz. Örneğin BashFAQ / 022'nin son kısmı .
gniourf_gniourf

Yanıtlar:


242

Yapamazsın. bash sadece tamsayılar yapar; Eğer gerekir gibi bir alete temsilci bc.


8
cevabı SONUÇ değişkenine koymak için bc gibi bir aracı nasıl devredebilirim?
Medya Gh

2
yani VAR = $ (echo "scale = 2; $ (($ IMG_WIDTH / $ IMG2_WIDTH))" | bc)?
Medya Gh

54
@Shevin VAR=$(echo "scale=2; $IMG_WIDTH/$IMG2_WIDTH" | bc)veya VAR=$(bc <<<"scale=2;$IMG_WIDTH/$IMG2_WIDTH")$ (()) olmadan (çift parantez); komuta çalıştırmadan önce bash tarafından genişletildi
Nahuel Fouilleul

4
Doğru, ancak awk genellikle sistemde zaten yüklüdür.
CMCDragonkai

9
@NahuelFouilleul Burada en iyi cevaba sahipsiniz. Gerçekten kendi cevabı olmalı ve cevap olarak kabul edilmelidir. Bu belirli satır inanılmaz faydalı oldu: VAR = $ (bc <<< "scale = 2; $ IMG_WIDTH / $ IMG2_WIDTH")
David

197

Bunu yapabilirsiniz:

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

GÜNCELLEME 20130926 : şunları kullanabilirsiniz:

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

8
... ve birçok rakam ekler. En azından makinemde bu 33.3333333333333333333333 verirken, ilk 33.33 verir.
Andreas Spindler

12
@AndreasSpindler Eski bir yazı türü, ancak herkesin bilmek istediği durumlarda, bu, örneğin scale komutu uygulanarak değiştirilebilir. bc -l <<< 'scale=2; 100/3'
Martie

Scale = 0 kullanarak bash'da daha sonra kullanmak üzere bir tamsayı almayı umuyorsanız dikkat edin. V1.06.95'ten itibaren, bc, herhangi bir nedenle, girdi sayıları ondalık bir kısma sahip olduğunda ölçek değişkenini yok sayar. Belki bu dokümanlarda, ama bulamadım. Deneyin: echo $ (bc -l <<< 'ölçeği = 0; 1 * 3.3333')
Greg Bell

1
@GregBell Man sayfası diyor Unless specifically mentioned the scale of the result is the maximum scale of the expressions involved.Ve /operatör için ek bir not var :The scale of the result is the value of the variable scale.
psmith

2
Teşekkürler @psmith. İlginçtir ki / için "sonucun ölçeği değişken ölçeğin değeridir" der, ancak çarpma için böyle değildir. Daha iyi örneklerim: bc <<< 'scale=1; 1*3.00001' ölçek bir nedenden dolayı gerçekten 5, bc <<< 'scale=1; 1/3.000001' ölçek 1. İlginçtir, 1 sete bölmek düz: bc <<< 'scale=1; 1*3.00001/1'ölçek 1
Greg Bell

136

darbe

Başkaları tarafından belirtildiği gibi, bashkayan nokta aritmetiğini desteklemez, ancak sabit bir ondalık hile ile taklit edebilirsiniz, örneğin iki ondalık ile:

echo $(( 100 * 1 / 3 )) | sed 's/..$/.&/'

Çıktı:

.33

Benzer ama daha özlü bir yaklaşım için Nilfred'in cevabına bakınız .

Alternatifler

Bahsedilen bcve awkalternatiflerin yanı sıra aşağıdakiler de vardır:

clisp

clisp -x '(/ 1.0 3)'

temizlenmiş çıktı ile:

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

veya stdin yoluyla:

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

dC

echo 2k 1 3 /p | dc

dahi cli hesap makinesi

echo 1/3.0 | genius

ghostscriptin

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

gnuplot

echo 'pr 1/3.' | gnuplot

jq

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

Veya:

jq -n 1/3

ksh

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

lua

lua -e 'print(1/3)'

veya stdin yoluyla:

echo 'print(1/3)' | lua

maxima

echo '1/3,numer;' | maxima

temizlenmiş çıktı ile:

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

düğüm

echo 1/3 | node -p

oktav

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

temizlenmiş çıktı ile:

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

yakut

echo print 1/3.0 | ruby

wcalc

echo 1/3 | wcalc

Temizlenmiş çıktı ile:

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

zsh

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

birimler

units 1/3

Kompakt çıkışlı:

units --co 1/3

Diğer kaynaklar

Stéphane Chazelas benzer bir soru cevap Unix.SX. üzerinde aşırı


9
Mükemmel cevap. Sorunun birkaç yıl sonra yayınlandığını biliyorum, ancak kabul edilen cevap olmayı daha hak ediyor.
Brian Cline

2
Zsh'iniz varsa, betiğinizi Bash yerine zsh'de yazmayı düşünmeye değer
Andrea Corbellini

Gnuplot için başparmak yukarı :)
andywiecko

Güzel liste. Özellikle echo 1/3 | node -pkısa.
Johnny Wong

39

Marvin cevabını biraz geliştirmek:

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

bc her zaman kurulu paket olarak gelmez.


4
Awk betiğinin exitgiriş akışından okumasını önlemek için bir a'ya ihtiyacı vardır . Ayrıca -veğik kürdan sendromunu önlemek için awk bayraklarını kullanmanızı öneririm . Yani:RESULT=$(awk -v dividend="${IMG_WIDTH}" -v divisor="${IMG2_WIDTH}" 'BEGIN {printf "%.2f", dividend/divisor; exit(0)}')
aecolley

2
Bunu yapmak için daha awkish yolu giriş akımından argümanları okumak olacaktır: RESULT=$(awk '{printf("result= %.2f\n",$1/$2)}' <<<" $IMG_WIDTH $IMG2_WIDTH ".
jmster

1
bcPOSIX'in bir parçasıdır, genellikle önceden yüklenir.
fuz

Bu windows 7 git bash kullanarak benim için çalıştı ... teşekkürler :)
Canavar

31

Bc -lseçeneğini (L harfi) seçeneğiyle kullanabilirsiniz.

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

4
Eğer -lsistemime dahil etmezsem, bc kayan noktalı matematik yapmaz.
starbeamrainbowlabs

28

BC'ye alternatif olarak, betiğinizde awk kullanabilirsiniz.

Örneğin:

echo "$IMG_WIDTH $IMG2_WIDTH" | awk '{printf "%.2f \n", $1/$2}'

Yukarıda "% .2f" printf fonksiyonuna ondalık basamaktan sonra iki basamaklı bir kayan nokta sayısı döndürmesini söyler. Awk değişkenler üzerinde düzgün çalıştığı için değişkenleri alan olarak göstermek için yankı kullandım. "$ 1" ve "$ 2", awk'ye giren birinci ve ikinci alanları ifade eder.

Ve sonucu kullanarak başka bir değişken olarak saklayabilirsiniz:

RESULT = `echo ...`

Mükemmel! Teşekkürler. Bu, bc'nin mevcut olmadığı gömülü ortam için yararlıdır. Bana biraz derleme zamanı kazandın.
enthusiasticgeek

19

Kayan nokta matematiği de dahil olmak üzere pek çok güzel özellik ile (neredeyse) bir bash süper seti olan zsh'ı denemek için mükemmel bir zaman. Örneğinizin zsh cinsinden örneği:

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

Bu yazı size yardımcı olabilir: bash - Günlük kullanım için zsh'a geçmeye değer mi?


3
Ben büyük bir zsh hayranıyım ve son 4 yıldır kullanıyorum, ancak interaktif kullanım burada iyi bir vurgu. Zsh gerektiren bir komut dosyası, genellikle standart olmayan, ne yazık ki (adil olmak, belki de sorun değil, OP nasıl kullanılacağını söylemedi) genellikle çeşitli makineler arasında çok taşınabilir olmayacaktır.
Brian Cline

17

Şamandıra, sabit ondalık mantığın kullanıldığı bir zamandan önce:

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

Son satır bir bashim, bash kullanmıyorsa, bunun yerine bu kodu deneyin:

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

Kodun arkasındaki mantık: 2 ondalık sayı elde etmek için bölmeden önce 100 ile çarpın.


5

Tercihinizin varyantını bulduysanız, onu bir işleve de sarabilirsiniz.

Burada bazı bashizmi div fonksiyonuna sarıyorum:

Bir astar:

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;}

Veya çok satırlı:

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
}

Şimdi fonksiyona sahipsiniz

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

ve onu gibi kullan

> div 1 2
.50

> div 273 123 5
2.21951

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

GÜNCELLEME Ben bash için kayan nokta sayıları ile temel işlemleri sağlayan küçük bir komut dosyası ekledi:

Kullanımı:

> 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

Ve işte senaryo:

#!/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
local _d=${3:-2}daha basit
KamilCuk

4

Gerçekten kayan nokta değil, ama bc'nin bir çağrılmasıyla birden fazla sonuç veren bir şey istiyorsanız ...

source /dev/stdin <<<$(bc <<< '
d='$1'*3.1415926535897932384626433832795*2
print "d=",d,"\n"
a='$1'*'$1'*3.1415926535897932384626433832795
print "a=",a,"\n"
')

echo bc radius:$1 area:$a diameter:$d

yarıçapı 1 $ olan bir dairenin alanını ve çapını hesaplar


4

Bc'yi kullanamayacağınız senaryolar vardır, çünkü meşgul kutusunun veya gömülü sistemlerin bazı kesilmiş sürümlerinde olduğu gibi mevcut olmayabilir. Her durumda dış bağımlılıkları sınırlamak her zaman yapmak için iyi bir şeydir, bu nedenle bölünen sayıya (pay) her zaman sıfır ekleyebilir, bu da 10'luk bir güçle çarpmakla aynıdır (10'a göre bir güç seçmelisiniz). bölme çıktısını bir tamsayı haline getirecektir). Bu tamsayıyı bir dize olarak ele aldığınızda ve ondalık noktasını (sağdan sola hareket ettirerek), sayıyı çarptığınız onun gücüne eşit sayıda konumlandırın. Bu, yalnızca tamsayı sayıları kullanarak kayan nokta sonuçları elde etmenin basit bir yoludur.


1
Busybox'ta bile Awk var. Belki burada daha belirgin bir Awk cevabı olmalı.
tripleee

4

Bash'te kayan nokta bölümünü kullanamasanız da sabit nokta bölümünü kullanabilirsiniz. Yapmanız gereken tek şey tamsayılarınızı 10'luk bir güçle çarpmak ve sonra tamsayı kısmını bölmek ve kesirli kısmı almak için bir modulo işlemi kullanmaktır. Gerektiği gibi yuvarlama.

#!/bin/bash

n=$1
d=$2

# because of rounding this should be 10^{i+1}
# where i is the number of decimal digits wanted
i=4
P=$((10**(i+1)))
Pn=$(($P / 10))
# here we 'fix' the decimal place, divide and round tward zero
t=$(($n * $P / $d + ($n < 0 ? -5 : 5)))
# then we print the number by dividing off the interger part and
# using the modulo operator (after removing the rounding digit) to get the factional part.
printf "%d.%0${i}d\n" $(($t / $P)) $(((t < 0 ? -t : t) / 10 % $Pn))

4

eski olduğunu biliyorum, ama çok cazip. yani, cevap: yapamazsın ... ama yapabilirsin. Hadi bunu deneyelim:

$IMG_WIDTH=1024
$IMG2_WIDTH=2048

$RATIO="$(( IMG_WIDTH / $IMG2_WIDTH )).$(( (IMG_WIDTH * 100 / IMG2_WIDTH) % 100 ))

noktadan sonra 2 basamak alırsınız, kesilir (aşağıya yuvarlama, haha) saf bash (diğer işlemleri başlatmaya gerek yoktur). Tabii ki, noktadan sonra sadece bir basamak gerekiyorsa 10 ile çarpın ve modulo 10 yapın.

bu ne yapar:

  • ilk $ ((...)) tamsayı bölme yapar;
  • second $ ((...)), 100 kat daha büyük bir şey üzerinde tamsayı bölme yapar, temel olarak 2 basamağınızı noktanın soluna hareket ettirir, sonra (%) modulo yaparak sadece bu 2 basamağı alır.

bonus parça : bc sürümü x 1000 dizüstü bilgisayarımda 1,8 saniye, saf bash ise 0,016 saniye sürdü.


3

Bash'da kayan nokta hesaplamaları nasıl yapılır:

En çok oylanan örneklerden biri gibi , komutla " here strings " ( <<<) kullanmak yerine , burada en sevdiğim kayan nokta örneğim , doğrudan man sayfalarının bölümünden ( kılavuz sayfalara bakın).bcbcEXAMPLESbcman bc

Başlamadan önce, pi için bir denklemin: olduğunu bilin pi = 4*atan(1). a()Aşağıda bcmatematik işlevi atan().

  1. Bu , bu durumda adı verilen bir değişkenin içine - bir bash değişken içine kayan nokta hesaplama sonucunu saklamak için nasılpi . Not scale=10bu durumda 10 hassasiyet ondalık basamak sayısını belirler. Bu yerden sonraki ondalık basamaklar kesilir .

    pi=$(echo "scale=10; 4*a(1)" | bc -l)
  2. Şimdi, bu değişkenin değerini de basan tek bir kod satırına sahip olmak için, echokomutu aşağıdaki gibi bir takip komutu olarak sonuna ekleyin . Komut verildiğinde , 10 ondalık basamaktaki kesmeyi not edin :

    pi=$(echo "scale=10; 4*a(1)" | bc -l); echo $pi
    3.1415926532
  3. Sonunda, biraz yuvarlayalım. Burada printfişlevi 4 ondalık basamağa yuvarlamak için kullanacağız . 3.14159...Şimdi mermi olduğunu unutmayın 3.1416. Yuvarladığımızdan, artık scale=1010 ondalık basamağa kısaltmak için kullanmamıza gerek yok , bu yüzden o parçayı kaldıracağız. İşte son çözüm:

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

İşte yukarıdaki tekniklerin gerçekten harika bir uygulaması ve demosu: çalışma süresini ölçme ve yazdırma.

Not dt_mingelen yuvarlak alır 0.01666666666...için 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

İlişkili:


1
ayrıntılı örnekleri ile sağlam cevap ve vakaları kullanır ve printf kullanarak yuvarlak
downvoteit


2

Kabul edilen cevapla yüzdeleri hesaplamaya çalışan, ancak hassasiyeti kaybedenler için:

Bunu çalıştırırsanız:

echo "scale=2; (100/180) * 180" | bc

Sadece elde edersiniz 99.00, ki bu hassasiyet kaybeder.

Bu şekilde çalıştırırsanız:

echo "result = (100/180) * 180; scale=2; result / 1" | bc -l

Şimdi anladın 99.99.

Çünkü sadece baskı anında ölçeklendiriyorsunuz.

Buraya bakın


1

** Bash / kabukta enjeksiyon güvenli kayan nokta matematiği **

Not: Bu cevabın odak noktası, bash (veya diğer kabuklarda) matematik yapmak için enjeksiyon güvenli çözüm için fikirler sağlamaktır. Tabii ki, aynı, gelişmiş dize işleme, vb yapmak için küçük ayarlama ile kullanılabilir.

Sunulan çözümün çoğu, harici veriler (değişkenler, dosyalar, komut satırı, ortam değişkenleri) kullanarak anında küçük bir komut dosyası oluşturur. Harici giriş motora kötü amaçlı kod enjekte etmek için kullanılabilir, birçoğu

Aşağıda, temel matematik hesaplaması yapmak için çeşitli dili kullanma konusunda bir karşılaştırma bulunmaktadır; burada sonuç kayan noktadır. A + B * 0.1'i (kayan nokta olarak) hesaplar.

Tüm çözüm girişimleri, bakımı son derece zor olan dinamik komut dosyaları oluşturmaktan kaçınır, Bunun yerine statik programı kullanır ve parametreleri belirtilen değişkene geçirir. Özel karakterlerle parametreleri güvenli bir şekilde ele alırlar - kod enjeksiyon olasılığını azaltır. İstisna, giriş / çıkış olanağı sağlamayan 'BC' dir

İstisna, herhangi bir girdi / çıktı sağlamayan 'bc' dir, tüm veriler stdin'deki programlar aracılığıyla gelir ve tüm çıktılar stdout'a gider. Tüm hesaplama, yan etkiye izin vermeyen (dosyaları açma vb.) Bir sanal alanda yürütülür. Teoride, tasarım ile enjeksiyon güvenli!

A=5.2
B=4.3

# Awk: Map variable into awk
# Exit 0 (or just exit) for success, non-zero for error.
#
awk -v A="$A" -v B="$B" 'BEGIN { print A + B * 0.1 ; exit 0}'

# Perl
perl -e '($A,$B) = @ARGV ; print $A + $B * 0.1' "$A" "$B"

# Python 2
python -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print a+b*0.1' "$A" "$B"

# Python 3
python3 -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print(a+b*0.1)' "$A" "$B"

# BC
bc <<< "scale=1 ; $A + $B * 0.1"

keyfi argümanlarla python3 için: python3 -c 'import sys ; *a, = map(float, sys.argv[1:]) ; print(a[0] + a[1]*0.1 + a[2])' "$A" "$B""4200.0" ==> 4205.63
WiringHarness

0

awk komutu: -F = alan ayırıcı == +

echo "2.1+3.1" |  awk -F "+" '{print ($1+$2)}'
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.