TL; DR
Sadece sigf
bölümündeki işlevi kopyalayıp kullanın A reasonably good "significant numbers" function:
. Kısa çizgi ile çalışmak için (bu cevaptaki tüm kodlar gibi) yazılmıştır .
Bu verecek printf
yaklaşma N tamsayı kısmı ile $sig
basamağı.
Ondalık ayırıcı hakkında.
Printf ile çözülmesi gereken ilk sorun, ABD'de bir nokta olan ve DE'de virgül olan "ondalık işareti" nin etkisi ve kullanımıdır (örneğin). Bu bir sorundur, çünkü bazı yerel ayar (veya kabuk) için işe yarayan diğer yerel ayarlarla başarısız olur. Misal:
$ dash -c 'printf "%2.3f\n" 12.3045'
12.305
$ ksh -c 'printf "%2.3f\n" 12.3045'
ksh: printf: 12.3045: arithmetic syntax error
ksh: printf: 12.3045: arithmetic syntax error
ksh: printf: warning: invalid argument of type f
12,000
$ ksh -c 'printf "%2.2f\n" 12,3045'
12,304
Yaygın (ve yanlış bir çözüm) LC_ALL=C
printf komutu için ayarlanmıştır . Ancak bu, ondalık işaretini sabit bir ondalık noktaya ayarlar. Virgülün (veya başka bir karakterin) ortak kullanılan ve sorun olan karakter olduğu yerler için.
Çözüm yerel kod ondalık ayırıcı nedir çalıştıran kabuk için komut dosyası içinde bulmaktır. Bu oldukça basit:
$ printf '%1.1f' 0
0,0 # for a comma locale (or shell).
Sıfırları kaldırma:
$ dec="$(IFS=0; printf '%s' $(printf '%.1f'))"; echo "$dec"
, # for a comma locale (or shell).
Bu değer, dosyayı test listesiyle değiştirmek için kullanılır:
sed -i 's/[,.]/'"$dec"'/g' infile
Bu, herhangi bir kabuk veya yerel ayardaki işlemleri otomatik olarak geçerli kılar.
Bazı temel bilgiler.
Biçimlendirilecek sayıyı print %.*e
veya formatf ile kesmek sezgisel olmalıdır %.*g
. %.*e
Veya arasındaki temel fark %.*g
basamakları sayma şeklidir. Biri tam sayıyı kullanır, diğeri daha az saymaya ihtiyaç duyar 1:
$ printf '%.*e %.*g' $((4-1)) 1,23456e0 4 1,23456e0
1,235e+00 1,235
Bu 4 önemli basamak için iyi çalıştı.
Rakam sayısı rakamdan kesildikten sonra, sayıları 0'dan farklı üslerle (yukarıda olduğu gibi) biçimlendirmek için ek bir adıma ihtiyacımız var.
$ N=$(printf '%.*e' $((4-1)) 1,23456e3); echo "$N"
1,235e+03
$ printf '%4.0f' "$N"
1235
Bu doğru çalışıyor. Tamsayı bölümünün sayısı (ondalık işaretinin solunda) yalnızca üs değerinin ($ exp) değeridir. Gereken ondalık sayısı, ondalık ayırıcının sol tarafında zaten kullanılan basamak miktarından daha az önemli basamak sayısıdır ($ sig):
a=$((exp<0?0:exp)) ### count of integer characters.
b=$((exp<sig?sig-exp:0)) ### count of decimal characters.
printf '%*.*f' "$a" "$b" "$N"
Biçimin ayrılmaz bir parçası f
sınırsız olduğu için, aslında açıkça bildirmeye gerek yoktur ve bu (daha basit) kod çalışır:
a=$((exp<sig?sig-exp:0)) ### count of decimal characters.
printf '%0.*f' "$a" "$N"
İlk deneme.
Bunu daha otomatik bir şekilde yapabilen ilk işlev:
# Function significant (number, precision)
sig1(){
sig=$(($2>0?$2:1)) ### significant digits (>0)
N=$(printf "%0.*e" "$(($sig-1))" "$1") ### N in sci (cut to $sig digits).
exp=$(echo "${N##*[eE+]}+1"|bc) ### get the exponent.
a="$((exp<sig?sig-exp:0))" ### calc number of decimals.
printf "%0.*f" "$a" "$N" ### re-format number.
}
Bu ilk deneme birçok sayı ile çalışır, ancak kullanılabilir basamak miktarının istenen önemli sayıdan daha az olduğu ve üs -4'ten az olduğu sayılarda başarısız olur:
Number sig Result Correct?
123456789 --> 4< 123500000 >--| yes
23455 --> 4< 23460 >--| yes
23465 --> 4< 23460 >--| yes
1,2e-5 --> 6< 0,0000120000 >--| no
1,2e-15 -->15< 0,00000000000000120000000000000 >--| no
12 --> 6< 12,0000 >--| no
Gerekli olmayan birçok sıfır ekleyecektir.
İkinci deneme.
Bunu çözmek için üssün N'sini ve sondaki sıfırları temizlememiz gerekir. Sonra etkili basamak uzunluğunu elde edebilir ve bununla çalışabiliriz:
# Function significant (number, precision)
sig2(){ local sig N exp n len a
sig=$(($2>0?$2:1)) ### significant digits (>0)
N=$(printf "%+0.*e" "$(($sig-1))" "$1") ### N in sci (cut to $sig digits).
exp=$(echo "${N##*[eE+]}+1"|bc) ### get the exponent.
n=${N%%[Ee]*} ### remove sign (first character).
n=${n%"${n##*[!0]}"} ### remove all trailing zeros
len=$(( ${#n}-2 )) ### len of N (less sign and dec).
len=$((len<sig?len:sig)) ### select the minimum.
a="$((exp<len?len-exp:0))" ### use $len to count decimals.
printf "%0.*f" "$a" "$N" ### re-format the number.
}
Ancak, bu kayan nokta matematiğini kullanıyor ve "kayan noktada hiçbir şey basit değil ": Sayılarım neden toplanmıyor?
Ancak "kayan nokta" daki hiçbir şey basit değildir.
printf "%.2g " 76500,00001 76500
7,7e+04 7,6e+04
Ancak:
printf "%.2g " 75500,00001 75500
7,6e+04 7,6e+04
Neden?:
printf "%.32g\n" 76500,00001e30 76500e30
7,6500000010000000001207515928855e+34
7,6499999999999999997831226199114e+34
Ve ayrıca, komut printf
birçok merminin yerleşikidir. Kabuk ile
hangi printf
baskılar değişebilir:
$ dash -c 'printf "%.*f" 4 123456e+25'
1234560000000000020450486779904.0000
$ ksh -c 'printf "%.*f" 4 123456e+25'
1234559999999999999886313162278,3840
$ dash ./script.sh
123456789 --> 4< 123500000 >--| yes
23455 --> 4< 23460 >--| yes
23465 --> 4< 23460 >--| yes
1.2e-5 --> 6< 0.000012 >--| yes
1.2e-15 -->15< 0.0000000000000012 >--| yes
12 --> 6< 12 >--| yes
123456e+25 --> 4< 1234999999999999958410892148736 >--| no
Oldukça iyi bir "anlamlı sayılar" işlevi:
dec=$(IFS=0; printf '%s' $(printf '%.1f')) ### What is the decimal separator?.
sed -i 's/[,.]/'"$dec"'/g' infile
zeros(){ # create an string of $1 zeros (for $1 positive or zero).
printf '%.*d' $(( $1>0?$1:0 )) 0
}
# Function significant (number, precision)
sigf(){ local sig sci exp N sgn len z1 z2 b c
sig=$(($2>0?$2:1)) ### significant digits (>0)
N=$(printf '%+e\n' $1) ### use scientific format.
exp=$(echo "${N##*[eE+]}+1"|bc) ### find ceiling{log(N)}.
N=${N%%[eE]*} ### cut after `e` or `E`.
sgn=${N%%"${N#-}"} ### keep the sign (if any).
N=${N#[+-]} ### remove the sign
N=${N%[!0-9]*}${N#??} ### remove the $dec
N=${N#"${N%%[!0]*}"} ### remove all leading zeros
N=${N%"${N##*[!0]}"} ### remove all trailing zeros
len=$((${#N}<sig?${#N}:sig)) ### count of selected characters.
N=$(printf '%0.*s' "$len" "$N") ### use the first $len characters.
result="$N"
# add the decimal separator or lead zeros or trail zeros.
if [ "$exp" -gt 0 ] && [ "$exp" -lt "$len" ]; then
b=$(printf '%0.*s' "$exp" "$result")
c=${result#"$b"}
result="$b$dec$c"
elif [ "$exp" -le 0 ]; then
# fill front with leading zeros ($exp length).
z1="$(zeros "$((-exp))")"
result="0$dec$z1$result"
elif [ "$exp" -ge "$len" ]; then
# fill back with trailing zeros.
z2=$(zeros "$((exp-len))")
result="$result$z2"
fi
# place the sign back.
printf '%s' "$sgn$result"
}
Ve sonuçlar:
$ dash ./script.sh
123456789 --> 4< 123400000 >--| yes
23455 --> 4< 23450 >--| yes
23465 --> 4< 23460 >--| yes
1.2e-5 --> 6< 0.000012 >--| yes
1.2e-15 -->15< 0.0000000000000012 >--| yes
12 --> 6< 12 >--| yes
123456e+25 --> 4< 1234000000000000000000000000000 >--| yes
123456e-25 --> 4< 0.00000000000000000001234 >--| yes
-12345.61234e-3 --> 4< -12.34 >--| yes
-1.234561234e-3 --> 4< -0.001234 >--| yes
76543 --> 2< 76000 >--| yes
-76543 --> 2< -76000 >--| yes
123456 --> 4< 123400 >--| yes
12345 --> 4< 12340 >--| yes
1234 --> 4< 1234 >--| yes
123.4 --> 4< 123.4 >--| yes
12.345678 --> 4< 12.34 >--| yes
1.23456789 --> 4< 1.234 >--| yes
0.1234555646 --> 4< 0.1234 >--| yes
0.0076543 --> 2< 0.0076 >--| yes
.000000123400 --> 2< 0.00000012 >--| yes
.000001234000 --> 2< 0.0000012 >--| yes
.000012340000 --> 2< 0.000012 >--| yes
.000123400000 --> 2< 0.00012 >--| yes
.001234000000 --> 2< 0.0012 >--| yes
.012340000000 --> 2< 0.012 >--| yes
.123400000000 --> 2< 0.12 >--| yes
1.234 --> 2< 1.2 >--| yes
12.340 --> 2< 12 >--| yes
123.400 --> 2< 120 >--| yes
1234.000 --> 2< 1200 >--| yes
12340.000 --> 2< 12000 >--| yes
123400.000 --> 2< 120000 >--| yes
%f
/ ifadesinden bahsediyor%g
, ancak bu birprintf
argüman veprintf
bir POSIX kabuğuna sahip olmak için POSIX gerekmez . Bence orada düzenlemek yerine yorum yapmış olmalısın.