Làm cách nào để in một giá trị kép với độ chính xác đầy đủ bằng cout?


331

Vì vậy, tôi đã nhận được câu trả lời cho câu hỏi cuối cùng của mình (tôi không biết tại sao tôi không nghĩ về điều đó). Tôi đã in một doublecách sử dụng coutđã được làm tròn khi tôi không mong đợi nó. Làm thế nào tôi có thể thực hiện coutin một doublecách chính xác đầy đủ?

Câu trả lời:


390

Bạn có thể đặt độ chính xác trực tiếp std::coutvà sử dụng bộ std::fixedxác định định dạng.

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

Bạn có thể #include <limits>để có được độ chính xác tối đa của một phao hoặc gấp đôi.

#include <limits>

typedef std::numeric_limits< double > dbl;

double d = 3.14159265358979;
cout.precision(dbl::max_digits10);
cout << "Pi: " << d << endl;

46
Tại sao bạn rõ ràng khuyên nên sử dụng fixed? Với double h = 6.62606957e-34;, fixedcho tôi 0.000000000000000scientificđầu ra 6.626069570000000e-34.
Arthur

36
Độ chính xác cần phải là 17 (hoặc std :: num_limits <double> :: chữ số10 + 2) vì cần thêm 2 chữ số khi chuyển đổi từ thập phân trở lại biểu diễn nhị phân để đảm bảo giá trị được làm tròn thành cùng giá trị ban đầu. Đây là một bài báo với một số chi tiết: docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
Mike Fisher

8
Là thực sự câu trả lời đúng? Khi tôi sử dụng thủ công một số cao, tôi có thể in ra 51 chữ số gần đúng của e, nhưng với cout.precision(numeric_limits<double>::digits10 + 2);tôi chỉ nhận được 16 ....
Assimilater

6
Đối với những người tìm kiếm nơi nó đề cập đến 17 chữ số trong bài báo @MikeFisher đã trích dẫn, nó theo Định lý 15.
Cormier Emile

15
@MikeFisher Bạn nói đúng, C ++ 11 giới thiệumax_digits10 để biểu thị tương tự. Đã sửa câu trả lời để phản ánh điều này.
huyền thoại2k

70

Sử dụng std::setprecision:

std::cout << std::setprecision (15) << 3.14159265358979 << std::endl;

2
Có một số loại macro hoặc enum MAX_PRECISION hoặc thứ gì đó tôi có thể chuyển sang std :: setPrecision không?
Jason Punyon

2
std :: setprecision (15) cho một cú đúp (ok hoặc 16), log_10 (2 ** 53) ~ = 15.9
user7116

14
std :: setprecision (std :: num_limits <double> :: chữ số10)
Éric Malenfant

6
Nên std::setprecision (17)gấp đôi, xem bình luận về câu trả lời của @Bill The Lizard.
Alec Jacobson

9
để std :: setprecision hoạt động, nên bao gồm #include <iomanip>.
dùng2262504

24

Đây là những gì tôi sẽ sử dụng:

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

Về cơ bản gói giới hạn có các đặc điểm cho tất cả các kiểu xây dựng.
Một trong những đặc điểm của số dấu phẩy động (float / double / long double) là thuộc tính Digit10. Điều này xác định độ chính xác (tôi quên thuật ngữ chính xác) của một số dấu phẩy động trong cơ sở 10.

Xem: http://www.cplusplus.com/reference/std/limits/numeric_limits.html
Để biết chi tiết về các thuộc tính khác.


12
Tiêu đề này là cần thiết để sử dụng std::setprecision(): #include <iomanip>
Martin Berger

nó nên std::numeric_limits<double>thay vìnumberic_limits<double>
niklasfi

2
Tại sao bạn thêm 1vào std::numeric_limits<double>::digits10?
Alessandro Jacopson

5
@LokiAstari Bạn có thể sử dụng C + 11 max_digits10thay thế. Xem này .
huyền thoại2k

1
@AlecJacobson Thà là chứ max_digits10không phải tùy tiện digits10+2. Nếu không, trong trường hợp float, long double, boost::multiprecision::float128này sẽ thất bại, vì có bạn cần +3thay vì +2.
Ruslan

14

Cách iostreams là loại clunky. Tôi thích sử dụng boost::lexical_castbởi vì nó tính toán độ chính xác phù hợp với tôi. Và nó cũng nhanh thôi.

#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;

Đầu ra:

Pi: 3.14159265358979


Tài liệu tăng cường cho biết "Đối với các số có chuyên môn tương ứng của std :: num_limits, phiên bản hiện tại chọn độ chính xác để khớp". Đây có vẻ là cách dễ nhất để có được độ chính xác tối đa. ( boost.org/doc/libs/1_58_0/doc/html/boost_lexical_cast/ gợi ý )
JDiMatteo

11

Với độ chính xác đầy đủ, tôi giả sử có nghĩa là đủ chính xác để hiển thị xấp xỉ tốt nhất với giá trị dự định, nhưng cần chỉ ra rằng doubleđược lưu trữ bằng cách sử dụng biểu diễn cơ sở 2 và cơ sở 2 không thể biểu thị 1.1chính xác như tầm thường . Cách duy nhất để có được độ chính xác đầy đủ của gấp đôi thực tế (KHÔNG CÓ ROUND OFF ERROR) là in ra các bit nhị phân (hoặc hex nybble). Một cách để làm điều đó là ghi doublevào a unionvà sau đó in ra giá trị nguyên của các bit.

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

Điều này sẽ cung cấp cho bạn độ chính xác 100% của gấp đôi ... và hoàn toàn không thể đọc được vì con người không thể đọc định dạng kép của IEEE! Wikipedia có một bài viết tốt về cách diễn giải các bit nhị phân.

Trong C ++ mới hơn, bạn có thể làm

std::cout << std::hexfloat << 1.1;

10

Dưới đây là cách hiển thị gấp đôi với độ chính xác đầy đủ:

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

Điều này hiển thị:

100.0000000000005


max_digits10 là số chữ số cần thiết để thể hiện duy nhất tất cả các giá trị kép riêng biệt. max_digits10 đại diện cho số chữ số trước và sau dấu thập phân.


Không sử dụng set_precision (max_digits10) với std :: fixed.
Trên ký hiệu cố định, set_precision () chỉ đặt số chữ số sau dấu thập phân. Điều này không chính xác vì max_digits10 đại diện cho số chữ số trướcsau dấu thập phân.

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

Điều này hiển thị kết quả không chính xác:

100.00000000000049738

Lưu ý: Yêu cầu tệp tiêu đề

#include <iomanip>
#include <limits>

4
Điều này xảy ra bởi vì 100.0000000000005không được đại diện chính xác như một double. (Có vẻ như nó nên, nhưng nó không, bởi vì nó được chuẩn hóa , tức là biểu diễn nhị phân của nó). Để thấy điều này, hãy thử : 100.0000000000005 - 100. Chúng tôi nhận được 4.973799150320701e-13.
Evgeni Sergeev

9

Làm cách nào để in một doublegiá trị với độ chính xác đầy đủ bằng cout?

Sử dụng hexfloathoặc
sử dụng scientificvà thiết lập độ chính xác

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

Quá nhiều câu trả lời chỉ giải quyết một trong 1) cơ sở 2) bố cục cố định / khoa học hoặc 3) chính xác. Quá nhiều câu trả lời với độ chính xác không cung cấp giá trị phù hợp cần thiết. Do đó câu trả lời cho một câu hỏi cũ.

  1. Cơ sở nào?

A doublechắc chắn được mã hóa bằng cơ sở 2. Cách tiếp cận trực tiếp với C ++ 11 là in bằng cách sử dụng std::hexfloat.
Nếu một đầu ra không thập phân được chấp nhận, chúng ta đã hoàn thành.

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. Nếu không: fixedhay scientific?

A doublelà loại điểm nổi , không phải điểm cố định .

Đừng không sử dụng std::fixednhư thất bại trong việc in nhỏ doublenhư bất cứ điều gì nhưng 0.000...000. Đối với lớn double, nó in nhiều chữ số, có thể hàng trăm thông tin nghi vấn.

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 

Để in với độ chính xác đầy đủ, trước tiên, sử dụng std::scientificsẽ "ghi các giá trị dấu phẩy động theo ký hiệu khoa học". Lưu ý mặc định là 6 chữ số sau dấu thập phân, một lượng không đủ, được xử lý ở điểm tiếp theo.

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. Độ chính xác bao nhiêu (bao nhiêu chữ số)?

Một doublemã hóa sử dụng cơ sở nhị phân 2 mã hóa cùng độ chính xác giữa các quyền hạn khác nhau của 2. Đây thường là 53 bit.

[1.0 ... 2.0) có 2 53 khác nhau double,
[2.0 ... 4.0) có 2 53 khác nhau double,
[4.0 ... 8.0) có 2 53 khác nhau double,
[8.0 ... 10.0) có 2 / 8 * 2 53 khác nhau double.

Tuy nhiên, nếu mã bản in trong hệ thập phân với Nchữ số có nghĩa, số kết hợp [1.0 ... 10.0) là 9/10 * 10 N .

Nlựa chọn (độ chính xác) là gì, sẽ không có ánh xạ một-một giữadouble văn bản thập phân. Nếu một cố định Nđược chọn, đôi khi nó sẽ hơi nhiều hơn hoặc ít hơn mức thực sự cần thiết cho các doublegiá trị nhất định . Chúng tôi có thể lỗi trên quá ít ( a)bên dưới) hoặc quá nhiều ( b)bên dưới).

3 thí sinh N:

a) Sử dụng một N như vậy khi chuyển đổi từ văn bản - văn bản, doublechúng tôi đến cùng một văn bản cho tất cả double.

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

b) Sử dụng Nnhư vậy khi chuyển đổi từ double-text-double chúng tôi đến cùng doublecho tất cả double.

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

Khi max_digits10không có sẵn, lưu ý rằng do thuộc tính cơ sở 2 và cơ sở 10 digits10 + 2 <= max_digits10 <= digits10 + 3, chúng tôi có thể sử dụng digits10 + 3để đảm bảo đủ các chữ số thập phân được in.

c) Sử dụng một Nthay đổi với giá trị.

Điều này có thể hữu ích khi mã muốn hiển thị văn bản tối thiểu ( N == 1) hoặc giá trị chính xác của một double( N == 1000-ishtrong trường hợpdenorm_min ). Tuy nhiên, vì đây là "công việc" và không có khả năng là mục tiêu của OP, nên nó sẽ được đặt sang một bên.


Nó thường là b) được sử dụng để "in một double giá trị với độ chính xác đầy đủ". Một số ứng dụng có thể thích a) lỗi khi không cung cấp quá nhiều thông tin.

Với .scientific, .precision()đặt số chữ số sẽ in sau dấu thập phân, để 1 + .precision()chữ số được in. Mã cần max_digits10tổng số chữ số để .precision()được gọi với 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

Câu hỏi C tương tự


Câu trả lời chính xác! Một vài lưu ý mặc dù: Bạn đã đúng khi precision()đặt số vị trí thập phân cho chế độ khoa học. Không chỉ định scientific, nó đặt tổng số chữ số, không bao gồm số mũ. Bạn vẫn có thể kết thúc với đầu ra khoa học, tùy thuộc vào giá trị số của bạn, nhưng sau đó bạn cũng có thể nhận được ít chữ số hơn bạn đã chỉ định. Ví dụ: cout.precision(3); cout << 1.7976931348623158e+308; // "1.8e+308"Kết quả cho printfcó thể khác nhau. Những thứ khó hiểu người ta nên nhận ra.
Đơn giản

Đối với hậu thế, đây là độ dài bộ đệm cần thiết để thể hiện chuỗi chính xác được đảm bảo của tất cả các số kép trong chế độ khoa học bằng printf: char buf[DBL_DECIMAL_DIG + 3 + 5]; sprintf(buf, "%.*g", DBL_DECIMAL_DIG, d);Các ký tự phụ là: dấu, dấu thập phân, dấu 0, e [+ | -], 3 chữ số cho số mũ ( DBL_MAX_10_EXP = 308). Do đó, tổng số ký tự được yêu cầu là 25.
Simpleton

Không thể chỉnh sửa nhận xét đầu tiên của tôi, vì vậy chúng tôi lại tiếp tục: Một vấn đề khác với chế độ khoa học là nó có thể quyết định không sử dụng đầu ra theo cấp số nhân, thậm chí nó có thể quyết định hoàn toàn không sử dụng đầu ra dấu phẩy động. Đó là, nó sẽ xuất ra 1.0 là "1", đây có thể là một vấn đề trong bối cảnh tuần tự hóa / giải tuần tự hóa. Bạn có thể buộc nó xuất ra một dấu thập phân bằng cách sử dụng "% #. * G", nhưng điều này có nhược điểm là nó thêm một số số 0 ở cuối, mà nó không có # ...
Simpleton

3
printf("%.12f", M_PI);

% .12f có nghĩa là điểm nổi, với độ chính xác là 12 chữ số.


11
Đây không phải là "sử dụng cout".
Johnsyweb

2
12 chữ số không phải là "chính xác hoàn toàn"
Roland Illig

0

Đáng chú ý nhất ...

#include <limits>

using std::numeric_limits;

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

16
Tôi tò mò: tại sao "+1"?
Éric Malenfant

Hạ cấp này ngay bây giờ.
stephanmg

0

Với Ostream :: precision (int)

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

sẽ mang lại

3.141592653589793, 2.718281828459045

Tại sao bạn phải nói "+1" Tôi không có manh mối, nhưng chữ số phụ bạn nhận được từ đó là chính xác.


3
num_limits <unsign char> :: chữ số10 bằng 2. Vì nó có thể chứa bất kỳ số thập phân nào có hai chữ số 0,99. Nó cũng có thể chứa 255 .. nhưng không phải 256, 257 ... 300, v.v ... đây là lý do tại sao chữ số10 không phải là 3! Tôi nghĩ rằng "+1" được thêm vào để khắc phục điều này.
Dmitriy Yurchenko

0

Điều này sẽ hiển thị giá trị lên đến hai chữ số thập phân sau dấu chấm.

#include <iostream>
#include <iomanip>

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

Xem tại đây: Ký hiệu điểm cố định

std :: cố định

Sử dụng ký hiệu dấu phẩy động cố định Đặt cờ định dạng trường nổi cho luồng str thành cố định.

Khi trường nổi được đặt thành cố định, các giá trị dấu phẩy động được viết bằng ký hiệu điểm cố định: giá trị được biểu thị bằng chính xác nhiều chữ số trong phần thập phân như được chỉ định bởi trường chính xác (độ chính xác) và không có phần số mũ.

std :: setprecision

Đặt độ chính xác thập phân Đặt độ chính xác thập phân được sử dụng để định dạng các giá trị dấu phẩy động trên các hoạt động đầu ra.

Nếu bạn quen thuộc với tiêu chuẩn IEEE để biểu diễn các dấu phẩy động, bạn sẽ biết rằng không thể hiển thị các dấu phẩy động với độ chính xác hoàn toàn ngoài phạm vi của tiêu chuẩn , nghĩa là, nó sẽ luôn dẫn đến làm tròn giá trị thực.

Trước tiên, bạn cần kiểm tra xem giá trị có nằm trong phạm vi không , nếu có, sau đó sử dụng:

cout << defaultfloat << d ;

std :: defaultfloat

Sử dụng ký hiệu dấu phẩy động mặc định Đặt cờ định dạng floatfield cho luồng str thành defaultfloat.

Khi floatfield được đặt thành defaultfloat, các giá trị dấu phẩy động được viết bằng ký hiệu mặc định: biểu diễn sử dụng càng nhiều chữ số có ý nghĩa cần đến độ chính xác thập phân của luồng (độ chính xác), đếm cả hai chữ số trước và sau dấu thập phân (nếu có ).

Đó cũng là hành vi mặc định của cout, có nghĩa là bạn không sử dụng nó một cách rõ ràng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.