Bagaimana cara mencetak nilai ganda dengan presisi penuh menggunakan cout?

332

Jadi saya mendapatkan jawaban untuk pertanyaan terakhir saya (saya tidak tahu mengapa saya tidak memikirkan itu). Saya mencetak doublemenggunakan coutyang bulat ketika saya tidak mengharapkannya. Bagaimana saya bisa membuat coutcetakan doublemenggunakan presisi penuh?

Jason Punyon
sumber

Jawaban:

391

Anda dapat mengatur ketepatan langsung std::coutdan menggunakan std::fixedpenentu format.

double d = 3.14159265358979;
cout.precision(17);
cout << "Pi: " << fixed << d << endl;

Anda bisa #include <limits>mendapatkan presisi maksimum float atau double.

#include <limits>

typedef std::numeric_limits< double > dbl;

double d = 3.14159265358979;
cout.precision(dbl::max_digits10);
cout << "Pi: " << d << endl;
Bill the Lizard
sumber
46
Mengapa Anda secara eksplisit menyarankan untuk menggunakan fixed? Dengan double h = 6.62606957e-34;, fixedberi saya 0.000000000000000dan scientificoutput 6.626069570000000e-34.
Arthur
36
Ketelitian harus 17 (atau std :: numeric_limits <double> :: digits10 + 2) karena 2 digit tambahan diperlukan saat mengkonversi dari kembali desimal ke representasi biner untuk memastikan nilainya dibulatkan ke nilai asli yang sama. Berikut ini adalah makalah dengan beberapa detail: docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
Mike Fisher
8
Apakah jawaban yang benar? Ketika saya secara manual menggunakan angka yang tinggi, saya dapat mencetak sebanyak 51 digit dari perkiraan, tetapi dengan cout.precision(numeric_limits<double>::digits10 + 2);saya hanya mendapatkan 16 ....
Assimilater
6
Bagi mereka yang mencari di mana disebutkan 17 digit di koran @MikeFisher dikutip, itu di bawah Teorema 15.
Emile Cormier
15
@MikeFisher Anda benar, C ++ 11 memperkenalkanmax_digits10 untuk menunjukkan hal yang sama. Memperbaiki jawaban untuk mencerminkan hal ini.
legends2k
70

Gunakan std::setprecision:

std::cout << std::setprecision (15) << 3.14159265358979 << std::endl;
Paul Beckingham
sumber
2
Apakah ada semacam makro MAX_PRECISION atau enum atau sesuatu yang bisa saya sampaikan ke std :: setPrecision?
Jason Punyon
2
std :: setprecision (15) untuk dobel (ok atau 16), log_10 (2 ** 53) ~ = 15.9
user7116
14
std :: setprecision (std :: numeric_limits <double> :: digits10)
Éric Malenfant
6
Seharusnya std::setprecision (17)ganda, lihat komentar di @Bill The Lizard's answer.
Alec Jacobson
9
untuk std :: setprecision berfungsi, #include <iomanip> harus dimasukkan.
user2262504
24

Inilah yang akan saya gunakan:

std::cout << std::setprecision (std::numeric_limits<double>::digits10 + 1)
          << 3.14159265358979
          << std::endl;

Pada dasarnya paket limit memiliki ciri untuk semua tipe build.
Salah satu ciri untuk angka floating point (float / double / long double) adalah atribut digits10. Ini mendefinisikan keakuratan (saya lupa terminologi yang tepat) dari angka floating point di basis 10.

Lihat: http://www.cplusplus.com/reference/std/limits/numeric_limits.html
Untuk detail tentang atribut lainnya.

Martin York
sumber
12
Header ini diperlukan untuk menggunakan std::setprecision(): #include <iomanip>
Martin Berger
seharusnya std::numeric_limits<double>bukannumberic_limits<double>
niklasfi
2
Mengapa Anda menambahkan 1ke std::numeric_limits<double>::digits10?
Alessandro Jacopson
5
@LokiAstari Anda bisa menggunakan C + 11 max_digits10sebagai gantinya. Lihat ini .
legends2k
1
@AlecJacobson Seharusnya lebih baik max_digits10, tidak ada yang sewenang-wenang digits10+2. Jika tidak, dalam kasus float, long double, boost::multiprecision::float128ini akan gagal, karena di sana Anda akan perlu +3bukan +2.
Ruslan
14

Cara iostreams agak kikuk. Saya lebih suka menggunakan boost::lexical_castkarena menghitung presisi yang tepat untuk saya. Dan juga cepat .

#include <string>
#include <boost/lexical_cast.hpp>

using boost::lexical_cast;
using std::string;

double d = 3.14159265358979;
cout << "Pi: " << lexical_cast<string>(d) << endl;

Keluaran:

Pi: 3.14159265358979

Timothy003
sumber
Dokumentasi boost mengatakan "Untuk numerik yang memiliki spesialisasi std :: numeric_limits, versi saat ini memilih presisi yang cocok". Ini sepertinya cara termudah untuk mendapatkan presisi maksimal. ( boost.org/doc/libs/1_58_0/doc/html/boost_lexical_cast/… )
JDiMatteo
11

Dengan presisi penuh, saya mengasumsikan presisi yang cukup berarti untuk menunjukkan perkiraan terbaik dengan nilai yang dimaksudkan, tetapi harus ditunjukkan bahwa doubledisimpan menggunakan representasi basis 2 dan basis 2 tidak dapat mewakili sesuatu yang sepele seperti 1.1tepatnya. Satu-satunya cara untuk mendapatkan presisi penuh-penuh dari dobel yang sebenarnya (dengan NO ROUND OFF ERROR) adalah dengan mencetak bit biner (atau hexbuster). Salah satu cara melakukannya adalah menulis doubleke a uniondan kemudian mencetak nilai integer bit.

union {
    double d;
    uint64_t u64;
} x;
x.d = 1.1;
std::cout << std::hex << x.u64;

Ini akan memberi Anda presisi akurat 100% dari ganda ... dan benar-benar tidak dapat dibaca karena manusia tidak dapat membaca format ganda IEEE! Wikipedia memiliki tulisan yang bagus tentang cara menafsirkan bit biner.

Di C ++ yang lebih baru, Anda bisa melakukannya

std::cout << std::hexfloat << 1.1;
Mark Lakata
sumber
10

Berikut ini cara menampilkan ganda dengan presisi penuh:

double d = 100.0000000000005;
int precision = std::numeric_limits<double>::max_digits10;
std::cout << std::setprecision(precision) << d << std::endl;

Ini menampilkan:

100.0000000000005


max_digits10 adalah jumlah digit yang diperlukan untuk secara unik mewakili semua nilai ganda yang berbeda. max_digits10 menunjukkan jumlah digit sebelum dan sesudah titik desimal.


Jangan gunakan set_precision (max_digits10) dengan std :: fix.
Pada notasi tetap, set_precision () menetapkan jumlah digit hanya setelah titik desimal. Ini tidak benar karena max_digits10 mewakili jumlah digit sebelum dan sesudah titik desimal.

double d = 100.0000000000005;
int precision = std::numeric_limits<double>::max_digits10;
std::cout << std::fixed << std::setprecision(precision) << d << std::endl;

Ini menampilkan hasil yang salah:

100.00000000000049738

Catatan: Diperlukan file tajuk

#include <iomanip>
#include <limits>
Daniel Laügt
sumber
4
Ini terjadi karena 100.0000000000005tidak direpresentasikan secara tepat sebagai a double. (Ini mungkin tampak seperti seharusnya, tetapi tidak, karena menjadi dinormalisasi , yaitu representasi binernya). Untuk melihat ini, coba: 100.0000000000005 - 100. Kami mendapatkan 4.973799150320701e-13.
Evgeni Sergeev
9

Bagaimana cara mencetak doublenilai dengan presisi penuh menggunakan cout?

Gunakan hexfloatatau
gunakan scientificdan atur presisi

std::cout.precision(std::numeric_limits<double>::max_digits10 - 1);
std::cout << std::scientific <<  1.0/7.0 << '\n';

// C++11 Typical output
1.4285714285714285e-01

Terlalu banyak jawaban hanya membahas satu dari 1) basis 2) tata letak tetap / ilmiah atau 3) presisi. Terlalu banyak jawaban dengan presisi tidak memberikan nilai tepat yang dibutuhkan. Karena itu, ini menjawab pertanyaan lama.

  1. Basis apa?

A doubletentu dikodekan menggunakan basis 2. Pendekatan langsung dengan C ++ 11 adalah mencetak menggunakan std::hexfloat.
Jika output non-desimal dapat diterima, kita selesai.

std::cout << "hexfloat: " << std::hexfloat << exp (-100) << '\n';
std::cout << "hexfloat: " << std::hexfloat << exp (+100) << '\n';
// output
hexfloat: 0x1.a8c1f14e2af5dp-145
hexfloat: 0x1.3494a9b171bf5p+144

  1. Jika tidak: fixedatau scientific?

A doubleadalah tipe titik mengambang , bukan titik tetap .

Jangan tidak menggunakan std::fixedsebagai yang gagal untuk mencetak kecil doublesebagai sesuatu tetapi 0.000...000. Secara umum double, ia mencetak banyak digit, mungkin ratusan informasi yang dipertanyakan.

std::cout << "std::fixed: " << std::fixed << exp (-100) << '\n';
std::cout << "std::fixed: " << std::fixed << exp (+100) << '\n';
// output
std::fixed: 0.000000
std::fixed: 26881171418161356094253400435962903554686976.000000 

Untuk mencetak dengan presisi penuh, gunakan pertama std::scientificyang akan "menulis nilai floating-point dalam notasi ilmiah". Perhatikan default 6 digit setelah titik desimal, jumlah yang tidak mencukupi, ditangani di titik berikutnya.

std::cout << "std::scientific: " << std::scientific << exp (-100) << '\n';  
std::cout << "std::scientific: " << std::scientific << exp (+100) << '\n';
// output
std::scientific: 3.720076e-44
std::scientific: 2.688117e+43

  1. Berapa presisi (berapa total digit)?

A doubledikodekan menggunakan basis biner 2 mengkodekan presisi yang sama antara berbagai kekuatan 2. Ini sering 53 bit.

[1.0 ... 2.0) ada 2 53 berbeda double,
[2.0 ... 4.0) ada 2 53 berbeda double,
[4.0 ... 8.0) ada 2 53 berbeda double,
[8.0 ... 10.0) ada 2 / 8 * 2 53 berbeda double.

Namun jika kode cetakan dalam desimal dengan Nangka yang signifikan, jumlah kombinasi [1.0 ... 10.0) adalah 9/10 * 10 N .

Apa pun N(presisi) yang dipilih, tidak akan ada pemetaan satu-ke-satu antara doubledan teks desimal. Jika fix Ndipilih, kadang-kadang akan sedikit lebih atau kurang dari yang dibutuhkan untuk doublenilai - nilai tertentu . Kami dapat kesalahan pada terlalu sedikit (di a)bawah) atau terlalu banyak (di b)bawah).

3 kandidat N:

a) Gunakan Njadi ketika mengkonversi dari teks- double-teks kita tiba di teks yang sama untuk semua double.

std::cout << dbl::digits10 << '\n';
// Typical output
15

b) Gunakan Njadi ketika mengkonversi dari double-text- doublekita tiba di sama doubleuntuk semua double.

// C++11
std::cout << dbl::max_digits10 << '\n';
// Typical output
17

Ketika max_digits10tidak tersedia, perhatikan bahwa karena atribut basis 2 dan basis 10 digits10 + 2 <= max_digits10 <= digits10 + 3,, kita dapat menggunakan digits10 + 3untuk memastikan angka desimal yang cukup dicetak.

c) Gunakan an Nyang bervariasi dengan nilainya.

Ini bisa bermanfaat ketika kode ingin menampilkan teks minimal ( N == 1) atau nilai tepat a double( N == 1000-ishuntuk kasus denorm_min). Namun karena ini "bekerja" dan bukan tujuan OP, itu akan dikesampingkan.


Biasanya b) yang digunakan untuk "mencetak doublenilai dengan presisi penuh". Beberapa aplikasi mungkin lebih suka a) salah karena tidak memberikan terlalu banyak informasi.

Dengan .scientific, .precision()atur jumlah digit untuk dicetak setelah titik desimal, sehingga 1 + .precision()digit dicetak. Kode membutuhkan max_digits10angka total sehingga .precision()disebut dengan a max_digits10 - 1.

typedef std::numeric_limits< double > dbl;
std::cout.precision(dbl::max_digits10 - 1);
std::cout << std::scientific <<  exp (-100) << '\n';
std::cout << std::scientific <<  exp (+100) << '\n';
// Typical output
3.7200759760208361e-44
2.6881171418161356e+43
//1234567890123456  17 total digits

Pertanyaan C serupa

chux - Pasang kembali Monica
sumber
Jawaban bagus! Beberapa komentar: Anda benar yang precision()menetapkan jumlah tempat desimal untuk mode ilmiah. Tanpa menentukan scientific, itu menetapkan jumlah digit, tidak termasuk eksponen. Anda mungkin masih berakhir dengan hasil ilmiah, tergantung pada nilai angka Anda, tetapi kemudian Anda mungkin juga mendapatkan lebih sedikit digit dari yang Anda tentukan. Contoh: cout.precision(3); cout << 1.7976931348623158e+308; // "1.8e+308"Hasil untuk printfmungkin berbeda. Hal-hal membingungkan yang harus disadari.
Simpleton
Untuk anak cucu, inilah panjang buffer yang diperlukan untuk representasi string yang pasti dijamin dari semua angka ganda dalam mode ilmiah menggunakan printf: char buf[DBL_DECIMAL_DIG + 3 + 5]; sprintf(buf, "%.*g", DBL_DECIMAL_DIG, d);Karakter tambahan untuk: tanda, titik desimal, trailing nol, e [+ | -], 3 digit untuk eksponen ( DBL_MAX_10_EXP = 308). Karenanya jumlah total karakter yang diperlukan adalah 25.
Simpleton
Tidak dapat mengedit komentar pertama saya, jadi begini lagi: Masalah lain dengan mode ilmiah adalah mungkin memutuskan untuk tidak menggunakan output eksponensial, bahkan mungkin memutuskan untuk tidak menggunakan output floating point sama sekali. Yaitu, itu akan menampilkan 1.0 sebagai "1", yang mungkin menjadi masalah dalam konteks serialisasi / deserialisasi. Anda dapat memaksanya untuk mengeluarkan titik desimal dengan menggunakan "% #. * G", tetapi ini memiliki kelemahan karena menambahkan sejumlah nol tambahan, yang tidak #,
Simpleton
3
printf("%.12f", M_PI);

% .12f berarti titik mengambang, dengan presisi 12 digit.

Tuan
sumber
11
Ini bukan "menggunakan cout".
Johnsyweb
2
12 digit bukan "presisi penuh"
Roland Illig
0

Paling nyaman ...

#include <limits>

using std::numeric_limits;

    ...
    cout.precision(numeric_limits<double>::digits10 + 1);
    cout << d;

sumber
16
Saya ingin tahu: mengapa "+1"?
Éric Malenfant
0

Dengan ostream :: presisi (int)

cout.precision( numeric_limits<double>::digits10 + 1);
cout << M_PI << ", " << M_E << endl;

akan menghasilkan

3.141592653589793, 2.718281828459045

Mengapa Anda harus mengatakan "+1" Saya tidak tahu, tetapi digit tambahan yang Anda dapatkan dari itu benar.

Jann
sumber
3
numeric_limits <unsigned char> :: digits10 sama dengan 2. Karena bisa berisi angka desimal dua digit 0..99. Ini juga dapat berisi 255 .. tetapi tidak 256, 257 ... 300 dll. Ini sebabnya digit10 bukan 3! Saya pikir "+1" ditambahkan untuk mengatasi sesuatu seperti ini.
Dmitriy Yurchenko
0

Ini akan menunjukkan nilai hingga dua tempat desimal setelah titik.

#include <iostream>
#include <iomanip>

double d = 2.0;
int n = 2;
cout << fixed << setprecison(n) << d;

Lihat di sini: Notasi titik tetap

std :: diperbaiki

Gunakan notasi floating-point tetap Mengatur flag format floatfield untuk str stream agar diperbaiki.

Ketika floatfield diatur ke tetap, nilai-nilai titik-mengambang ditulis menggunakan notasi titik-tetap: nilainya diwakili dengan persis sebanyak digit di bagian desimal sebagaimana ditentukan oleh bidang presisi (presisi) dan tanpa bagian eksponen.

std :: setprecision

Mengatur presisi desimal Mengatur presisi desimal yang akan digunakan untuk memformat nilai floating-point pada operasi keluaran.

Jika Anda terbiasa dengan standar IEEE untuk mewakili floating-point, Anda akan tahu bahwa tidak mungkin untuk menunjukkan floating-point dengan presisi penuh di luar ruang lingkup standar , yaitu, itu akan selalu menghasilkan pembulatan dari nilai sebenarnya.

Anda harus terlebih dahulu memeriksa apakah nilainya dalam cakupan , jika ya, gunakan:

cout << defaultfloat << d ;

std :: defaultfloat

Gunakan notasi floating-point default. Mengatur flag format floatfield untuk str stream ke defaultfloat.

Ketika floatfield diatur ke defaultfloat, nilai-nilai floating-point ditulis menggunakan notasi default: representasi menggunakan sebanyak digit yang berarti sesuai kebutuhan hingga presisi desimal aliran (presisi), menghitung kedua digit sebelum dan setelah titik desimal (jika ada ).

Itu juga merupakan perilaku default cout, yang berarti Anda tidak menggunakannya secara eksplisit.

emmmphd
sumber