From c64340a9bb7035b7651748cf1831387be4166bb4 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Wed, 20 Jul 2016 13:15:17 +0200 Subject: [PATCH] Fixed error in float serialization (issue #324) --- CHANGELOG.md | 13 ++- include/ArduinoJson/Internals/JsonWriter.hpp | 37 ++++-- test/JsonWriter_WriteFloat_Tests.cpp | 113 +++++++++++++++++++ 3 files changed, 150 insertions(+), 13 deletions(-) create mode 100644 test/JsonWriter_WriteFloat_Tests.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index b2b0b58e..49e98e86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ ArduinoJson: change log ======================= +HEAD +---- + +* Fixed error in float serialization (issue #324) + v5.6.3 ------ @@ -112,7 +117,7 @@ v5.0.3 v5.0.2 ------ -* Fixed segmentation fault in `parseObject(String)` and `parseArray(String)`, when the +* Fixed segmentation fault in `parseObject(String)` and `parseArray(String)`, when the `StaticJsonBuffer` is too small to hold a copy of the string * Fixed Clang warning "register specifier is deprecated" (issue #102) * Fixed GCC warning "declaration shadows a member" (issue #103) @@ -228,14 +233,14 @@ v3.1 Old generator API: - JsonObject<3> root; + JsonObject<3> root; root.add("sensor", "gps"); root.add("time", 1351824120); root.add("data", array); New generator API: - JsonObject<3> root; + JsonObject<3> root; root["sensor"] = "gps"; root["time"] = 1351824120; root["data"] = array; @@ -292,7 +297,7 @@ v1.1 * Example: changed `char* json` into `char[] json` so that the bytes are not write protected * Fixed parsing bug when the JSON contains multi-dimensional arrays -v1.0 +v1.0 ---- Initial release diff --git a/include/ArduinoJson/Internals/JsonWriter.hpp b/include/ArduinoJson/Internals/JsonWriter.hpp index c7f26b26..8225f951 100644 --- a/include/ArduinoJson/Internals/JsonWriter.hpp +++ b/include/ArduinoJson/Internals/JsonWriter.hpp @@ -81,7 +81,7 @@ class JsonWriter { } } - void writeFloat(JsonFloat value, int digits = 2) { + void writeFloat(JsonFloat value, uint8_t digits = 2) { if (Polyfills::isNaN(value)) return writeRaw("NaN"); if (value < 0.0) { @@ -98,6 +98,9 @@ class JsonWriter { powersOf10 = 0; } + // Round up last digit (so that print(1.999, 2) prints as "2.00") + value += getRoundingBias(digits); + // Extract the integer part of the value and print it JsonUInt int_part = static_cast(value); JsonFloat remainder = value - static_cast(int_part); @@ -115,9 +118,6 @@ class JsonWriter { char currentDigit = char(remainder); remainder -= static_cast(currentDigit); - // Round up last digit (so that print(1.999, 2) prints as "2.00") - if (digits == 0 && remainder >= 0.5) currentDigit++; - // Print writeRaw(char('0' + currentDigit)); } @@ -135,16 +135,15 @@ class JsonWriter { void writeInteger(JsonUInt value) { char buffer[22]; + char *ptr = buffer + sizeof(buffer) - 1; - uint8_t i = 0; + *ptr = 0; do { - buffer[i++] = static_cast(value % 10 + '0'); + *--ptr = static_cast(value % 10 + '0'); value /= 10; } while (value); - while (i > 0) { - writeRaw(buffer[--i]); - } + writeRaw(ptr); } void writeRaw(const char *s) { @@ -160,6 +159,26 @@ class JsonWriter { private: JsonWriter &operator=(const JsonWriter &); // cannot be assigned + + static JsonFloat getLastDigit(uint8_t digits) { + // Designed as a compromise between code size and speed + switch (digits) { + case 0: + return 1e-0; + case 1: + return 1e-1; + case 2: + return 1e-2; + case 3: + return 1e-3; + default: + return getLastDigit(uint8_t(digits - 4)) * 1e-4; + } + } + + FORCE_INLINE static JsonFloat getRoundingBias(uint8_t digits) { + return 0.5 * getLastDigit(digits); + } }; } } diff --git a/test/JsonWriter_WriteFloat_Tests.cpp b/test/JsonWriter_WriteFloat_Tests.cpp new file mode 100644 index 00000000..9fa24a30 --- /dev/null +++ b/test/JsonWriter_WriteFloat_Tests.cpp @@ -0,0 +1,113 @@ +// Copyright Benoit Blanchon 2014-2016 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson +// If you like this project, please add a star! + +#include +#include + +#include +#include + +using namespace ArduinoJson::Internals; + +class JsonWriter_WriteFloat_Tests : public testing::Test { + protected: + void whenInputIs(double input, uint8_t digits = 2) { + StaticStringBuilder sb(buffer, sizeof(buffer)); + JsonWriter writer(sb); + writer.writeFloat(input, digits); + returnValue = writer.bytesWritten(); + } + + void outputMustBe(const char *expected) { + EXPECT_STREQ(expected, buffer); + EXPECT_EQ(strlen(expected), returnValue); + } + + private: + char buffer[1024]; + size_t returnValue; +}; + +TEST_F(JsonWriter_WriteFloat_Tests, NaN) { + whenInputIs(std::numeric_limits::signaling_NaN()); + outputMustBe("NaN"); +} + +TEST_F(JsonWriter_WriteFloat_Tests, PositiveInfinity) { + whenInputIs(std::numeric_limits::infinity()); + outputMustBe("Infinity"); +} + +TEST_F(JsonWriter_WriteFloat_Tests, NegativeInfinity) { + whenInputIs(-std::numeric_limits::infinity()); + outputMustBe("-Infinity"); +} + +TEST_F(JsonWriter_WriteFloat_Tests, Zero) { + whenInputIs(0); + outputMustBe("0.00"); +} + +TEST_F(JsonWriter_WriteFloat_Tests, ZeroDigits_Rounding) { + whenInputIs(9.5, 0); + outputMustBe("10"); +} + +TEST_F(JsonWriter_WriteFloat_Tests, ZeroDigits_NoRounding) { + whenInputIs(9.4, 0); + outputMustBe("9"); +} + +TEST_F(JsonWriter_WriteFloat_Tests, OneDigit_Rounding) { + whenInputIs(9.95, 1); + outputMustBe("10.0"); +} + +TEST_F(JsonWriter_WriteFloat_Tests, OneDigit_NoRounding) { + whenInputIs(9.94, 1); + outputMustBe("9.9"); +} + +TEST_F(JsonWriter_WriteFloat_Tests, TwoDigits_Rounding) { + whenInputIs(9.995, 2); + outputMustBe("10.00"); +} + +TEST_F(JsonWriter_WriteFloat_Tests, TwoDigits_NoRounding) { + whenInputIs(9.994, 2); + outputMustBe("9.99"); +} + +TEST_F(JsonWriter_WriteFloat_Tests, ThreeDigits_Rounding) { + whenInputIs(9.9995, 3); + outputMustBe("10.000"); +} + +TEST_F(JsonWriter_WriteFloat_Tests, ThreeDigits_NoRounding) { + whenInputIs(9.9994, 3); + outputMustBe("9.999"); +} + +TEST_F(JsonWriter_WriteFloat_Tests, FourDigits_Rounding) { + whenInputIs(9.99995, 4); + outputMustBe("10.0000"); +} + +TEST_F(JsonWriter_WriteFloat_Tests, FourDigits_NoRounding) { + whenInputIs(9.99994, 4); + outputMustBe("9.9999"); +} + +TEST_F(JsonWriter_WriteFloat_Tests, FiveDigits_Rounding) { + whenInputIs(9.999995, 5); + outputMustBe("10.00000"); +} + +TEST_F(JsonWriter_WriteFloat_Tests, FiveDigits_NoRounding) { + whenInputIs(9.999994, 5); + outputMustBe("9.99999"); +}