Full precision display of floating point numbers in C++? [duplicate]
Asked Answered
I

3

6

I have read several topics about the display of floating point numbers display in C++ and I couldn't find a satisfying answer.

My question is: how to display all the significant digits of a floating point numbers in C++ in a scientific format (mantissa/exponent) ?

The problem is that all numbers do not have the same number of significant digits in base 10.

For example a double has 15 to 17 significant decimal digits precision, but std::numeric_limits<double>::digits10 returns 15 and consequently, for some numbers I will loose 2 extra decimal digits of precision.

Intend answered 26/10, 2013 at 18:12 Comment(6)
So, use precision(std::numeric_limits<double>::digits10 + 2)? Of course, if the last two digits are "random garbage generated via your calculations (e.g. 1.00000000000005 + 100 - 100 [assuming compiler didn't remove the +100 - 100 as meaningless, for example because those are variables unknown at the time]). It should be noted that for example glibc's printf will produce "garbage" if you print something like double value = 1.234E+300 as printf("%f", value); - there will be about 280 digits of 'randomness')Buddybuderus
@Mats Petersson Garbage is in the eye of the beholder. If you print all n digits of 2^-n, none are garbage.Orthoptic
@RickRegan: So you think that 1234000000000001278847239713129823712387918732982173971923... is a better result than 1234000000000000000000000000000000000000... ?? [The printf I'm working on at work is doing the latter, but of course when I'm using glibc as a reference, I have to take into account the difference]. I have no idea what cout does in this case.Buddybuderus
@MatsPetersson: It is generally false that the “random garbage” generated via calculations appears primarily in the digits beyond digits10. Those digits are the ones that might not be preserved in a conversion from decimal to floating-point in back, and they are only the digits affected by the smallest possible rounding in floating-point. Once one performs more than a single calculation, the errors may compound in a variety of ways, yielding a compounded error that can range from zero to infinity, depending on circumstances.Aftergrowth
@MatsPetersson: Furthermore, for those who understand floating-point and use it carefully, the digits are not meaningless. Yes, we use all available information. Among other things, the number of digits you need to write in order to be able to read in the original number again generally exceeds digits10. Therefore, you must have more digits.Aftergrowth
@Mats Petersson No, I was just saying that if you know that you're dealing with exactly representable values -- powers of two for example -- then all the digits mean something. glibc being able to print all the digits can be useful in this case (see, for example, my article exploringbinary.com/… ).Orthoptic
S
6

The value std::numeric_limits<double>::digits10 provides the number of decimal digits which can safely be restored, i.e., the number of decimal digits which survive a round-trip decimal->double->decimal. Assuming more decimal digits are correct isn't helpful. If you want to faciliate the round-trip double->decimal->double you'd use std::numeric_limits<double>::max_digits10.

If you want the exact values you'd use std::numeric_limits<double>::digits but it will display numbers converted from decimal values in a funny way as they are normally rounded values. This is also the reason why max_digits10 isn't useful when presenting numbers for human consumption: the last couple of digits are normally not those expect by the human reader.

Saxena answered 26/10, 2013 at 18:45 Comment(0)
F
4

In C++20 you can use std::format to do this:

std::cout << std::format("{}", std::numbers::pi_v<double>);

Output (assuming IEEE 754 double):

3.141592653589793

The default floating-point format is the shortest decimal representation with a round-trip guarantee. The advantage of this method compared to using the precision of max_digits10 from std::numeric_limits is that it doesn't print unnecessary digits. For example:

std::cout << std::setprecision(
  std::numeric_limits<double>::max_digits10) << 0.3;

prints 0.29999999999999999 while

std::cout << std::format("{}", 0.3);

prints 0.3 (godbolt).

In the meantime you can use the {fmt} library, std::format is based on. {fmt} also provides the print function that makes this even easier and more efficient (godbolt):

fmt::print("{}", M_PI);

Disclaimer: I'm the author of {fmt} and C++20 std::format.

Fahlband answered 17/12, 2020 at 19:17 Comment(0)
V
2

Have you looked at std::max_digits10?

From cppreference:

The value of std::numeric_limits<T>::max_digits10 is the number of base-10 digits that are necessary to uniquely represent all distinct values of the type T, such as necessary for serialization/deserialization to text. This constant is meaningful for all floating-point types.

The implication of this (and is how I use it) is that the text output can be copy/pasted into another program and the number will represent the same number.

Now, I must say that my work-horse format is always right-justified %23.16E, and I use engineering judgement for the last few digits. I like it because is is sufficient for the sign, the exponent, and sixteen digits.

-----------------------
-1.1234567812345678E+12

Now, notice that digits of precision and decimal digits of precision are not necessarily the same thing.

Volitant answered 26/10, 2013 at 18:25 Comment(2)
+1 for recommending a good default for printing double precision numbers. I use % 23.16e myself, which leaves space for printing the sign, if necessary.Irriguous
@Irriguous I think we have the same format. In my case I "right justify" %23.16Evia flags (as opposed to via space in the format declaration) so that I have space for the optional sign. It is a good format!Volitant

© 2022 - 2024 — McMap. All rights reserved.