टी एल; डॉ
बस sigf
अनुभाग में फ़ंक्शन को कॉपी और उपयोग करें A reasonably good "significant numbers" function:
। यह डैश के साथ काम करने के लिए (इस उत्तर में सभी कोड के रूप में) लिखा है ।
यह अंकों के साथ Nprintf
के पूर्णांक वाले हिस्से को सन्निकटन देगा $sig
।
दशमलव विभाजक के बारे में।
प्रिंटफ के साथ हल करने के लिए पहली समस्या "दशमलव चिह्न" का प्रभाव और उपयोग है, जो कि यूएस में एक बिंदु है, और डे में एक अल्पविराम (उदाहरण के लिए) है। यह एक समस्या है क्योंकि कुछ लोकेल (या शेल) के लिए जो काम करता है वह कुछ अन्य लोकेल के साथ विफल हो जाएगा। उदाहरण:
$ 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
एक सामान्य (और गलत समाधान) LC_ALL=C
प्रिंटफ कमांड के लिए सेट करना है। लेकिन वह दशमलव चिह्न को एक निश्चित दशमलव बिंदु पर सेट करता है। उन स्थानों के लिए जहां अल्पविराम (या अन्य) सामान्य उपयोग किया जाने वाला वर्ण है जो एक समस्या है।
इसका समाधान यह है कि शेल के लिए स्क्रिप्ट के अंदर यह पता लगाना है कि लोकेल दशमलव विभाजक क्या है। यह काफी सरल है:
$ printf '%1.1f' 0
0,0 # for a comma locale (or shell).
शून्य हटाना:
$ dec="$(IFS=0; printf '%s' $(printf '%.1f'))"; echo "$dec"
, # for a comma locale (or shell).
उस मूल्य का उपयोग परीक्षण की सूची के साथ फाइल को बदलने के लिए किया जाता है:
sed -i 's/[,.]/'"$dec"'/g' infile
यह किसी भी शेल या लोकेल पर रन को स्वचालित रूप से वैध बनाता है।
कुछ मूल बातें।
फॉर्मेट %.*e
या यहां तक %.*g
कि प्रिंटफ के साथ फॉर्मेट की जाने वाली संख्या में कटौती करना सहज होना चाहिए । का उपयोग कर के बीच मुख्य अंतर %.*e
या %.*g
कैसे वे अंक गिनती है। एक पूरी गिनती का उपयोग करता है, दूसरे को गिनती कम चाहिए 1:
$ printf '%.*e %.*g' $((4-1)) 1,23456e0 4 1,23456e0
1,235e+00 1,235
इसने 4 महत्वपूर्ण अंकों के लिए अच्छा काम किया।
अंकों की संख्या को संख्या से काट दिए जाने के बाद, हमें 0 से भिन्न प्रतिपादकों के साथ संख्याओं को प्रारूपित करने के लिए एक अतिरिक्त चरण की आवश्यकता है (जैसा कि यह ऊपर था)।
$ N=$(printf '%.*e' $((4-1)) 1,23456e3); echo "$N"
1,235e+03
$ printf '%4.0f' "$N"
1235
यह सही ढंग से काम करता है। पूर्णांक भाग (दशमलव चिह्न के बाईं ओर) की गणना केवल घातांक ($ exp) का मान है। दशमलव संख्या की गणना महत्वपूर्ण अंकों ($ 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"
जैसा कि f
प्रारूप के लिए अभिन्न अंग की कोई सीमा नहीं है, वास्तव में इसे स्पष्ट रूप से घोषित करने की कोई आवश्यकता नहीं है और यह (सरलता) कोड है:
a=$((exp<sig?sig-exp:0)) ### count of decimal characters.
printf '%0.*f' "$a" "$N"
प्रथम परीक्षण।
एक पहला कार्य जो इसे और अधिक स्वचालित तरीके से कर सकता है:
# 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.
}
यह पहला प्रयास कई संख्याओं के साथ काम करता है, लेकिन उन संख्याओं के साथ विफल हो जाएगा जिनके लिए उपलब्ध अंकों की मात्रा अनुरोधित महत्वपूर्ण संख्या से कम है और प्रतिपादक -4 से कम है:
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
इसमें कई शून्य जोड़े जाएंगे जिनकी आवश्यकता नहीं है।
दूसरा परीक्षण।
यह हल करने के लिए कि हमें घातांक और किसी अनुगामी शून्य की सफाई करनी है। तब हम उपलब्ध अंकों की प्रभावी लंबाई प्राप्त कर सकते हैं और उसके साथ काम कर सकते हैं:
# 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.
}
हालाँकि, वह फ़्लोटिंग पॉइंट गणित का उपयोग कर रहा है, और "फ़्लोटिंग पॉइंट में कुछ भी सरल नहीं है": मेरे नंबर क्यों नहीं जोड़ते हैं?
लेकिन "फ्लोटिंग पॉइंट" में कुछ भी सरल नहीं है।
printf "%.2g " 76500,00001 76500
7,7e+04 7,6e+04
तथापि:
printf "%.2g " 75500,00001 75500
7,6e+04 7,6e+04
क्यों?:
printf "%.32g\n" 76500,00001e30 76500e30
7,6500000010000000001207515928855e+34
7,6499999999999999997831226199114e+34
और, भी, कमान printf
कई गोले का एक अंतर्निहित है। शेल के साथ
क्या printf
प्रिंट बदल सकते हैं:
$ 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
एक बहुत अच्छा "महत्वपूर्ण संख्या" फ़ंक्शन:
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"
}
और परिणाम हैं:
$ 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
/%g
, लेकिन यहprintf
तर्क है, और किसी कोprintf
POSIX शेल होने के लिए POSIX की आवश्यकता नहीं है । मुझे लगता है कि आपको वहां संपादित करने के बजाय टिप्पणी करनी चाहिए थी।