dropbox / json11

A tiny JSON library for C++11.
MIT License
2.55k stars 613 forks source link

floating precision problem #54

Closed wrbuaa2005 closed 8 years ago

wrbuaa2005 commented 8 years ago

Is there any way to output a Json string with fixed precision for doubles? for e.g. if I have some json object like:

{"a":0.123435231, "b":1.238431123}

I only want to keep 5 decimal places when outputting it as a string.

{"a":0.12344, "b": 1.23843}

The goal is to put it into a database. We can save lots of spaces by keeping some decimal places.

j4cbo commented 8 years ago

No, this is not a goal - json11 is designed to round-trip cleanly, not truncate on serialization. You could use a custom type for the objects that need special formatting and implement a to_json() member function, perhaps?

wrbuaa2005 commented 8 years ago

Thanks. The reason I asked is that even if I used sth. like round(num * 100000) / 100000 for num = 1.233491314, this number might be stored as 1.2334900000000002 because of machine error. The only way I can think of to avoid is to convert it to string. But my colleague won't allow me since it will make the program slower.

I'll look into the way to define object of custom type.

artwyman commented 8 years ago

One possible workaround would be to do a hacky version of fixed-point and store your numbers in an int. E.g. if you know you always want exactly 5 decimal places, store 1.23349 as 123349. When storing data to JSON, multiply by 100000 and cast to int. When reading from JSON, cast to double and divide by 100000. This assumes you never need more than 53 bits of integer, and that you won't waste more space by giving every number the full 5 decimal places (e.g. if many of them are integers which would've truncated naturally).

peterritter commented 8 years ago

Hi all

I heavily modified the library for my own purposes. I never subscribed to the philosophy that a json object should not / cannot be modified after it was created so I just changed it to fit my own requirements (in many ways). I also fixed the floating point issue by including a couple libraries. Unfortunately I modified the json11 library so much that it can't be merged anymore. I also don't know how to use github. But here are the libs I used for floating point conversion - it was relatively straight forward to change. Both algorithm don't use the exact same precision, one stops at 17 digits after decimal point, the other at around 25, so roundtrip is not perfect but its good enough for me. This could maybe be adjusted somewhere but I had already spent a day on this so I just let it be.

Regards, Peter

to convert to string I used:

https://github.com/miloyip/dtoa-benchmark/blob/master/src/milo/dtoa_milo.h

to convert from string I used :

/*

//#include "config.h"

ifdef HAVE_STDLIB_H

include

endif

include

include

//extern  int     errno;

ifndef STDC

ifdef GNUC

define const const

else

define const

endif

endif

ifndef TRUE

define TRUE 1

define FALSE 0

endif

ifndef NULL

define NULL 0

endif

static int maxExponent = 511;   /* Largest possible base 10 exponent.  Any
                 * exponent larger than this will already
                 * produce underflow or overflow, so there's
                 * no need to worry about additional digits.
                 */
static double powersOf10[] = {  /* Table giving binary powers of 10.  Entry */
    10.,            /* is 10^2^i.  Used to convert decimal */
    100.,           /* exponents into floating-point numbers. */
    1.0e4,
    1.0e8,
    1.0e16,
    1.0e32,
    1.0e64,
    1.0e128,
    1.0e256
};

/*
 *----------------------------------------------------------------------
 *
 * strtod --
 *
 *  This procedure converts a floating-point number from an ASCII
 *  decimal representation to internal double-precision format.
 *
 * Results:
 *  The return value is the double-precision floating-point
 *  representation of the characters in string.  If endPtr isn't
 *  NULL, then *endPtr is filled in with the address of the
 *  next character after the last one that was part of the
 *  floating-point number.
 *
 * Side effects:
 *  None.
 *
 *----------------------------------------------------------------------
 */

double
    strtod_alt(string, endPtr)
    const char *string;     /* A decimal ASCII floating-point number,
                 * optionally preceded by white space.
                 * Must have form "-I.FE-X", where I is the
                 * integer part of the mantissa, F is the
                 * fractional part of the mantissa, and X
                 * is the exponent.  Either of the signs
                 * may be "+", "-", or omitted.  Either I
                 * or F may be omitted, or both.  The decimal
                 * point isn't necessary unless F is present.
                 * The "E" may actually be an "e".  E and X
                 * may both be omitted (but not just one).
                 */
char **endPtr;      /* If non-NULL, store terminating character's
             * address here. */
{
    int sign, expSign = FALSE;
    double fraction, dblExp, *d;
    register const char *p;
    register int c;
    int exp = 0;        /* Exponent read from "EX" field. */
    int fracExp = 0;        /* Exponent that derives from the fractional
                 * part.  Under normal circumstatnces, it is
                 * the negative of the number of digits in F.
                 * However, if I is very long, the last digits
                 * of I get dropped (otherwise a long I with a
                 * large negative exponent could cause an
                 * unnecessary overflow on I alone).  In this
                 * case, fracExp is incremented one for each
                 * dropped digit. */
    int mantSize;       /* Number of digits in mantissa. */
    int decPt;          /* Number of mantissa digits BEFORE decimal
                 * point. */
    const char *pExp;       /* Temporarily holds location of exponent
                 * in string. */

                 /*
                  * Strip off leading blanks and check for a sign.
                  */

    p = string;
    while (isspace(*p)) {
        p += 1;
    }
    if (*p == '-') {
        sign = TRUE;
        p += 1;
    }
    else {
        if (*p == '+') {
            p += 1;
        }
        sign = FALSE;
    }

    /*
     * Count the number of digits in the mantissa (including the decimal
     * point), and also locate the decimal point.
     */

    decPt = -1;
    for (mantSize = 0; ; mantSize += 1)
    {
        c = *p;
        if (!isdigit(c)) {
            if ((c != '.') || (decPt >= 0)) {
                break;
            }
            decPt = mantSize;
        }
        p += 1;
    }

    /*
     * Now suck up the digits in the mantissa.  Use two integers to
     * collect 9 digits each (this is faster than using floating-point).
     * If the mantissa has more than 18 digits, ignore the extras, since
     * they can't affect the value anyway.
     */

    pExp = p;
    p -= mantSize;
    if (decPt < 0) {
        decPt = mantSize;
    }
    else {
        mantSize -= 1;          /* One of the digits was the point. */
    }
    if (mantSize > 18) {
        fracExp = decPt - 18;
        mantSize = 18;
    }
    else {
        fracExp = decPt - mantSize;
    }
    if (mantSize == 0) {
        fraction = 0.0;
        p = string;
        goto done;
    }
    else {
        int frac1, frac2;
        frac1 = 0;
        for (; mantSize > 9; mantSize -= 1)
        {
            c = *p;
            p += 1;
            if (c == '.') {
                c = *p;
                p += 1;
            }
            frac1 = 10 * frac1 + (c - '0');
        }
        frac2 = 0;
        for (; mantSize > 0; mantSize -= 1)
        {
            c = *p;
            p += 1;
            if (c == '.') {
                c = *p;
                p += 1;
            }
            frac2 = 10 * frac2 + (c - '0');
        }
        fraction = (1.0e9 * frac1) + frac2;
    }

    /*
     * Skim off the exponent.
     */

    p = pExp;
    if ((*p == 'E') || (*p == 'e')) {
        p += 1;
        if (*p == '-') {
            expSign = TRUE;
            p += 1;
        }
        else {
            if (*p == '+') {
                p += 1;
            }
            expSign = FALSE;
        }
        while (isdigit(*p)) {
            exp = exp * 10 + (*p - '0');
            p += 1;
        }
    }
    if (expSign) {
        exp = fracExp - exp;
    }
    else {
        exp = fracExp + exp;
    }

    /*
     * Generate a floating-point number that represents the exponent.
     * Do this by processing the exponent one bit at a time to combine
     * many powers of 2 of 10. Then combine the exponent with the
     * fraction.
     */

    if (exp < 0) {
        expSign = TRUE;
        exp = -exp;
    }
    else {
        expSign = FALSE;
    }
    if (exp > maxExponent) {
        exp = maxExponent;
        errno = ERANGE;
    }
    dblExp = 1.0;
    for (d = powersOf10; exp != 0; exp >>= 1, d += 1) {
        if (exp & 01) {
            dblExp *= *d;
        }
    }
    if (expSign) {
        fraction /= dblExp;
    }
    else {
        fraction *= dblExp;
    }

done:
    if (endPtr != NULL) {
        *endPtr = (char *)p;
    }

    if (sign) {
        return -fraction;
    }
    return fraction;
}