diff --git a/extras/tests/JsonSerializer/JsonVariant.cpp b/extras/tests/JsonSerializer/JsonVariant.cpp index 56370282..aa218ed2 100644 --- a/extras/tests/JsonSerializer/JsonVariant.cpp +++ b/extras/tests/JsonSerializer/JsonVariant.cpp @@ -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") { diff --git a/extras/tests/TextFormatter/writeFloat.cpp b/extras/tests/TextFormatter/writeFloat.cpp index db67396a..693f57bf 100644 --- a/extras/tests/TextFormatter/writeFloat.cpp +++ b/extras/tests/TextFormatter/writeFloat.cpp @@ -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") { diff --git a/src/ArduinoJson/Json/TextFormatter.hpp b/src/ArduinoJson/Json/TextFormatter.hpp index 91d55436..a9a499de 100644 --- a/src/ArduinoJson/Json/TextFormatter.hpp +++ b/src/ArduinoJson/Json/TextFormatter.hpp @@ -66,13 +66,16 @@ class TextFormatter { template 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(s), strlen(s)); } diff --git a/src/ArduinoJson/Numbers/FloatParts.hpp b/src/ArduinoJson/Numbers/FloatParts.hpp index aae21e35..901ffef5 100644 --- a/src/ArduinoJson/Numbers/FloatParts.hpp +++ b/src/ArduinoJson/Numbers/FloatParts.hpp @@ -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; - 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