8

In C++, can I write and read back a float (or double) in text format without losing precision?

Consider the following:

float f = ...;
{
    std::ofstream fout("file.txt");
    // Set some flags on fout
    fout << f;
 }
 float f_read;
 {
     std::ifstream fin("file.txt");
     fin >> f;
  }
  if (f != f_read) {
      std::cout << "precision lost" << std::endl;
  }

I understand why precision is lost sometimes. However, if I print the value with enough digits, I should be able to read back the exact same value.

Is there a given set of flags that is guaranteed to never lose precision? Would this behaviour be portable across platforms?

luispedro
  • 6,934
  • 4
  • 35
  • 45
  • If you want precise storing and loading, convert the binary representation of the float to base64 and store that... – Kerrek SB Sep 06 '11 at 18:16
  • "However, if I print the value with enough digits, I should be able to read back the exact same value." Yes, about 17 significant decimal digits. Related C question: [Printf width specifier to maintain precision of floating-point value](https://stackoverflow.com/q/16839658/2410359). – chux - Reinstate Monica Feb 27 '21 at 07:21

4 Answers4

6

Have a look at this article: How to Print Floating-Point Numbers Accurately and also at that one: Printing Floating-Point Numbers Quickly and Accurately.

It is also mentioned on stackoverflow here, and there is some pointer to an implementation here.

Community
  • 1
  • 1
Lior Kogan
  • 19,919
  • 6
  • 53
  • 85
6

If you don't need to support platforms that lack C99 support (MSVC), your best bet is actually to use the %a format-specifier with printf, which always generates an exact (hexadecimal) representation of the number while using a bounded number of digits. If you use this method, then no rounding occurs during the conversion to a string or back, so the rounding mode has no effect on the result.

Stephen Canon
  • 103,815
  • 19
  • 183
  • 269
  • if there's no need for human-reading in the file then just print out the number as hexadimals `printf("%8X %16X", 1.1f, 1.1);`. This is the fastest and works regardless of C99 support – phuclv Aug 03 '13 at 01:11
  • 1
    @LưuVĩnhPhúc: Two things: `%a` is human-readable, and your suggestion invokes undefined behavior (in particular, it won't do what you want on most 64-bit platforms; it will just print arbitrary garbage bits). – Stephen Canon Aug 03 '13 at 15:29
  • the behavior is not undefined because printf just pops out 8 bytes from stack and decides what's that data type depend on the formatter. If you use %a it'll treat those bytes as double, %lld will treat them as long long int, and similarly %llX will just print them as hexadecimals. The code I typed above has some mistake. The right one should be `printf("%08llX %016llX", 1.1f, 1.1);` another way to use is `printf("%08X\n", *(int *)&a);` – phuclv Aug 03 '13 at 15:42
  • That's how printf works on a few platforms. However, there's nothing to guarantee it. Try on 64-bit osx or Linux and see what happens... – Stephen Canon Aug 03 '13 at 23:19
  • @LưuVĩnhPhúc "just pops out 8 bytes from stack and decides what's that data" --> expect that FP values are sometimes passed in a FP stack and integers in the usual stack. `printf("%8X %16X", 1.1f, 1.1);` remains UB. – chux - Reinstate Monica Feb 27 '21 at 07:19
5

if I print the value with enough digits, I should be able to read back the exact same value

Not if you write it in decimal - there's not an integer relationship between the number of binary digits and the number of decimal digits required to represent a number. If you print your number out in binary or hexadecimal, you'll be able to read it back without losing any precision.

In general, floating point numbers are not portable between platforms in the first place, so your text representation is not going to be able to bridge that gap. In practice, most machines use IEEE 754 floating point numbers, so it'll probably work reasonably well.

Carl Norum
  • 219,201
  • 40
  • 422
  • 469
  • 1
    If I have enough decimal digits, there should be a single float that is the closest representation, no? – luispedro Sep 06 '11 at 17:55
  • @luis, not necessarily - it may be impossible for you to control the rounding behaviour, for example, so if you print out a decimal number and it rounds one way, and then it rounds a different way when reading it back in, you'll be stuck. – Carl Norum Sep 06 '11 at 17:57
  • That was my question: is it possible to set the rounding so that it works simmetrically? – luispedro Sep 06 '11 at 18:00
  • @luis - 100% compiler & system dependent. You'll have to go read your documentation. It will definitely be non-portable. – Carl Norum Sep 06 '11 at 18:03
3

You can't necessarily print the exact value of a "power of two" float in decimal.
Think of using base three to store 1/3, now try and print 1/3 in decimal perfectly.

For solutions see: How do you print the EXACT value of a floating point number?

Community
  • 1
  • 1
Martin Beckett
  • 94,801
  • 28
  • 188
  • 263
  • 1
    This is false. Because 10 is a multiple of 2, *every* floating-point number has a finite representation in decimal (the converse is not true). – Stephen Canon Sep 07 '11 at 13:55
  • @Stephen - you're right, apparently IEEE754 requires that the rounding rules generate the correct resulting float IF you use exactly the correct number of decimal places. Although they don't if you print more ! – Martin Beckett Sep 07 '11 at 16:44
  • I actually meant it in a purely mathematical sense. The situation is quite different from writing 1/3 in decimal, because 3 does not divide 10 evenly. Ignoring IEEE754, every dyadic rational number can be written with a finite number of decimal digits. – Stephen Canon Sep 07 '11 at 17:13
  • @Stephen - yes it was a bad example, I confused explaining that you couldn't necessarily represent a fraction in one base finitely in another - but of course you can in base 2 and 10 (except for roundign rule sin the format) – Martin Beckett Sep 07 '11 at 17:57