Skip to content

Commit

Permalink
fix: minimize the precision loss when dumping double to string (#712)
Browse files Browse the repository at this point in the history
* fix: minimize the precision loss when dumping double to string
* abandon one-time macro
* replace hard-coded buffer size, replace `sprintf` with `snprintf`
* replace `DECIMAL_DIG` with `DBL_DECIMAL_DIG`
* Update json.h changed to C++11 DECIMAL_DIGIT
* added cfloat include
* identify float and double as different numeral types, and approach their precisions accordingly

---------

Co-authored-by: June Han <[email protected]>
Co-authored-by: gittiver <[email protected]>
  • Loading branch information
3 people authored Jan 29, 2024
1 parent fb171f5 commit 973d5fa
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 14 deletions.
49 changes: 38 additions & 11 deletions include/crow/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <memory>
#include <vector>
#include <cmath>
#include <cfloat>

#include "crow/utility.h"
#include "crow/settings.h"
Expand Down Expand Up @@ -105,7 +106,8 @@ namespace crow
Signed_integer,
Unsigned_integer,
Floating_point,
Null
Null,
Double_precision_floating_point
};

class rvalue;
Expand Down Expand Up @@ -781,6 +783,7 @@ namespace crow
switch (r.nt())
{
case num_type::Floating_point: os << r.d(); break;
case num_type::Double_precision_floating_point: os << r.d(); break;
case num_type::Signed_integer: os << r.i(); break;
case num_type::Unsigned_integer: os << r.u(); break;
case num_type::Null: throw std::runtime_error("Number with num_type Null");
Expand Down Expand Up @@ -1318,7 +1321,9 @@ namespace crow
ui(value) {}
constexpr number(std::int64_t value) noexcept:
si(value) {}
constexpr number(double value) noexcept:
explicit constexpr number(double value) noexcept:
d(value) {}
explicit constexpr number(float value) noexcept:
d(value) {}
} num; ///< Value if type is a number.
std::string s; ///< Value if type is a string.
Expand Down Expand Up @@ -1357,7 +1362,7 @@ namespace crow
wvalue(float value):
returnable("application/json"), t_(type::Number), nt(num_type::Floating_point), num(static_cast<double>(value)) {}
wvalue(double value):
returnable("application/json"), t_(type::Number), nt(num_type::Floating_point), num(static_cast<double>(value)) {}
returnable("application/json"), t_(type::Number), nt(num_type::Double_precision_floating_point), num(static_cast<double>(value)) {}

wvalue(char const* value):
returnable("application/json"), t_(type::String), s(value) {}
Expand Down Expand Up @@ -1408,7 +1413,7 @@ namespace crow
return;
case type::Number:
nt = r.nt();
if (nt == num_type::Floating_point)
if (nt == num_type::Floating_point || nt == num_type::Double_precision_floating_point)
num.d = r.d();
else if (nt == num_type::Signed_integer)
num.si = r.i();
Expand Down Expand Up @@ -1444,7 +1449,7 @@ namespace crow
return;
case type::Number:
nt = r.nt;
if (nt == num_type::Floating_point)
if (nt == num_type::Floating_point || nt == num_type::Double_precision_floating_point)
num.d = r.num.d;
else if (nt == num_type::Signed_integer)
num.si = r.num.si;
Expand Down Expand Up @@ -1514,7 +1519,7 @@ namespace crow
return *this;
}

wvalue& operator=(double value)
wvalue& operator=(float value)
{
reset();
t_ = type::Number;
Expand All @@ -1523,6 +1528,15 @@ namespace crow
return *this;
}

wvalue& operator=(double value)
{
reset();
t_ = type::Number;
num.d = value;
nt = num_type::Double_precision_floating_point;
return *this;
}

wvalue& operator=(unsigned short value)
{
reset();
Expand Down Expand Up @@ -1835,7 +1849,7 @@ namespace crow
case type::True: out += "true"; break;
case type::Number:
{
if (v.nt == num_type::Floating_point)
if (v.nt == num_type::Floating_point || v.nt == num_type::Double_precision_floating_point)
{
if (isnan(v.num.d) || isinf(v.num.d))
{
Expand All @@ -1850,11 +1864,22 @@ namespace crow
zero
} f_state;
char outbuf[128];
if (v.nt == num_type::Double_precision_floating_point)
{
#ifdef _MSC_VER
sprintf_s(outbuf, sizeof(outbuf), "%f", v.num.d);
sprintf_s(outbuf, sizeof(outbuf), "%.*g", DECIMAL_DIG, v.num.d);
#else
snprintf(outbuf, sizeof(outbuf), "%f", v.num.d);
snprintf(outbuf, sizeof(outbuf), "%.*g", DECIMAL_DIG, v.num.d);
#endif
}
else
{
#ifdef _MSC_VER
sprintf_s(outbuf, sizeof(outbuf), "%f", v.num.d);
#else
snprintf(outbuf, sizeof(outbuf), "%f", v.num.d);
#endif
}
char *p = &outbuf[0], *o = nullptr; // o is the position of the first trailing 0
f_state = start;
while (*p != '\0')
Expand Down Expand Up @@ -1968,14 +1993,16 @@ namespace crow
{
int64_t get(int64_t fallback)
{
if (ref.t() != type::Number || ref.nt == num_type::Floating_point)
if (ref.t() != type::Number || ref.nt == num_type::Floating_point ||
ref.nt == num_type::Double_precision_floating_point)
return fallback;
return ref.num.si;
}

double get(double fallback)
{
if (ref.t() != type::Number || ref.nt != num_type::Floating_point)
if (ref.t() != type::Number || ref.nt != num_type::Floating_point ||
ref.nt == num_type::Double_precision_floating_point)
return fallback;
return ref.num.d;
}
Expand Down
2 changes: 1 addition & 1 deletion include/crow/middlewares/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ namespace crow
{
case type::Number:
{
if (rv.nt() == num_type::Floating_point)
if (rv.nt() == num_type::Floating_point || rv.nt() == num_type::Double_precision_floating_point)
return multi_value{rv.d()};
else if (rv.nt() == num_type::Unsigned_integer)
return multi_value{int64_t(rv.u())};
Expand Down
6 changes: 4 additions & 2 deletions tests/unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1038,11 +1038,13 @@ TEST_CASE("json::wvalue::wvalue(float)")

TEST_CASE("json::wvalue::wvalue(double)")
{
double d = 4.2;
double d = 0.036303908355795146;
json::wvalue value = d;

CHECK(value.t() == json::type::Number);
CHECK(value.dump() == "4.2");
auto dumped_value = value.dump();
CROW_LOG_DEBUG << dumped_value;
CHECK(std::abs(utility::lexical_cast<double>(dumped_value) - d) < numeric_limits<double>::epsilon());
} // json::wvalue::wvalue(double)

TEST_CASE("json::wvalue::wvalue(char const*)")
Expand Down

0 comments on commit 973d5fa

Please sign in to comment.