mirror of https://github.com/CrowCpp/Crow.git
fix: minimize the precision loss when dumping double to string (#712)
* 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 <jun_h@pretia.co.jp> Co-authored-by: gittiver <gulliver@traumkristalle.net>
This commit is contained in:
parent
fb171f5195
commit
973d5fa1cd
|
@ -14,6 +14,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cfloat>
|
||||||
|
|
||||||
#include "crow/utility.h"
|
#include "crow/utility.h"
|
||||||
#include "crow/settings.h"
|
#include "crow/settings.h"
|
||||||
|
@ -105,7 +106,8 @@ namespace crow
|
||||||
Signed_integer,
|
Signed_integer,
|
||||||
Unsigned_integer,
|
Unsigned_integer,
|
||||||
Floating_point,
|
Floating_point,
|
||||||
Null
|
Null,
|
||||||
|
Double_precision_floating_point
|
||||||
};
|
};
|
||||||
|
|
||||||
class rvalue;
|
class rvalue;
|
||||||
|
@ -781,6 +783,7 @@ namespace crow
|
||||||
switch (r.nt())
|
switch (r.nt())
|
||||||
{
|
{
|
||||||
case num_type::Floating_point: os << r.d(); break;
|
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::Signed_integer: os << r.i(); break;
|
||||||
case num_type::Unsigned_integer: os << r.u(); break;
|
case num_type::Unsigned_integer: os << r.u(); break;
|
||||||
case num_type::Null: throw std::runtime_error("Number with num_type Null");
|
case num_type::Null: throw std::runtime_error("Number with num_type Null");
|
||||||
|
@ -1318,7 +1321,9 @@ namespace crow
|
||||||
ui(value) {}
|
ui(value) {}
|
||||||
constexpr number(std::int64_t value) noexcept:
|
constexpr number(std::int64_t value) noexcept:
|
||||||
si(value) {}
|
si(value) {}
|
||||||
constexpr number(double value) noexcept:
|
explicit constexpr number(double value) noexcept:
|
||||||
|
d(value) {}
|
||||||
|
explicit constexpr number(float value) noexcept:
|
||||||
d(value) {}
|
d(value) {}
|
||||||
} num; ///< Value if type is a number.
|
} num; ///< Value if type is a number.
|
||||||
std::string s; ///< Value if type is a string.
|
std::string s; ///< Value if type is a string.
|
||||||
|
@ -1357,7 +1362,7 @@ namespace crow
|
||||||
wvalue(float value):
|
wvalue(float 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::Floating_point), num(static_cast<double>(value)) {}
|
||||||
wvalue(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):
|
wvalue(char const* value):
|
||||||
returnable("application/json"), t_(type::String), s(value) {}
|
returnable("application/json"), t_(type::String), s(value) {}
|
||||||
|
@ -1408,7 +1413,7 @@ namespace crow
|
||||||
return;
|
return;
|
||||||
case type::Number:
|
case type::Number:
|
||||||
nt = r.nt();
|
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();
|
num.d = r.d();
|
||||||
else if (nt == num_type::Signed_integer)
|
else if (nt == num_type::Signed_integer)
|
||||||
num.si = r.i();
|
num.si = r.i();
|
||||||
|
@ -1444,7 +1449,7 @@ namespace crow
|
||||||
return;
|
return;
|
||||||
case type::Number:
|
case type::Number:
|
||||||
nt = r.nt;
|
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;
|
num.d = r.num.d;
|
||||||
else if (nt == num_type::Signed_integer)
|
else if (nt == num_type::Signed_integer)
|
||||||
num.si = r.num.si;
|
num.si = r.num.si;
|
||||||
|
@ -1514,7 +1519,7 @@ namespace crow
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
wvalue& operator=(double value)
|
wvalue& operator=(float value)
|
||||||
{
|
{
|
||||||
reset();
|
reset();
|
||||||
t_ = type::Number;
|
t_ = type::Number;
|
||||||
|
@ -1523,6 +1528,15 @@ namespace crow
|
||||||
return *this;
|
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)
|
wvalue& operator=(unsigned short value)
|
||||||
{
|
{
|
||||||
reset();
|
reset();
|
||||||
|
@ -1835,7 +1849,7 @@ namespace crow
|
||||||
case type::True: out += "true"; break;
|
case type::True: out += "true"; break;
|
||||||
case type::Number:
|
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))
|
if (isnan(v.num.d) || isinf(v.num.d))
|
||||||
{
|
{
|
||||||
|
@ -1850,11 +1864,22 @@ namespace crow
|
||||||
zero
|
zero
|
||||||
} f_state;
|
} f_state;
|
||||||
char outbuf[128];
|
char outbuf[128];
|
||||||
|
if (v.nt == num_type::Double_precision_floating_point)
|
||||||
|
{
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
sprintf_s(outbuf, sizeof(outbuf), "%f", v.num.d);
|
sprintf_s(outbuf, sizeof(outbuf), "%.*g", DECIMAL_DIG, v.num.d);
|
||||||
#else
|
#else
|
||||||
snprintf(outbuf, sizeof(outbuf), "%f", v.num.d);
|
snprintf(outbuf, sizeof(outbuf), "%.*g", DECIMAL_DIG, v.num.d);
|
||||||
#endif
|
#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
|
char *p = &outbuf[0], *o = nullptr; // o is the position of the first trailing 0
|
||||||
f_state = start;
|
f_state = start;
|
||||||
while (*p != '\0')
|
while (*p != '\0')
|
||||||
|
@ -1968,14 +1993,16 @@ namespace crow
|
||||||
{
|
{
|
||||||
int64_t get(int64_t fallback)
|
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 fallback;
|
||||||
return ref.num.si;
|
return ref.num.si;
|
||||||
}
|
}
|
||||||
|
|
||||||
double get(double fallback)
|
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 fallback;
|
||||||
return ref.num.d;
|
return ref.num.d;
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@ namespace crow
|
||||||
{
|
{
|
||||||
case type::Number:
|
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()};
|
return multi_value{rv.d()};
|
||||||
else if (rv.nt() == num_type::Unsigned_integer)
|
else if (rv.nt() == num_type::Unsigned_integer)
|
||||||
return multi_value{int64_t(rv.u())};
|
return multi_value{int64_t(rv.u())};
|
||||||
|
|
|
@ -1038,11 +1038,13 @@ TEST_CASE("json::wvalue::wvalue(float)")
|
||||||
|
|
||||||
TEST_CASE("json::wvalue::wvalue(double)")
|
TEST_CASE("json::wvalue::wvalue(double)")
|
||||||
{
|
{
|
||||||
double d = 4.2;
|
double d = 0.036303908355795146;
|
||||||
json::wvalue value = d;
|
json::wvalue value = d;
|
||||||
|
|
||||||
CHECK(value.t() == json::type::Number);
|
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)
|
} // json::wvalue::wvalue(double)
|
||||||
|
|
||||||
TEST_CASE("json::wvalue::wvalue(char const*)")
|
TEST_CASE("json::wvalue::wvalue(char const*)")
|
||||||
|
|
Loading…
Reference in New Issue