कैसे बैश में 2 महत्वपूर्ण अंकों के साथ फ्लोटिंग पॉइंट नंबर को प्रारूपित करें?


17

मैं अस्थायी बिंदु संख्या को बश में दो महत्वपूर्ण अंकों के साथ प्रिंट करना चाहता हूं (शायद awk, bc, dc, perl आदि जैसे सामान्य टूल का उपयोग करके)।

उदाहरण:

  • 76543 को 76000 के रूप में मुद्रित किया जाना चाहिए
  • 0.0076543 को 0.0076 के रूप में मुद्रित किया जाना चाहिए

दोनों मामलों में महत्वपूर्ण अंक 7 हैं और 6. मैंने समान समस्याओं के लिए कुछ उत्तर पढ़े हैं:

शेल में फ्लोटिंग पॉइंट संख्या गोल कैसे करें?

अस्थायी बिंदु चर की शुद्धता सीमित करें

लेकिन उत्तर महत्वपूर्ण अंकों के बजाय दशमलव स्थानों की संख्या (जैसे bcकमांड scale=2या printfकमांड के साथ %.2f) को सीमित करने पर ध्यान केंद्रित करते हैं ।

क्या वास्तव में 2 महत्वपूर्ण अंकों के साथ संख्या को प्रारूपित करने का एक आसान तरीका है या क्या मुझे अपना कार्य लिखना है?

जवाबों:


13

पहले जुड़े सवाल के इस जवाब में अंत में लगभग फेंक दिया गया है:

%gमहत्वपूर्ण अंकों की एक निर्दिष्ट संख्या में गोलाई के लिए भी देखें ।

तो आप बस लिख सकते हैं

printf "%.2g" "$n"

(लेकिन दशमलव विभाजक और स्थान पर नीचे अनुभाग देखें, और ध्यान दें कि गैर-बैश printfको समर्थन की आवश्यकता नहीं है ) %fऔर %g

उदाहरण:

$ printf "%.2g\n" 76543 0.0076543
7.7e+04
0.0077

बेशक, अब आपके पास शुद्ध दशमलव के बजाय मंटिसा-प्रतिपादक प्रतिनिधित्व है, इसलिए आप वापस परिवर्तित करना चाहेंगे:

$ printf "%0.f\n" 7.7e+06
7700000

$ printf "%0.7f\n" 7.7e-06
0.0000077

यह सब एक साथ रखना, और इसे एक समारोह में लपेटना:

# Function round(precision, number)
round() {
    n=$(printf "%.${1}g" "$2")
    if [ "$n" != "${n#*e}" ]
    then
        f="${n##*e-}"
        test "$n" = "$f" && f= || f=$(( ${f#0}+$1-1 ))
        printf "%0.${f}f" "$n"
    else
        printf "%s" "$n"
    fi
}

(नोट - यह फ़ंक्शन पोर्टेबल (POSIX) शेल में लिखा गया है, लेकिन यह मान लेता है कि printfफ्लोटिंग-पॉइंट वार्तालाप को हैंडल करता है। बैश में एक बिल्ट-इन printfहै, इसलिए आप यहाँ ठीक हैं, और GNU कार्यान्वयन भी काम करता है, इसलिए अधिकांश GNU / लिनक्स सिस्टम सुरक्षित रूप से डैश का उपयोग कर सकते हैं)।

परीक्षण के मामलों

radix=$(printf %.1f 0)
for i in $(seq 12 | sed -e 's/.*/dc -e "12k 1.234 10 & 6 -^*p"/e' -e "y/_._/$radix/")
do
    echo $i "->" $(round 2 $i)
done

परीक्षण के परिणाम

.000012340000 -> 0.000012
.000123400000 -> 0.00012
.001234000000 -> 0.0012
.012340000000 -> 0.012
.123400000000 -> 0.12
1.234 -> 1.2
12.340 -> 12
123.400 -> 120
1234.000 -> 1200
12340.000 -> 12000
123400.000 -> 120000
1234000.000 -> 1200000

दशमलव विभाजक और स्थान पर एक नोट

ऊपर काम करने वाले सभी मानते हैं कि मूलांक वर्ण (जिसे दशमलव विभाजक के रूप में भी जाना जाता है) ., अधिकांश अंग्रेजी स्थानों में है। अन्य स्थानों के ,बजाय उपयोग करते हैं, और कुछ गोले में एक अंतर्निहित है printfजो स्थानीय का सम्मान करता है। इन गोले में, आपको मूलांक वर्ण LC_NUMERIC=Cके उपयोग को बाध्य करने के लिए सेट करने की आवश्यकता हो सकती है ., या /usr/bin/printfअंतर्निहित संस्करण के उपयोग को रोकने के लिए लिख सकते हैं । यह बाद इस तथ्य से जटिल है कि (कम से कम कुछ संस्करण) हमेशा तर्कों का उपयोग करते हुए लगते हैं ., लेकिन वर्तमान स्थानीय सेटिंग्स का उपयोग करके प्रिंट करें।


@ स्टीफन चेज़लस, आपने बैशवाद को हटाने के बाद मेरी सावधानी से परीक्षण की गई पॉसिक्स शेल शेबबैंग को वापस बैश में क्यों बदल दिया? आपकी टिप्पणी का उल्लेख है %f/ %g, लेकिन यह printfतर्क है, और किसी को printfPOSIX शेल होने के लिए POSIX की आवश्यकता नहीं है । मुझे लगता है कि आपको वहां संपादित करने के बजाय टिप्पणी करनी चाहिए थी।
टोबी स्पाइट

printf %gPOSIX स्क्रिप्ट में उपयोग नहीं किया जा सकता है। यह सच है कि यह printfउपयोगिता के लिए नीचे है , लेकिन यह उपयोगिता अधिकांश गोले में निर्मित है। ओपी को बैश के रूप में टैग किया गया है, इसलिए बैश शेबबैंग का उपयोग करके प्रिंटफ़ प्राप्त करने का एक आसान तरीका है जो% g का समर्थन करता है। अन्यथा, आपको अपना प्रिंटफ़ (या shयदि आपकाprintf%g
बिल्ट का प्रिंटफ़ बिल्टिन

dashएक बेसिन है printf(जो समर्थन करता है %g)। जीएनयू सिस्टम पर, mkshशायद इन दिनों एकमात्र शेल है जिसमें एक बिलिन नहीं होगा printf
स्टीफन चेजलस

आपके सुधारों के लिए धन्यवाद - मैंने शेबंग को निकालने के लिए संपादित किया है (क्योंकि प्रश्न टैग किया गया है bash) और इसमें से कुछ को नोटों में बदल दिया है - क्या यह अभी सही लगता है?
टोबी स्पाइट

1
यदि अनुगामी अंक शून्य हैं, तो अफसोस की बात यह है कि अंकों की सही संख्या नहीं है। उदाहरण के लिए printf "%.3g\n" 0.4000.4 नहीं 0.400 देता है
फेयरस्की

4

टी एल; डॉ

बस 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

0

यदि आपके पास पहले से ही एक स्ट्रिंग के रूप में संख्या है, जो कि "3456" या "0.003756" के रूप में है, तो आप संभवतः इसे केवल स्ट्रिंग हेरफेर का उपयोग करके कर सकते हैं। निम्नलिखित मेरे सिर के ऊपर से है, और अच्छी तरह से परीक्षण नहीं किया गया है, और sed का उपयोग करता है, लेकिन विचार करें:

f() {
    local A="$1"
    local B="$(echo "$A" | sed -E "s/^-?0?\.?0*//")"
    local C="$(eval echo "${A%$B}")"
    if ((${#B} > 2)); then
        D="${B:0:2}"
    else
        D="$B"
    fi
    echo "$C$D"
}

जहां मूल रूप से आप स्ट्रिप बंद करते हैं और शुरू में किसी भी "-0.000" सामान को बचाते हैं, तो बाकी हिस्सों पर एक साधारण प्रतिस्थापन ऑपरेशन का उपयोग करें। उपरोक्त के बारे में एक चेतावनी यह है कि कई प्रमुख 0 हटाए नहीं गए हैं। मैं इसे एक अभ्यास के रूप में छोड़ दूँगा।


1
एक अभ्यास से अधिक: यह शून्य के साथ पूर्णांक को पैड नहीं करता है, और न ही यह एम्बेडेड दशमलव बिंदु के लिए खाता है। लेकिन हां, यह इस दृष्टिकोण का उपयोग करने योग्य है (हालांकि यह प्राप्त करना ओपी के कौशल से परे हो सकता है)।
थॉमस डिकी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.