0

There is wonderful work of Florian Loitsch called "Printing Floating-Point Numbers Quickly and Accurately with Integers". This sounds pretty useful in case of printing any kind of double into string. I think many people included me until recently only know like setting a fix precision with setprecisiton(n) or printf("%0.nf") and often either choosing n too large and getting floating point imprecision or too small and cutting off valuable data. I wonder is there something similar to this in C++ standard (including upcoming), boost, libc, or somewhere easily reachable and tested?

François Andrieux
  • 28,148
  • 6
  • 56
  • 87
Yuki
  • 3,857
  • 5
  • 25
  • 43
  • Before reading the PDF: `printf("%g", value)`? – pmg Mar 26 '21 at 18:08
  • Uses default precision 6, i.e. again you have to specify precision. – Yuki Mar 26 '21 at 18:11
  • Does this answer your question? [How do I print a double value with full precision using cout?](https://stackoverflow.com/questions/554063/how-do-i-print-a-double-value-with-full-precision-using-cout) – Woodford Mar 26 '21 at 18:13
  • 2
    C and C++ are different languages and while yield different answers. I've removed the C tag, since the question specifically mentions the C++ standard and thus seems to be targeting C++. – François Andrieux Mar 26 '21 at 18:13
  • As the paper says, “ Correct printing become part of the specification of many languages and furthermore all major C libraries (and as a consequence all programs relying on the `printf` functions) adapted accurate algorithms and print correct results now.” – Pete Becker Mar 26 '21 at 18:17
  • Do you specifically want the algorithm in that paper that prints with the fewest digits necessary to identify the original value, or are the algorithms that produce a fixed number of digits (selected to be always sufficient) acceptable? – Eric Postpischil Mar 26 '21 at 18:17
  • @EricPostpischil, any algorithm that produces nearest decimal number is fine. – Yuki Mar 26 '21 at 18:47
  • @Yuki How accurate do you want that _decimal number_? With zero error? – chux - Reinstate Monica Mar 26 '21 at 18:57
  • Yuki, concerning "choosing n too large and getting floating point imprecision or too small ..." You may find [Printf width specifier to maintain precision of floating-point value](https://stackoverflow.com/q/16839658/2410359) useful. – chux - Reinstate Monica Mar 26 '21 at 19:00
  • fyi read some of these posts on round-tripping float and double - https://www.exploringbinary.com/tag/floating-point/ – Richard Critten Mar 26 '21 at 19:32

2 Answers2

1

I don't think there is any really good solution in the standard C++ library.

I use the trick of trying several different formats, and then selecting the text representation that is the shortest and the text representation has the exact same value when round-tripped. Which I use to save in my text file (XML) output.

Which sometimes means selecting the exacting hexadecimal floating point representation. (May be a deal breaker for your use case.)

Here's the code, which I've added a table to generate some examples, and mark them as * if they are the shortest, or with a warning marker if they do not round trip.

#include <iomanip>
#include <ios>
#include <iostream>
#include <sstream>

using std::cout;
using std::defaultfloat;
using std::fixed;
using std::hexfloat;
using std::istringstream;
using std::left;
using std::scientific;
using std::setw;
using std::string;
using std::stringstream;

enum class cap { title, middle, end };

static void print(const char* text, double num, cap c) {
    stringstream ss;
    ss << fixed << num;
    auto s = ss.str();
    double fn = stod(s);
    size_t flen = s.length();
    string falert = "";
    if (fn != num) { falert = "/!\\"; flen += 100; }

    ss.str("");
    ss.clear();
    ss << scientific << num;
    s = ss.str();
    double sn = stod(s);
    size_t slen = s.length();
    string salert = "";
    if (sn != num) { salert = "/!\\"; slen += 100; }

    ss.str("");
    ss.clear();
    ss << hexfloat << num;
    s = ss.str();
    double hn = stod(s);
    size_t hlen = s.length();
    string halert = "";
    if (hn != num) { halert = "/!\\"; hlen += 100; }

    ss.str("");
    ss.clear();
    ss << defaultfloat << num;
    s = ss.str();
    double dn = stod(s);
    size_t dlen = s.length();
    string dalert = "";
    if (dn != num) { dalert = "/!\\"; dlen += 100; }

    char gbuf[256];
    sprintf(gbuf, "%g", num);
    s = gbuf;
    double gn = stod(s);
    size_t glen = s.length();
    string galert = "";
    if (gn != num) { galert = "/!\\"; glen += 100; }

    if (flen <= slen && flen <= hlen && flen <= dlen) falert += "*";
    if (slen <= flen && slen <= hlen && slen <= dlen) salert += "*";
    if (hlen <= flen && hlen <= slen && hlen <= dlen) halert += "*";
    if (dlen <= flen && dlen <= hlen && dlen <= slen) dalert += "*";
    if (glen <= dlen && glen <= flen && glen <= hlen && glen <= slen) galert += "*";

    if (c == cap::title) cout <<
    "┌──────────┬────────────┬──────────────────────────┐\n"
    "│  number  │   iomanip  │      representation      │\n"
    "├──────────┼────────────┼──────────────────────────┤\n"
    ;
    cout << left
    << "│ " << setw(8) << text << " │ fixed      │ " << setw(24) << fixed        << num << " │" << falert << "\n"
    << "│ " << setw(8) << text << " │ scientific │ " << setw(24) << scientific   << num << " │" << salert << "\n"
    << "│ " << setw(8) << text << " │ hexfloat   │ " << setw(24) << hexfloat     << num << " │" << halert << "\n"
    << "│ " << setw(8) << text << " │ default    │ " << setw(24) << defaultfloat << num << " │" << dalert << "\n"
    << "│ " << setw(8) << text << " │ %g         │ " << setw(24) << gbuf                << " │" << galert << "\n"
    ;
    cout << (c != cap::end ?
    "├──────────┼────────────┼──────────────────────────┤\n" :
    "└──────────┴────────────┴──────────────────────────┘\n" );
}

static void table() {
    print("0.0", 0.0, cap::title);
    print("0.01", 0.01, cap::middle);
    print("0.00001", 0.00001, cap::middle);
    print("1e99"   , 1.e+99, cap::middle);
    print("0.1"    , 0.1, cap::middle);
    print("0.2"    , 0.2, cap::middle);
    print("0.3"    , 0.3, cap::middle);
    print("0.4"    , 0.4, cap::middle);
    print("0.5"    , 0.5, cap::middle);
    print("0.6"    , 0.6, cap::middle);
    print("0.7"    , 0.7, cap::middle);
    print("0.8"    , 0.8, cap::middle);
    print("0.9"    , 0.9, cap::middle);
    print("NTSC"   , 30.0/1001.0, cap::middle);
    print("1/3"    , 1.0/3.0, cap::end);
}

int main() {
    table();
}
Eljay
  • 4,648
  • 3
  • 16
  • 27
0

Commonly available functions for accurate floating-point number printing

Post originally tagged [C] and [C++]

 // Hexadecimal/decimal exponential output.
 printf("%a\n", value);  

 // Decimal exponential output with sufficient precision to distinguish against other double
 printf("%.*e\n", DBL_DECIMAL_DIG - 1, double_value);  

 // Decimal sometimes exponential output with sufficient precision to distinguish against other double
 printf("%.*g\n", DBL_DECIMAL_DIG, double_value);  

All finite floating point values are exact. Rarely does one need to see it exactly as a decimal. That takes more code.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • OP may want the algorithm that produces the fewest number of digits required to uniquely identify the original value for the specific number being converted. The C++ library routines do not do this, even if they round correctly (which the standard does not require, unless it has been upgraded). – Eric Postpischil Mar 26 '21 at 18:18
  • @EricPostpischil With language tag edit by non-OP, this C answer may still shed some light for OP. As to what OP truly wants, if it is not "Commonly available functions for accurate floating-point number printing" of the title, may also evolve. – chux - Reinstate Monica Mar 26 '21 at 18:24
  • @chux Thank you. But that would not work, it does not represent numbers, accurately. For example, `100.74` would be represented incorrectly in single precision using this approach. – Yuki Mar 26 '21 at 18:39
  • @Yuki Surely that's a question of *internal representation*. Most decimal fractions can only be *approximated* by binary fractions, which is what finite-precision IEEE-754 binary floating-point numbers are. This seems to be a duplicate of "Is floating point math broken?" – njuffa Mar 26 '21 at 18:45
  • @njuffa I do not think the math is broken. I know some fundamental of how floating-point works internally. You can try to read the begging of the paper to get the flavor of what I am talking about. – Yuki Mar 26 '21 at 18:49
  • @Yuki Question is about " floating-point number". `100.74` is not exactly representable as a `float` nor `double`. It is not a FP value. `printf("%a\n", value);` reports FP values exactly and accurately with 0 error. Are you asking about FP values or code like `100.74`? – chux - Reinstate Monica Mar 26 '21 at 18:55
  • @Yuki I *am* familiar with the paper. See this [answer](https://stackoverflow.com/a/7159280/780717) – njuffa Mar 26 '21 at 19:03