Precise floating-point<->string conversion

后端 未结 5 482
误落风尘
误落风尘 2020-12-09 05:03

I am looking for a library function to convert floating point numbers to strings, and back again, in C++. The properties I want are that str2num(num2str(x)) == x and that nu

相关标签:
5条回答
  • 2020-12-09 05:34

    I think this does what you want, in combination with the standard library's strtod():

    #include <stdio.h>
    #include <stdlib.h>
    
    int dtostr(char* buf, size_t size, double n)
    {
      int prec = 15;
      while(1)
      {
        int ret = snprintf(buf, size, "%.*g", prec, n);
        if(prec++ == 18 || n == strtod(buf, 0)) return ret;
      }
    }
    

    A simple demo, which doesn't bother to check input words for trailing garbage:

    int main(int argc, char** argv)
    {
      int i;
      for(i = 1; i < argc; i++)
      {
        char buf[32];
        dtostr(buf, sizeof(buf), strtod(argv[i], 0));
        printf("%s\n", buf);
      }
      return 0;
    }
    

    Some example inputs:

    % ./a.out 0.1 1234567890.1234567890 17 1e99 1.34 0.000001 0 -0 +INF NaN
    0.1
    1234567890.1234567
    17
    1e+99
    1.34
    1e-06
    0
    -0
    inf
    nan
    

    I imagine your C library needs to conform to some sufficiently recent version of the standard in order to guarantee correct rounding.

    I'm not sure I chose the ideal bounds on prec, but I imagine they must be close. Maybe they could be tighter? Similarly I think 32 characters for buf are always sufficient but never necessary. Obviously this all assumes 64-bit IEEE doubles. Might be worth checking that assumption with some kind of clever preprocessor directive -- sizeof(double) == 8 would be a good start.

    The exponent is a bit messy, but it wouldn't be difficult to fix after breaking out of the loop but before returning, perhaps using memmove() or suchlike to shift things leftwards. I'm pretty sure there's guaranteed to be at most one + and at most one leading 0, and I don't think they can even both occur at the same time for prec >= 10 or so.

    Likewise if you'd rather ignore signed zero, as Javascript does, you can easily handle it up front, e.g.:

    if(n == 0) return snprintf(buf, size, "0");
    

    I'd be curious to see a detailed comparison with that 3000-line monstrosity you dug up in the Python codebase. Presumably the short version is slower, or less correct, or something? It would be disappointing if it were neither....

    0 讨论(0)
  • 2020-12-09 05:34

    Actually I think you'll find that 1.34 IS 1.3400000000000001. Floating point numbers are not precise. You can't get around this. 1.34f is 1.34000000333786011 for example.

    0 讨论(0)
  • As stated by others. Floating-point numbers are not that accurate its an artifact on how they store the value.

    What you are really looking for is a Decimal number representation. Basically this uses an integer to store the number and has a specific accuracy after the decimal point.

    A quick Google got this: http://www.codeproject.com/KB/mcpp/decimalclass.aspx

    0 讨论(0)
  • 2020-12-09 05:50

    I am still unable to find a library that supplies the necessary code, but I did find some code that does work:

    http://svn.python.org/view/python/branches/py3k/Python/dtoa.c?view=markup

    By supplying a fairly small number of defines it's easy to abstract away the Python integration. This code does indeed meet all the properties I outline.

    0 讨论(0)
  • 2020-12-09 05:51

    The reason for wanting these functions is to save floating point values to CSV files, and then read them correctly. In addition, I'd like the CSV files to contain simple numbers as far as possible so they can be consumed by humans.

    You cannot have conversion double → string → double and in the same time having the string human readable.

    You need to need to choose between an exact conversion and a human readable string. This is the definition of max_digits10 and digits10:

    • difference explained by stackoverflow
    • digits10
    • max_digits10

    Here is an implementation of num2str and str2num with two different contexts from_double (conversion double → string → double) and from_string (conversion string → double → string):

    #include <iostream>
    #include <limits>
    #include <iomanip>
    #include <sstream>
    
    namespace from_double
    {
      std::string num2str(double d)
      {
        std::stringstream ss;
        ss << std::setprecision(std::numeric_limits<double>::max_digits10) << d;
        return ss.str();
      }
    
      double str2num(const std::string& s)
      {
        double d;
        std::stringstream ss(s);
        ss >> std::setprecision(std::numeric_limits<double>::max_digits10) >> d;
        return d;
      }
    }
    
    namespace from_string
    {
      std::string num2str(double d)
      {
        std::stringstream ss;
        ss << std::setprecision(std::numeric_limits<double>::digits10) << d;
        return ss.str();
      }
    
      double str2num(const std::string& s)
      {
        double d;
        std::stringstream ss(s);
        ss >> std::setprecision(std::numeric_limits<double>::digits10) >> d;
        return d;
      }
    }
    
    int main()
    {
      double d = 1.34;
      if (from_double::str2num(from_double::num2str(d)) == d)
        std::cout << "Good for double -> string -> double" << std::endl;
      else
        std::cout << "Bad for double -> string -> double" << std::endl;
    
      std::string s = "1.34";
      if (from_string::num2str(from_string::str2num(s)) == s)
        std::cout << "Good for string -> double -> string" << std::endl;
      else
        std::cout << "Bad for string -> double -> string" << std::endl;
    
      return 0;
    }
    
    0 讨论(0)
提交回复
热议问题