3

Since i have to convert lots of double to char[] very fast, after step into sprintf, i found that _cfltcvt_l did the real conversion work, and before _cfltcvt_l is called, it has many switch,case,validate...function that i don't really need, so i want to convert double to char[] by myself.

I want to get the same result as sprintf(c,"%16.9E",d);does, for example:

d=49.9999999995; --> c={"5.000000000E+001"}

d=2.5323867855e+298; --> c={"2.532386786e+298"}

Here is my code:

#include<iostream>
using namespace std;
void fast_sprintf(char* c, double d)
{
    int i = 0, e = 0, n = 0, flag = 0;//flag=0E+;1E-

    if (d < 0)
    {
        c[i++] = '-';
        d = -d;
    }
    while (d >= 10)
    {
        d /= 10;//here is the problem
        e++;
    }
    while (d < 1)
    {
        d *= 10;
        e++;
        flag = 1;
    }
    int v = d, dot;
    c[i++] = '0' + v;//the integer part
    dot = i;
    n++;
    c[i++] = '.';
    d -= v;
    while (d != 0 && n < 10)
    {
        d *= 10;
        v = d;
        c[i++] = '0' + v;
        n++;
        d -= v;
    }
    if (d != 0)
    {

        if (d * 10 >= 5)//rounding
        {
            int j = i - 1;
            c[j]++;
            while (c[j]>'9')
            {
                c[j] = '0';
                if (j - 1 == dot)
                    j--;
                c[--j]++;
            }
        }
    }
    else
    {
        while (n < 10)
        {
            c[i++] = '0';
            n++;
        }
    }

    c[i++] = 'E';
    c[i++] = (flag == 0) ? '+' : '-';
    if (e >= 100)
    {
        int tmp = e / 100;
        c[i++] = '0' + tmp;
        e -= (tmp*100);
        c[i++] = '0' + e / 10;
        c[i++] = '0' + e % 10;
    }
    else if (e <= 9)
    {
        c[i++] = '0';
        c[i++] = '0';
        c[i++] = '0' + e;
    }
    else
    {
        c[i++] = '0';
        c[i++] = '0' + e / 10;
        c[i++] = '0' + e % 10;
    }
    c[i] = '\0';
}

int main()
{
    char c[20];
    //double d=49.9999999995;
    double d=2.5323867855e+298;

    sprintf(c,"%16.9E",d);
    cout<<c<<endl;

    fast_sprintf(c,d);
    cout<<c<<endl;
    return 0;
}

But when d=2.5323867855e+298, c={"2.532386785e+298"} instead of c={"2.532386786e+298"}

That's because after this loop:

//before loop, d=2.5323867855000001e+298
while (d >= 10)
    {
        d /= 10;//here is the problem
        e++;
    }
//after loop, d=2.5323867854999969

d loses its precision, the last digit 5 becomes 4999969.

So how can i fix my code or is there a better way to implement another sprintf(c,"%16.9E",d);?

Thanks.

user1024
  • 982
  • 4
  • 13
  • 26
  • 3
    And by the time you write code for all the corner cases, taking care of loss of precision, etc. you wind up with ... `sprintf()` (or worse). – PaulMcKenzie Feb 10 '15 at 10:48
  • If you need precision you definitely can't rely on floating point numbers: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html – dfranca Feb 10 '15 at 10:53
  • So i wonder how `sprintf` works. – user1024 Feb 10 '15 at 12:07
  • 1
    @PaulMcKenzie Not necessarily. Knowing that he's targeting a `"%E` format, with exactly 10 digits, can avoid a lot of `if`s, `switch`s, etc. Some of which may be in tight loops; knowing that he needs exactly 10 digits, in fact (and no more) means that he can probably completely unroll the loop, with no loop condition, and that he can do everything in integer arithmetic (on `int`; the generic algorithm traditionally requires integer arithmetic on values which don't fit in an `int`). – James Kanze Feb 10 '15 at 13:56
  • [Convert double to string quickly observing given precision](https://stackoverflow.com/q/34744591/995714), [C++: what is the optimal way to convert a double to a string?](https://stackoverflow.com/q/1313988/995714), [double to string without scientific notation or trailing zeros, efficiently](https://stackoverflow.com/q/15165502/995714), [Converting double to char* in C++ with high performance](https://stackoverflow.com/q/10749585/995714) – phuclv Mar 24 '19 at 00:10

1 Answers1

0

The reference document for these conversions is &ldqou;How to Print Floating-Point Numbers Accurately”, by Steele and White; it was published in PLDI '90, and is available on line from the ACM digital library.

Given that you're interested in speed and a fixed format, with 10 digits, a somewhat simpler approach might be to scale the number to the range [1E9...1E10), then use modf to extract the integer part, incrementing it if the fractional part is greater than 0.5. Then convert the integral part to an int, and generate your digits from that. The amount you've scaled it by should give the exponent.

Finding the value to scale it by could be a critical issue; using log10 is an obvious solution, provided it's accurate in your library. Otherwise, you could use a table of powers of ten (calculated once, off line), and a second table, indexed by the exponent (see frexp) into this table. Using this technique, you'd have at most two comparisons to find the correct scaling factor. (Functions like modf and frexp are generally very fast, because they manipulate the bit pattern of the number directly; if they don't, it's easy to do what you need by manipulating the bit pattern of your double yourself.)

This is, of course, just a general suggestion of an approach. The devil is in the details, and it will take a lot of care to get all of the corner cases right. But there is a good chance that it will be faster than sprintf, precisely because it only does this one particular format.

James Kanze
  • 150,581
  • 18
  • 184
  • 329