Jawaban untuk pertanyaan yang ditautkan pertama ini memiliki garis yang hampir dibuang di akhir:
Lihat juga %g
untuk pembulatan ke sejumlah digit signifikan.
Jadi Anda cukup menulis
printf "%.2g" "$n"
(tetapi lihat bagian di bawah ini pada pemisah desimal dan lokal, dan perhatikan bahwa non-Bash printf
tidak perlu mendukung %f
dan %g
).
Contoh:
$ printf "%.2g\n" 76543 0.0076543
7.7e+04
0.0077
Tentu saja, Anda sekarang memiliki representasi mantissa-eksponen daripada desimal murni, jadi Anda ingin mengonversi kembali:
$ printf "%0.f\n" 7.7e+06
7700000
$ printf "%0.7f\n" 7.7e-06
0.0000077
Menyatukan semua ini, dan membungkusnya dalam suatu fungsi:
# 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
}
(Catatan - fungsi ini ditulis dalam shell portabel (POSIX), tetapi mengasumsikan bahwa printf
menangani konversi floating-point. Bash memiliki built-in printf
yang tidak, jadi Anda baik-baik saja di sini, dan implementasi GNU juga berfungsi, sehingga sebagian besar GNU / Sistem Linux dapat menggunakan Dash dengan aman).
Uji kasus
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
Hasil tes
.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
Catatan tentang pemisah dan lokal desimal
Semua yang bekerja di atas mengasumsikan bahwa karakter radix (juga dikenal sebagai pemisah desimal) .
, seperti di sebagian besar lokal bahasa Inggris. Lokal lain menggunakan ,
sebagai gantinya, dan beberapa shell memiliki built-in printf
yang menghormati lokal. Dalam shell ini, Anda mungkin perlu mengatur LC_NUMERIC=C
untuk memaksa penggunaan .
sebagai karakter radix, atau menulis /usr/bin/printf
untuk mencegah penggunaan versi built-in. Yang terakhir ini diperumit oleh fakta bahwa (setidaknya beberapa versi) tampaknya selalu menguraikan argumen menggunakan .
, tetapi mencetak menggunakan pengaturan lokal saat ini.
%f
/%g
, tapi itulahprintf
argumennya, dan orang tidak perlu POSIXprintf
untuk memiliki shell POSIX. Saya pikir Anda seharusnya berkomentar daripada mengedit di sana.printf %g
tidak dapat digunakan dalam skrip POSIX. Memang benar itu keprintf
utilitas, tetapi utilitas itu dibangun di sebagian besar shell. OP ditandai sebagai bash, jadi menggunakan bash shebang adalah salah satu cara mudah untuk mendapatkan printf yang mendukung% g. Jika tidak, Anda perlu menambahkan asumsi printf Anda (atau printf bawaansh
jika Andaprintf
dibangun di sana) mendukung non-standar (tetapi cukup umum)%g
...dash
Memiliki builtinprintf
(yang mendukung%g
). Pada sistem GNU,mksh
mungkin satu-satunya shell hari ini yang tidak memiliki builtinprintf
.bash
) dan memindahkan beberapa ke catatan - apakah terlihat benar sekarang?printf "%.3g\n" 0.400
memberi 0,4 bukan 0,400TL; DR
Cukup salin dan gunakan fungsi
sigf
di bagian iniA reasonably good "significant numbers" function:
. Ada tertulis (karena semua kode dalam jawaban ini) bekerja dengan tanda hubung .Ini akan memberikan
printf
perkiraan ke bagian integer N dengan$sig
digit.Tentang pemisah desimal.
Masalah pertama yang harus diselesaikan dengan printf adalah efek dan penggunaan "tanda desimal", yang di AS adalah sebuah titik, dan di DE adalah koma (misalnya). Ini adalah masalah karena apa yang berfungsi untuk beberapa lokal (atau shell) akan gagal dengan beberapa lokal lainnya. Contoh:
Salah satu solusi umum (dan salah) adalah untuk mengatur
LC_ALL=C
perintah printf. Tapi itu menetapkan tanda desimal ke titik desimal tetap. Untuk lokal di mana koma (atau lainnya) adalah karakter yang umum digunakan yang menjadi masalah.Solusinya adalah mencari tahu di dalam skrip untuk shell menjalankannya apa pemisah desimal lokal. Itu cukup sederhana:
Menghapus nol:
Nilai itu digunakan untuk mengubah file dengan daftar tes:
Itu membuat proses pada shell atau lokal apa pun secara otomatis valid.
Beberapa dasar.
Ini harus intuitif untuk memotong nomor yang akan diformat dengan format
%.*e
atau bahkan%.*g
printf. Perbedaan utama antara menggunakan%.*e
atau%.*g
bagaimana mereka menghitung angka. Satu menggunakan penghitungan penuh, yang lain membutuhkan penghitungan kurang 1:Itu bekerja dengan baik untuk 4 digit signifikan.
Setelah jumlah digit telah dipotong dari angka, kita perlu langkah tambahan untuk memformat angka dengan eksponen yang berbeda dari 0 (seperti di atas).
Ini berfungsi dengan benar. Hitungan bilangan bulat (di sebelah kiri tanda desimal) hanya nilai eksponen ($ exp). Hitungan desimal yang dibutuhkan adalah jumlah digit signifikan ($ sig) dikurangi jumlah digit yang sudah digunakan di bagian kiri pemisah desimal:
Karena bagian integral dari
f
format tidak memiliki batas, sebenarnya tidak perlu mendeklarasikannya secara eksplisit dan kode ini (lebih sederhana) berfungsi:Percobaan pertama.
Fungsi pertama yang dapat melakukan ini dengan cara yang lebih otomatis:
Upaya pertama ini bekerja dengan banyak angka tetapi akan gagal dengan angka yang jumlah digit yang tersedia kurang dari jumlah signifikan yang diminta dan eksponen kurang dari -4:
Ini akan menambahkan banyak nol yang tidak diperlukan.
Uji coba kedua.
Untuk mengatasinya kita perlu membersihkan N dari eksponen dan angka nol di belakangnya. Kemudian kita bisa mendapatkan panjang digit efektif yang tersedia dan bekerja dengan itu:
Namun, itu menggunakan matematika titik mengambang, dan "tidak ada yang sederhana di titik mengambang": Mengapa angka saya tidak bertambah?
Tapi tidak ada dalam "floating point" yang sederhana.
Namun:
Mengapa?:
Dan, juga, perintahnya
printf
adalah builtin dari banyak shell.Apa yang
printf
dicetak dapat berubah dengan shell:Fungsi "angka signifikan" yang cukup baik:
Dan hasilnya adalah:
sumber
Jika Anda sudah memiliki nomor tersebut sebagai string, yaitu "3456" atau "0,003756", maka Anda berpotensi melakukannya hanya menggunakan manipulasi string. Berikut ini dari atas kepala saya, dan tidak diuji secara menyeluruh, dan menggunakan sed, tetapi pertimbangkan:
Di mana pada dasarnya Anda menanggalkan dan menyimpan barang "-0.000" di awal, lalu gunakan operasi substring sederhana pada yang lain. Satu peringatan tentang hal di atas adalah bahwa beberapa 0 di depan tidak dihapus. Saya akan meninggalkan itu sebagai latihan.
sumber