-1

I need to convert integers (which represents decimals but without using decimal places) to doubles in C. I know how many decimal places the integer should have because this information is also there. This new double is handed over to a JSON-API which appends this to a JSON structure afterwards.

Example: I need to produce a double value of 255.89 for an input of int1 := 25589 and int2 := 2

I've written this function:

cJSON* addDecimalToJSON(cJSON* const object, const char * const name, const int number, const int decimals) {
    double d = number;
    for(int i = 0; i < decimals; i++) {
        d = d / 10;
    }
    return addNumberToObject(object, name, d);
}

This works for some values and in the JSON structure is the correct decimal value representation, but for other values (like the example above) the result is a "255.89000000000001".

After trial and error I'm at this point:

double test = 25589;
test = test / 10;
test = test / 10;
cout << test << endl;
-> 255.89
double test2 = 255.89;
cout << (test == test2) << endl;
-> 0
cout << (test2 == 255.89) << endl;
-> 1

If I put test to the JSON API it still produces "255.89000000000001". If I put test2 to the JSON API it produces "255.89". So there must be any kind of precision problem which is "carried" into the API (but which gets cut off from cout function so I cant 'see' it). What I need in the end is the desired decimal value of "255.89" without the precision problem. How can this be achieved?

Lundin
  • 195,001
  • 40
  • 254
  • 396
Martin Fernau
  • 787
  • 1
  • 6
  • 19
  • *Why* do you need the output to be this precise? floating point numbers have by their very nature certain imprecisions in them (since computers need to store them in finite amount of memory) – UnholySheep Feb 11 '22 at 15:07
  • 7
    Obligatory: [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – Retired Ninja Feb 11 '22 at 15:08
  • That's C++ code, not C... Not that it makes a difference with respect to floating point math. – Shawn Feb 11 '22 at 15:13
  • 2
    Comparing floating point values for equality (`==`) rarely turns out well. – Jabberwocky Feb 11 '22 at 15:16
  • 1
    The format most used by C++ implementations for `double` is IEEE-754 “double precision,” also called binary64. It uses powers of two. The number 255.89 **cannot** be represented in this format. The nearest representable value is 255.8899999999999863575794734060764312744140625, which is 2^−45 multiplied by an integer. Only integers multiplied by powers of two can be represented in this format. – Eric Postpischil Feb 11 '22 at 15:18
  • That said, dividing by 10 repeatedly introduces additional errors, one in each multiplication. A better way to do the conversion may be to tack “e-x” into the end of the numeral, where “x” is the number of digits, forming a string such as “25589e-2”, and then parse it with a standard string-to-double routine, letting it do the math. Good standard library implementations will produce a correctly rounded result. Some implementations might not. – Eric Postpischil Feb 11 '22 at 15:19
  • If you need precisely `255.89`, then you cannot use `double`, because `double` cannot represent this number exactly. So you have a choice, use something that is not `double` or give up the exactness requirement. – n. m. could be an AI Feb 11 '22 at 15:21
  • @OP, and no matter how much you may try to massage the `double` to make it fit your requirements, there will be that odd case where all the massaging will not work. Then when you try to get the odd case to work, you break the cases that used to work. You will go nuts trying to get all the output to behave. So as stated previously, use something other than `double`. – PaulMcKenzie Feb 11 '22 at 15:29

1 Answers1

0

The addNumberToObject doesn't let you control how many significant digits you want to print.

You can get around this by using sprintf to format the number yourself and adding it as a string.

cJSON* addDecimalToJSON(cJSON* const object, const char * const name, 
                        const int number, const int decimals) {
    double d = number;
    for(int i = 0; i < decimals; i++) {
        d = d / 10;
    }
    char dstr[50];
    sprintf(dstr,"%.*f", decimals, d);
    return addStringToObject(object, name, dstr);
}
dbush
  • 205,898
  • 23
  • 218
  • 273
  • JSON has the usual format for floating-point numerals. We can eliminate all the arithmetic (and the extra rounding errors introduced by repeated division) and just use `snprintf(dstr, sizeof dstr, "%de%+d", number, -decimals)`. E.g., producing “25589e-2” for the example in the question. – Eric Postpischil Feb 11 '22 at 15:24
  • After reading all above comments I agree that double is not the right data type to use here even if it works if I use the number "directly" (without generate it using maths). Thanks for the example with sprintf/snprintf – Martin Fernau Feb 11 '22 at 16:21