mirror of
https://github.com/bblanchon/ArduinoJson.git
synced 2026-07-07 08:50:54 +02:00
Rewrite the float-to-string conversion
Instead of committing to a number of decimal places, the new algorithm targets a certain number of significant digits. Since the mantissa has to fit in a 32-bit integer, the number of significant digits is limited to 9.
This commit is contained in:
@@ -51,10 +51,15 @@ TEST_CASE("serializeJson(JsonVariant)") {
|
||||
|
||||
SECTION("double") {
|
||||
CHECK(serialize(0.0) == "0");
|
||||
CHECK(serialize(-0.0) == "0");
|
||||
CHECK(serialize(10.0) == "10");
|
||||
CHECK(serialize(100.0) == "100");
|
||||
CHECK(serialize(0.1) == "0.1");
|
||||
CHECK(serialize(0.01) == "0.01");
|
||||
CHECK(serialize(3.1415927) == "3.1415927");
|
||||
CHECK(serialize(-3.1415927) == "-3.1415927");
|
||||
CHECK(serialize(1.7976931348623157E+308) == "1.797693135e308");
|
||||
CHECK(serialize(4.94065645841247e-324) == "4.940656458e-324");
|
||||
CHECK(serialize(1.7976931348623157E+308) == "1.79769313e308");
|
||||
CHECK(serialize(4.94065645841247e-324) == "4.94065646e-324");
|
||||
}
|
||||
|
||||
SECTION("float") {
|
||||
|
||||
@@ -24,7 +24,7 @@ static std::string toString(TFloat input) {
|
||||
|
||||
TEST_CASE("TextFormatter::writeFloat(double)") {
|
||||
SECTION("Pi") {
|
||||
REQUIRE(toString(3.14159265359) == "3.141592654");
|
||||
REQUIRE(toString(3.14159265359) == "3.14159265");
|
||||
}
|
||||
|
||||
SECTION("Signaling NaN") {
|
||||
@@ -49,13 +49,13 @@ TEST_CASE("TextFormatter::writeFloat(double)") {
|
||||
}
|
||||
|
||||
SECTION("Espilon") {
|
||||
REQUIRE(toString(2.2250738585072014E-308) == "2.225073859e-308");
|
||||
REQUIRE(toString(-2.2250738585072014E-308) == "-2.225073859e-308");
|
||||
REQUIRE(toString(2.2250738585072014E-308) == "2.22507386e-308");
|
||||
REQUIRE(toString(-2.2250738585072014E-308) == "-2.22507386e-308");
|
||||
}
|
||||
|
||||
SECTION("Max double") {
|
||||
REQUIRE(toString(1.7976931348623157E+308) == "1.797693135e308");
|
||||
REQUIRE(toString(-1.7976931348623157E+308) == "-1.797693135e308");
|
||||
REQUIRE(toString(1.7976931348623157E+308) == "1.79769313e308");
|
||||
REQUIRE(toString(-1.7976931348623157E+308) == "-1.79769313e308");
|
||||
}
|
||||
|
||||
SECTION("Big exponent") {
|
||||
@@ -72,10 +72,10 @@ TEST_CASE("TextFormatter::writeFloat(double)") {
|
||||
}
|
||||
|
||||
SECTION("Exponentation when >= 1e7") {
|
||||
REQUIRE(toString(9999999.999) == "9999999.999");
|
||||
REQUIRE(toString(9999999.99) == "9999999.99");
|
||||
REQUIRE(toString(10000000.0) == "1e7");
|
||||
|
||||
REQUIRE(toString(-9999999.999) == "-9999999.999");
|
||||
REQUIRE(toString(-9999999.99) == "-9999999.99");
|
||||
REQUIRE(toString(-10000000.0) == "-1e7");
|
||||
}
|
||||
|
||||
@@ -85,12 +85,20 @@ TEST_CASE("TextFormatter::writeFloat(double)") {
|
||||
REQUIRE(toString(0.9999999996) == "1");
|
||||
}
|
||||
|
||||
SECTION("9 decimal places") {
|
||||
REQUIRE(toString(0.10000001) == "0.10000001");
|
||||
REQUIRE(toString(0.99999999) == "0.99999999");
|
||||
|
||||
REQUIRE(toString(9.00000001) == "9.00000001");
|
||||
REQUIRE(toString(9.99999999) == "9.99999999");
|
||||
}
|
||||
|
||||
SECTION("9 decimal places") {
|
||||
REQUIRE(toString(0.100000001) == "0.100000001");
|
||||
REQUIRE(toString(0.999999999) == "0.999999999");
|
||||
|
||||
REQUIRE(toString(9.000000001) == "9.000000001");
|
||||
REQUIRE(toString(9.999999999) == "9.999999999");
|
||||
REQUIRE(toString(9.000000001) == "9");
|
||||
REQUIRE(toString(9.999999999) == "10");
|
||||
}
|
||||
|
||||
SECTION("10 decimal places") {
|
||||
|
||||
@@ -66,13 +66,16 @@ class TextFormatter {
|
||||
|
||||
template <typename T>
|
||||
void writeFloat(T value) {
|
||||
writeFloat(JsonFloat(value), sizeof(T) >= 8 ? 9 : 6);
|
||||
writeFloat(JsonFloat(value), sizeof(T) >= 8 ? 9 : 7);
|
||||
}
|
||||
|
||||
void writeFloat(JsonFloat value, int8_t decimalPlaces) {
|
||||
if (isnan(value))
|
||||
return writeRaw(ARDUINOJSON_ENABLE_NAN ? "NaN" : "null");
|
||||
|
||||
if (!value)
|
||||
return writeRaw("0");
|
||||
|
||||
#if ARDUINOJSON_ENABLE_INFINITY
|
||||
if (value < 0.0) {
|
||||
writeRaw('-');
|
||||
@@ -93,9 +96,28 @@ class TextFormatter {
|
||||
|
||||
auto parts = decomposeFloat(value, decimalPlaces);
|
||||
|
||||
writeInteger(parts.integral);
|
||||
if (parts.decimalPlaces)
|
||||
writeDecimals(parts.decimal, parts.decimalPlaces);
|
||||
// buffer should be big enough for all digits and the dot
|
||||
char buffer[32];
|
||||
char* end = buffer + sizeof(buffer);
|
||||
char* begin = end;
|
||||
|
||||
// write the string in reverse order
|
||||
while (parts.mantissa != 0 || parts.pointIndex > 0) {
|
||||
*--begin = char(parts.mantissa % 10 + '0');
|
||||
parts.mantissa /= 10;
|
||||
if (parts.pointIndex == 1) {
|
||||
*--begin = '.';
|
||||
}
|
||||
parts.pointIndex--;
|
||||
}
|
||||
|
||||
// Avoid a leading dot
|
||||
if (parts.pointIndex == 0) {
|
||||
*--begin = '0';
|
||||
}
|
||||
|
||||
// and dump it in the right order
|
||||
writeRaw(begin, end);
|
||||
|
||||
if (parts.exponent) {
|
||||
writeRaw('e');
|
||||
@@ -132,23 +154,6 @@ class TextFormatter {
|
||||
writeRaw(begin, end);
|
||||
}
|
||||
|
||||
void writeDecimals(uint32_t value, int8_t width) {
|
||||
// buffer should be big enough for all digits and the dot
|
||||
char buffer[16];
|
||||
char* end = buffer + sizeof(buffer);
|
||||
char* begin = end;
|
||||
|
||||
// write the string in reverse order
|
||||
while (width--) {
|
||||
*--begin = char(value % 10 + '0');
|
||||
value /= 10;
|
||||
}
|
||||
*--begin = '.';
|
||||
|
||||
// and dump it in the right order
|
||||
writeRaw(begin, end);
|
||||
}
|
||||
|
||||
void writeRaw(const char* s) {
|
||||
writer_.write(reinterpret_cast<const uint8_t*>(s), strlen(s));
|
||||
}
|
||||
|
||||
@@ -13,29 +13,32 @@
|
||||
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||
|
||||
struct FloatParts {
|
||||
uint32_t integral;
|
||||
uint32_t decimal;
|
||||
uint32_t mantissa;
|
||||
int16_t exponent;
|
||||
int8_t decimalPlaces;
|
||||
int8_t pointIndex;
|
||||
};
|
||||
|
||||
constexpr uint32_t pow10(int exponent) {
|
||||
return (exponent == 0) ? 1 : 10 * pow10(exponent - 1);
|
||||
}
|
||||
|
||||
inline FloatParts decomposeFloat(JsonFloat value, int8_t decimalPlaces) {
|
||||
ARDUINOJSON_ASSERT(value >= 0);
|
||||
ARDUINOJSON_ASSERT(decimalPlaces >= 0);
|
||||
inline FloatParts decomposeFloat(JsonFloat value, int8_t significantDigits) {
|
||||
ARDUINOJSON_ASSERT(value > 0);
|
||||
ARDUINOJSON_ASSERT(significantDigits > 1);
|
||||
ARDUINOJSON_ASSERT(significantDigits <= 9); // to prevent uint32_t overflow
|
||||
|
||||
using traits = FloatTraits<JsonFloat>;
|
||||
|
||||
uint32_t maxDecimalPart = pow10(decimalPlaces);
|
||||
bool useScientificNotation =
|
||||
value >= ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD ||
|
||||
value <= ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD;
|
||||
|
||||
int16_t exponent = 0;
|
||||
int8_t index = traits::binaryPowersOfTenArraySize - 1;
|
||||
int bit = 1 << index;
|
||||
|
||||
if (value >= ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD) {
|
||||
// Normalize value to range [1..10) and compute exponent
|
||||
if (value > 1) {
|
||||
for (; index >= 0; index--) {
|
||||
if (value >= traits::positiveBinaryPowersOfTen()[index]) {
|
||||
value *= traits::negativeBinaryPowersOfTen()[index];
|
||||
@@ -44,8 +47,8 @@ inline FloatParts decomposeFloat(JsonFloat value, int8_t decimalPlaces) {
|
||||
bit >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (value > 0 && value <= ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD) {
|
||||
ARDUINOJSON_ASSERT(value < 10);
|
||||
if (value < 1) {
|
||||
for (; index >= 0; index--) {
|
||||
if (value < traits::negativeBinaryPowersOfTen()[index] * 10) {
|
||||
value *= traits::positiveBinaryPowersOfTen()[index];
|
||||
@@ -54,39 +57,36 @@ inline FloatParts decomposeFloat(JsonFloat value, int8_t decimalPlaces) {
|
||||
bit >>= 1;
|
||||
}
|
||||
}
|
||||
ARDUINOJSON_ASSERT(value >= 1);
|
||||
// ARDUINOJSON_ASSERT(value < 10);
|
||||
|
||||
uint32_t integral = uint32_t(value);
|
||||
// reduce number of decimal places by the number of integral places
|
||||
for (uint32_t tmp = integral; tmp >= 10; tmp /= 10) {
|
||||
maxDecimalPart /= 10;
|
||||
decimalPlaces--;
|
||||
}
|
||||
value *= JsonFloat(pow10(significantDigits - 1));
|
||||
|
||||
JsonFloat remainder =
|
||||
(value - JsonFloat(integral)) * JsonFloat(maxDecimalPart);
|
||||
auto mantissa = uint32_t(value);
|
||||
ARDUINOJSON_ASSERT(mantissa > 0);
|
||||
|
||||
uint32_t decimal = uint32_t(remainder);
|
||||
remainder = remainder - JsonFloat(decimal);
|
||||
// rounding
|
||||
auto remainder = value - JsonFloat(mantissa);
|
||||
if (remainder >= 0.5)
|
||||
mantissa++;
|
||||
|
||||
// rounding:
|
||||
// increment by 1 if remainder >= 0.5
|
||||
decimal += uint32_t(remainder * 2);
|
||||
if (decimal >= maxDecimalPart) {
|
||||
decimal = 0;
|
||||
integral++;
|
||||
if (exponent && integral >= 10) {
|
||||
exponent++;
|
||||
integral = 1;
|
||||
}
|
||||
auto pointIndex = int8_t(significantDigits - 1);
|
||||
|
||||
if (!useScientificNotation) {
|
||||
pointIndex = int8_t(pointIndex - int8_t(exponent));
|
||||
exponent = 0;
|
||||
}
|
||||
|
||||
// remove trailing zeros
|
||||
while (decimal % 10 == 0 && decimalPlaces > 0) {
|
||||
decimal /= 10;
|
||||
decimalPlaces--;
|
||||
while (mantissa % 10 == 0 && (useScientificNotation || pointIndex > 0)) {
|
||||
mantissa /= 10;
|
||||
if (pointIndex > 0)
|
||||
pointIndex--;
|
||||
else
|
||||
exponent++;
|
||||
}
|
||||
|
||||
return {integral, decimal, exponent, decimalPlaces};
|
||||
return {mantissa, exponent, pointIndex};
|
||||
}
|
||||
|
||||
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||
|
||||
Reference in New Issue
Block a user