Apakah ada printf
penentu lebar yang dapat diterapkan ke penentu titik mengambang yang secara otomatis akan memformat keluaran ke jumlah digit signifikan yang diperlukan sehingga saat memindai string kembali, nilai titik mengambang asli diperoleh?
Misalnya, saya mencetak float
dengan presisi 2
tempat desimal:
float foobar = 0.9375;
printf("%.2f", foobar); // prints out 0.94
Ketika saya memindai output 0.94
, saya tidak memiliki jaminan yang sesuai standar bahwa saya akan mendapatkan 0.9375
kembali nilai floating-point asli (dalam contoh ini, saya mungkin tidak akan).
Saya ingin cara memberitahu printf
untuk secara otomatis mencetak nilai floating-point ke jumlah digit signifikan yang diperlukan untuk memastikan bahwa itu dapat dipindai kembali ke nilai asli yang diteruskan printf
.
Saya dapat menggunakan beberapa makro float.h
untuk mendapatkan lebar maksimum untuk diteruskan printf
, tetapi apakah sudah ada penentu untuk mencetak secara otomatis ke jumlah digit signifikan yang diperlukan - atau setidaknya ke lebar maksimum?
sumber
printf( "%f", val );
yang sudah portabel, efisien, dan default.double
. Karena Andadouble
menjadi sangat besar (sangat jauh dari 1.0), itu sebenarnya menjadi kurang akurat dalam bagian desimal (bagian nilai kurang dari 1.0). Jadi Anda tidak bisa mendapatkan jawaban yang memuaskan di sini, karena pertanyaan Anda memiliki asumsi yang salah di dalamnya (yaitu bahwa semuafloat
s /double
s dibuat sama)Jawaban:
Saya merekomendasikan solusi heksadesimal @Jens Gustedt: gunakan% a.
OP menginginkan "mencetak dengan presisi maksimum (atau setidaknya hingga desimal paling signifikan)".
Contoh sederhananya adalah mencetak satu ketujuh seperti di:
Tapi mari kita gali lebih dalam ...
Secara matematis, jawabannya adalah "0.142857 142857 142857 ...", tetapi kami menggunakan angka floating point presisi hingga. Mari kita asumsikan biner presisi ganda IEEE 754 . Jadi
OneSeventh = 1.0/7.0
hasilnya di nilai dibawah ini. Juga ditampilkandouble
bilangan floating point sebelum dan sesudahnya.Mencetak representasi desimal yang tepat dari a
double
memiliki kegunaan terbatas.C memiliki 2 keluarga makro
<float.h>
untuk membantu kami.Set pertama adalah jumlah digit signifikan untuk dicetak dalam string dalam desimal sehingga saat memindai string kembali, kami mendapatkan titik mengambang asli. Ada yang ditampilkan dengan nilai minimum spesifikasi C dan kompiler C11 sampel .
Set kedua adalah jumlah digit signifikan sebuah string dapat dipindai menjadi floating point dan kemudian FP dicetak, masih mempertahankan presentasi string yang sama. Ada yang ditampilkan dengan nilai minimum spesifikasi C dan kompiler C11 sampel . Saya yakin tersedia pra-C99.
Set makro pertama tampaknya memenuhi tujuan OP untuk digit signifikan . Tetapi makro itu tidak selalu tersedia.
"+ 3" adalah inti dari jawaban saya sebelumnya. Ini berpusat pada jika mengetahui konversi bolak-balik string-FP-string (set # 2 makro tersedia C89), bagaimana cara menentukan digit FP-string-FP (set # 1 makro tersedia pos C89)? Secara umum, tambahkan 3 adalah hasilnya.
Sekarang berapa banyak angka penting yang akan dicetak diketahui dan didorong
<float.h>
.Untuk mencetak N digit desimal signifikan, seseorang dapat menggunakan berbagai format.
Dengan
"%e"
, bidang presisi adalah jumlah digit setelah digit awal dan titik desimal. Begitu- 1
juga dengan ketertiban. Catatan: Ini-1
bukan di awalint Digs = DECIMAL_DIG;
Dengan
"%f"
, bidang presisi adalah jumlah digit setelah koma desimal. Untuk angka sepertiOneSeventh/1000000.0
, seseorang perluOP_DBL_Digs + 6
melihat semua angka penting .Catatan: Banyak yang terbiasa
"%f"
. Itu menampilkan 6 digit setelah koma desimal; 6 adalah tampilan default, bukan ketepatan angka.sumber
%f
adalah 6."%f"
di tempat pertama. Menggunakan"%e"
seperti yang Anda tunjukkan tentu saja merupakan pendekatan yang lebih baik secara keseluruhan dan secara efektif merupakan jawaban yang layak (meskipun mungkin tidak sebagus penggunaan"%a"
jika tersedia, dan tentu saja"%a"
harus tersedia jika `DBL_DECIMAL_DIG ada). Saya selalu berharap untuk penentu format yang akan selalu bulat persis dengan presisi maksimum (bukan 6 tempat desimal hard-code).Jawaban singkat untuk mencetak angka floating point tanpa kehilangan (sehingga bisa dibaca kembali ke angka yang persis sama, kecuali NaN dan Infinity):
printf("%.9g", number)
.printf("%.17g", number)
.JANGAN gunakan
%f
, karena itu hanya menentukan berapa banyak angka penting setelah desimal dan akan memotong angka kecil. Untuk referensi, angka ajaib 9 dan 17 dapat ditemukan difloat.h
mana mendefinisikanFLT_DECIMAL_DIG
danDBL_DECIMAL_DIG
.sumber
%g
penspesifikasinya?double
nilai di atas0.1
:1.000_0000_0000_0000_2e-01
,1.000_0000_0000_0000_3e-01
kebutuhan 17 digit untuk membedakan."%.16g"
tidak cukup dan"%.17g"
dan"%.16e"
cukup. Detailnya%g
, salah ingat oleh saya.Jika Anda hanya tertarik pada bit (pola resp hex) Anda dapat menggunakan
%a
format. Ini menjamin Anda:Saya harus menambahkan bahwa ini hanya tersedia sejak C99.
sumber
Tidak, tidak ada penentu lebar printf untuk mencetak floating-point dengan presisi maksimum . Izinkan saya menjelaskan alasannya.
Ketepatan maksimum dari
float
dandouble
adalah variabel , dan bergantung pada nilai sebenarnya darifloat
ataudouble
.Ingat
float
dandouble
disimpan dalam format sign.exponent.mantissa . Ini berarti ada lebih banyak bit yang digunakan untuk komponen pecahan untuk bilangan kecil daripada bilangan besar.Misalnya,
float
dapat dengan mudah membedakan antara 0,0 dan 0,1.Tetapi
float
tidak tahu perbedaan antara1e27
dan1e27 + 0.1
.Ini karena semua presisi (yang dibatasi oleh jumlah bit mantissa) digunakan untuk sebagian besar bilangan, di kiri desimal.
The
%.f
pengubah hanya mengatakan berapa banyak nilai desimal Anda ingin mencetak dari nomor float sejauh format pergi. Fakta bahwa akurasi yang tersedia tergantung pada ukuran nomor itu terserah Anda sebagai programmer untuk menangani.printf
tidak bisa / tidak menangani itu untuk Anda.sumber
float
disediakan, dan Anda menegaskan bahwa tidak ada hal seperti itu (yaitu tidak adaFLT_DIG
), yang salah.FLT_DIG
tidak berarti apa-apa. Jawaban ini menegaskan jumlah tempat desimal yang tersedia bergantung pada nilai di dalam float .Cukup gunakan makro dari
<float.h>
dan penentu konversi lebar-variabel (".*"
):sumber
printf("%." FLT_DIG "f\n", f);
%e
, tidak terlalu baik untuk%f
: hanya jika diketahui bahwa nilai yang akan dicetak mendekati1.0
.%e
mencetak angka yang signifikan untuk angka yang sangat kecil dan%f
tidak. misx = 1e-100
.%.5f
cetakan0.00000
(kehilangan presesi total).%.5e
cetakan1.00000e-100
.FLT_DIG
didefinisikan ke nilai yang didefinisikan karena suatu alasan. Jika 6, itu karenafloat
tidak mampu menahan lebih dari 6 digit presisi. Jika Anda mencetaknya dengan menggunakan%.7f
, digit terakhir tidak akan ada artinya. Berpikirlah sebelum Anda memberi suara negatif.%.6f
tidak setara, karenaFLT_DIG
tidak selalu 6. Dan siapa yang peduli dengan efisiensi? I / O sudah mahal sekali, satu digit lebih atau kurang presisi tidak akan membuat bottleneck.Saya menjalankan percobaan kecil untuk memverifikasi bahwa pencetakan dengan
DBL_DECIMAL_DIG
memang benar-benar mempertahankan representasi biner nomor tersebut. Ternyata untuk kompiler dan pustaka C yang saya coba,DBL_DECIMAL_DIG
jumlah digitnya memang diperlukan, dan mencetak bahkan dengan satu digit kurang menciptakan masalah yang signifikan.Saya menjalankan ini dengan kompiler C Microsoft 19.00.24215.1 dan gcc versi 7.4.0 20170516 (Debian 6.3.0-18 + deb9u1). Menggunakan satu digit desimal kurang membagi dua jumlah angka yang perbandingannya sama persis. (Saya juga memverifikasi bahwa yang
rand()
digunakan memang menghasilkan sekitar satu juta angka yang berbeda.) Berikut adalah detail hasil.Microsoft C
GCC
sumber
RAND_MAX == 32767
. Pertimbangkanu.s[j] = (rand() << 8) ^ rand();
atau sejenisnya untuk memastikan semua bit mendapat peluang menjadi 0 atau 1.Dalam salah satu komentar saya untuk sebuah jawaban, saya menyesali bahwa saya sudah lama menginginkan cara untuk mencetak semua digit signifikan dalam nilai floating point dalam bentuk desimal, dengan cara yang sama seperti pertanyaan yang diajukan. Akhirnya saya duduk dan menulisnya. Ini tidak cukup sempurna, dan ini adalah kode demo yang mencetak informasi tambahan, tetapi sebagian besar berfungsi untuk pengujian saya. Tolong beritahu saya jika Anda (yaitu siapa saja) menginginkan salinan seluruh program pembungkus yang mendorongnya untuk pengujian.
sumber
Sepengetahuan saya, ada algoritma yang tersebar dengan baik yang memungkinkan untuk menghasilkan jumlah digit signifikan yang diperlukan sehingga ketika memindai string kembali, nilai floating point asli diperoleh secara
dtoa.c
tertulis oleh Daniel Gay, yang tersedia di sini di Netlib (lihat juga kertas terkait ). Kode ini digunakan misalnya di Python, MySQL, Scilab, dan banyak lainnya.sumber