From a13879196408e1ffb10149c528933b1d7c6d40d3 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Thu, 28 Apr 2016 18:54:14 +0200 Subject: [PATCH] Added custom implementation of `ftoa` (issues #266, #267, #269 and #270) --- CHANGELOG.md | 3 +- include/ArduinoJson/Arduino/Print.hpp | 46 ------------- include/ArduinoJson/Internals/JsonWriter.hpp | 69 ++++++++++++++++++- include/ArduinoJson/Internals/Parse.hpp | 15 ++++ include/ArduinoJson/JsonVariant.hpp | 52 ++++++++++---- include/ArduinoJson/JsonVariant.ipp | 21 +++++- include/ArduinoJson/Polyfills/isInfinity.hpp | 45 ++++++++++++ include/ArduinoJson/Polyfills/isNaN.hpp | 46 +++++++++++++ include/ArduinoJson/Polyfills/normalize.hpp | 47 +++++++++++++ include/ArduinoJson/TypeTraits/IsIntegral.hpp | 23 ++----- .../TypeTraits/IsSignedIntegral.hpp | 33 +++++++++ .../TypeTraits/IsUnsignedIntegral.hpp | 33 +++++++++ scripts/create-build-envs.sh | 4 +- test/Issue67.cpp | 48 ------------- test/JsonVariant_PrintTo_Tests.cpp | 50 ++++++++++++-- 15 files changed, 396 insertions(+), 139 deletions(-) create mode 100644 include/ArduinoJson/Polyfills/isInfinity.hpp create mode 100644 include/ArduinoJson/Polyfills/isNaN.hpp create mode 100644 include/ArduinoJson/Polyfills/normalize.hpp create mode 100644 include/ArduinoJson/TypeTraits/IsSignedIntegral.hpp create mode 100644 include/ArduinoJson/TypeTraits/IsUnsignedIntegral.hpp delete mode 100644 test/Issue67.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index f71d1b57..526c5744 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ ArduinoJson: change log HEAD ---- -* Fix `unsigned long` printed as `signed long` (issue #170) +* Added custom implementation of `ftoa` (issues #266, #267, #269 and #270) +* Fixed `unsigned long` printed as `signed long` (issue #170) v5.2.0 ------ diff --git a/include/ArduinoJson/Arduino/Print.hpp b/include/ArduinoJson/Arduino/Print.hpp index 77592355..a68f8d5e 100644 --- a/include/ArduinoJson/Arduino/Print.hpp +++ b/include/ArduinoJson/Arduino/Print.hpp @@ -9,19 +9,8 @@ #ifndef ARDUINO -#include "../Internals/JsonFloat.hpp" -#include "../Internals/JsonInteger.hpp" - #include #include -#include - -#if defined(_MSC_VER) && _MSC_VER <= 1800 -// snprintf has been added in Visual Studio 2015 -#define ARDUINOJSON_SNPRINTF _snprintf -#else -#define ARDUINOJSON_SNPRINTF snprintf -#endif // This class reproduces Arduino's Print class class Print { @@ -38,41 +27,6 @@ class Print { return n; } - size_t print(ArduinoJson::Internals::JsonFloat value, int digits = 2) { - char tmp[32]; - - // https://github.com/arduino/Arduino/blob/db8cbf24c99dc930b9ccff1a43d018c81f178535/hardware/arduino/sam/cores/arduino/Print.cpp#L220 - bool isBigDouble = value > 4294967040.0 || value < -4294967040.0; - - if (isBigDouble) { - // Arduino's implementation prints "ovf" - // We prefer using the scientific notation, since we have sprintf - ARDUINOJSON_SNPRINTF(tmp, sizeof(tmp), "%g", value); - } else { - // Here we have the exact same output as Arduino's implementation - ARDUINOJSON_SNPRINTF(tmp, sizeof(tmp), "%.*f", digits, value); - } - - return print(tmp); - } - - size_t print(ArduinoJson::Internals::JsonUInt value) { - char buffer[22]; - - uint8_t i = 0; - do { - buffer[i++] = static_cast(value % 10 + '0'); - value /= 10; - } while (value); - - size_t n = 0; - while (i > 0) { - n += write(buffer[--i]); - } - - return n; - } - size_t println() { return write('\r') + write('\n'); } }; diff --git a/include/ArduinoJson/Internals/JsonWriter.hpp b/include/ArduinoJson/Internals/JsonWriter.hpp index 53bd105c..fd4b55c3 100644 --- a/include/ArduinoJson/Internals/JsonWriter.hpp +++ b/include/ArduinoJson/Internals/JsonWriter.hpp @@ -8,6 +8,9 @@ #pragma once #include "../Arduino/Print.hpp" +#include "../Polyfills/isNaN.hpp" +#include "../Polyfills/isInfinity.hpp" +#include "../Polyfills/normalize.hpp" #include "Encoding.hpp" #include "ForceInline.hpp" #include "JsonFloat.hpp" @@ -63,10 +66,70 @@ class JsonWriter { } } - void writeInteger(JsonUInt value) { _length += _sink.print(value); } + void writeFloat(JsonFloat value, int digits = 2) { + if (Polyfills::isNaN(value)) return writeRaw("NaN"); - void writeFloat(JsonFloat value, uint8_t decimals) { - _length += _sink.print(value, decimals); + if (value < 0.0) { + writeRaw('-'); + value = -value; + } + + if (Polyfills::isInfinity(value)) return writeRaw("Infinity"); + + short powersOf10; + if (value > 1000 || value < 0.001) { + powersOf10 = Polyfills::normalize(value); + } else { + powersOf10 = 0; + } + + // Round correctly so that print(1.999, 2) prints as "2.00" + JsonFloat rounding = 0.5; + for (uint8_t i = 0; i < digits; ++i) rounding /= 10.0; + + value += rounding; + + // Extract the integer part of the value and print it + JsonUInt int_part = static_cast(value); + JsonFloat remainder = value - static_cast(int_part); + writeInteger(int_part); + + // Print the decimal point, but only if there are digits beyond + if (digits > 0) { + writeRaw('.'); + } + + // Extract digits from the remainder one at a time + while (digits-- > 0) { + remainder *= 10.0; + JsonUInt toPrint = JsonUInt(remainder); + writeInteger(JsonUInt(remainder)); + remainder -= static_cast(toPrint); + } + + if (powersOf10 < 0) { + writeRaw("e-"); + writeInteger(-powersOf10); + } + + if (powersOf10 > 0) { + writeRaw('e'); + writeInteger(powersOf10); + } + } + + void writeInteger(JsonUInt value) { + char buffer[22]; + + uint8_t i = 0; + do { + buffer[i++] = static_cast(value % 10 + '0'); + value /= 10; + } while (value); + + while (i > 0) { + writeRaw(buffer[--i]); + } } void writeRaw(const char *s) { _length += _sink.print(s); } diff --git a/include/ArduinoJson/Internals/Parse.hpp b/include/ArduinoJson/Internals/Parse.hpp index 8d8046d4..6a59e8d6 100644 --- a/include/ArduinoJson/Internals/Parse.hpp +++ b/include/ArduinoJson/Internals/Parse.hpp @@ -29,6 +29,11 @@ inline long parse(const char *s) { return strtol(s, NULL, 10); } +template <> +inline unsigned long parse(const char *s) { + return strtoul(s, NULL, 10); +} + template <> inline int parse(const char *s) { return atoi(s); @@ -39,6 +44,11 @@ template <> inline long long parse(const char *s) { return strtoll(s, NULL, 10); } + +template <> +inline unsigned long long parse(const char *s) { + return strtoull(s, NULL, 10); +} #endif #if ARDUINOJSON_USE_INT64 @@ -46,6 +56,11 @@ template <> inline __int64 parse<__int64>(const char *s) { return _strtoi64(s, NULL, 10); } + +template <> +inline unsigned __int64 parse(const char *s) { + return _strtoui64(s, NULL, 10); +} #endif } } diff --git a/include/ArduinoJson/JsonVariant.hpp b/include/ArduinoJson/JsonVariant.hpp index d1d384f4..79e48bea 100644 --- a/include/ArduinoJson/JsonVariant.hpp +++ b/include/ArduinoJson/JsonVariant.hpp @@ -63,23 +63,33 @@ class JsonVariant : public JsonVariantBase { } // Create a JsonVariant containing an integer value. - // JsonVariant(short) - // JsonVariant(int) - // JsonVariant(long) + // JsonVariant(signed short) + // JsonVariant(signed int) + // JsonVariant(signed long) template FORCE_INLINE JsonVariant( - T value, - typename TypeTraits::EnableIf::value>::type * = - 0) { + T value, typename TypeTraits::EnableIf< + TypeTraits::IsSignedIntegral::value>::type * = 0) { using namespace Internals; if (value >= 0) { _type = JSON_POSITIVE_INTEGER; - _content.asInteger = static_cast(value); + _content.asInteger = static_cast(value); } else { _type = JSON_NEGATIVE_INTEGER; - _content.asInteger = static_cast(-value); + _content.asInteger = static_cast(-value); } } + // JsonVariant(unsigned short) + // JsonVariant(unsigned int) + // JsonVariant(unsigned long) + template + FORCE_INLINE JsonVariant( + T value, typename TypeTraits::EnableIf< + TypeTraits::IsUnsignedIntegral::value>::type * = 0) { + using namespace Internals; + _type = JSON_POSITIVE_INTEGER; + _content.asInteger = static_cast(value); + } // Create a JsonVariant containing a string. FORCE_INLINE JsonVariant(const char *value); @@ -95,15 +105,26 @@ class JsonVariant : public JsonVariantBase { // Get the variant as the specified type. // - // short as() const; - // int as() const; - // long as() const; + // short as() const; + // int as() const; + // long as() const; template - const typename TypeTraits::EnableIf::value, T>::type + const typename TypeTraits::EnableIf::value, + T>::type as() const { return static_cast(asInteger()); } // + // short as() const; + // int as() const; + // long as() const; + template + const typename TypeTraits::EnableIf::value, + T>::type + as() const { + return static_cast(asUnsignedInteger()); + } + // // double as() const; // float as() const; template @@ -247,9 +268,16 @@ class JsonVariant : public JsonVariantBase { JsonObject &asObject() const; private: + // It's not allowed to store a char + template + FORCE_INLINE JsonVariant(T value, + typename TypeTraits::EnableIf< + TypeTraits::IsSame::value>::type * = 0); + String toString() const; Internals::JsonFloat asFloat() const; Internals::JsonInteger asInteger() const; + Internals::JsonUInt asUnsignedInteger() const; bool isBoolean() const; bool isFloat() const; bool isInteger() const; diff --git a/include/ArduinoJson/JsonVariant.ipp b/include/ArduinoJson/JsonVariant.ipp index 46697ad3..ecd9bde4 100644 --- a/include/ArduinoJson/JsonVariant.ipp +++ b/include/ArduinoJson/JsonVariant.ipp @@ -55,7 +55,7 @@ inline Internals::JsonInteger JsonVariant::asInteger() const { case JSON_BOOLEAN: return _content.asInteger; case JSON_NEGATIVE_INTEGER: - return -_content.asInteger; + return -static_cast(_content.asInteger); case JSON_STRING: case JSON_UNPARSED: if (!_content.asString) return 0; @@ -66,6 +66,25 @@ inline Internals::JsonInteger JsonVariant::asInteger() const { } } +inline Internals::JsonUInt JsonVariant::asUnsignedInteger() const { + using namespace Internals; + switch (_type) { + case JSON_UNDEFINED: + return 0; + case JSON_POSITIVE_INTEGER: + case JSON_BOOLEAN: + case JSON_NEGATIVE_INTEGER: + return _content.asInteger; + case JSON_STRING: + case JSON_UNPARSED: + if (!_content.asString) return 0; + if (!strcmp("true", _content.asString)) return 1; + return parse(_content.asString); + default: + return static_cast(_content.asFloat); + } +} + #if ARDUINOJSON_ENABLE_STD_STREAM inline std::ostream &operator<<(std::ostream &os, const JsonVariant &source) { return source.printTo(os); diff --git a/include/ArduinoJson/Polyfills/isInfinity.hpp b/include/ArduinoJson/Polyfills/isInfinity.hpp new file mode 100644 index 00000000..b3b3b5a3 --- /dev/null +++ b/include/ArduinoJson/Polyfills/isInfinity.hpp @@ -0,0 +1,45 @@ +// Copyright Benoit Blanchon 2014-2016 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson +// If you like this project, please add a star! + +#pragma once + +// If Visual Studo <= 2012 +#if defined(_MSC_VER) && _MSC_VER <= 1700 +#include +#else +#include +#endif + +namespace ArduinoJson { +namespace Polyfills { + +// If Visual Studo <= 2012 +#if defined(_MSC_VER) && _MSC_VER <= 1700 +template +bool isInfinity(T x) { + return !_finite(x); +} +#else +template +bool isInfinity(T x) { + return isinf(x); +} + +#ifdef __GLIBC__ +template <> +inline bool isInfinity(double x) { + return isinfl(x); +} + +template <> +inline bool isInfinity(float x) { + return isinff(x); +} +#endif +#endif +} +} diff --git a/include/ArduinoJson/Polyfills/isNaN.hpp b/include/ArduinoJson/Polyfills/isNaN.hpp new file mode 100644 index 00000000..0fb72577 --- /dev/null +++ b/include/ArduinoJson/Polyfills/isNaN.hpp @@ -0,0 +1,46 @@ +// Copyright Benoit Blanchon 2014-2016 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson +// If you like this project, please add a star! + +#pragma once + +// If Visual Studo <= 2012 +#if defined(_MSC_VER) && _MSC_VER <= 1700 +#include +#else +#include +#endif + +namespace ArduinoJson { +namespace Polyfills { + +// If Visual Studo <= 2012 +#if defined(_MSC_VER) && _MSC_VER <= 1700 +template +bool isNaN(T x) { + return _isnan(x) != 0; +} +#else +template +bool isNaN(T x) { + return isnan(x); +} + +#ifdef __GLIBC__ +template <> +inline bool isNaN(double x) { + return isnanl(x); +} + +template <> +inline bool isNaN(float x) { + return isnanf(x); +} +#endif + +#endif +} +} diff --git a/include/ArduinoJson/Polyfills/normalize.hpp b/include/ArduinoJson/Polyfills/normalize.hpp new file mode 100644 index 00000000..5aea7f13 --- /dev/null +++ b/include/ArduinoJson/Polyfills/normalize.hpp @@ -0,0 +1,47 @@ +// Copyright Benoit Blanchon 2014-2016 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson +// If you like this project, please add a star! + +#pragma once + +namespace ArduinoJson { +namespace Polyfills { + +#ifdef ARDUINO + +// on embedded platform, favor code size over speed + +template +short normalize(T& value) { + short powersOf10 = 0; + while (value && value < 1) { + powersOf10--; + value *= 10; + } + while (value > 10) { + powersOf10++; + value /= 10; + } + return powersOf10; +} + +#else + +// on non-embedded platform, favor speed over code size + +template +short normalize(T& value) { + if (value == 0.0) return 0; + + short powersOf10 = static_cast(floor(log10(value))); + value /= pow(T(10), powersOf10); + + return powersOf10; +} + +#endif +} +} diff --git a/include/ArduinoJson/TypeTraits/IsIntegral.hpp b/include/ArduinoJson/TypeTraits/IsIntegral.hpp index 53183d5c..9bad95cd 100644 --- a/include/ArduinoJson/TypeTraits/IsIntegral.hpp +++ b/include/ArduinoJson/TypeTraits/IsIntegral.hpp @@ -9,8 +9,8 @@ #include "../Configuration.hpp" #include "IsSame.hpp" - -#include +#include "IsSignedIntegral.hpp" +#include "IsUnsignedIntegral.hpp" namespace ArduinoJson { namespace TypeTraits { @@ -18,23 +18,8 @@ namespace TypeTraits { // A meta-function that returns true if T is an integral type. template struct IsIntegral { - static const bool value = TypeTraits::IsSame::value || - TypeTraits::IsSame::value || - TypeTraits::IsSame::value || - TypeTraits::IsSame::value || - TypeTraits::IsSame::value || - TypeTraits::IsSame::value || - TypeTraits::IsSame::value || - TypeTraits::IsSame::value || -#if ARDUINOJSON_USE_LONG_LONG - TypeTraits::IsSame::value || - TypeTraits::IsSame::value || -#endif - -#if ARDUINOJSON_USE_INT64 - TypeTraits::IsSame::value || - TypeTraits::IsSame::value || -#endif + static const bool value = TypeTraits::IsSignedIntegral::value || + TypeTraits::IsUnsignedIntegral::value || TypeTraits::IsSame::value; }; } diff --git a/include/ArduinoJson/TypeTraits/IsSignedIntegral.hpp b/include/ArduinoJson/TypeTraits/IsSignedIntegral.hpp new file mode 100644 index 00000000..7973dd75 --- /dev/null +++ b/include/ArduinoJson/TypeTraits/IsSignedIntegral.hpp @@ -0,0 +1,33 @@ +// Copyright Benoit Blanchon 2014-2016 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson +// If you like this project, please add a star! + +#pragma once + +#include "../Configuration.hpp" +#include "IsSame.hpp" + +namespace ArduinoJson { +namespace TypeTraits { + +// A meta-function that returns true if T is an integral type. +template +struct IsSignedIntegral { + static const bool value = TypeTraits::IsSame::value || + TypeTraits::IsSame::value || + TypeTraits::IsSame::value || + TypeTraits::IsSame::value || +#if ARDUINOJSON_USE_LONG_LONG + TypeTraits::IsSame::value || +#endif + +#if ARDUINOJSON_USE_INT64 + TypeTraits::IsSame::value || +#endif + false; +}; +} +} diff --git a/include/ArduinoJson/TypeTraits/IsUnsignedIntegral.hpp b/include/ArduinoJson/TypeTraits/IsUnsignedIntegral.hpp new file mode 100644 index 00000000..21d9ad50 --- /dev/null +++ b/include/ArduinoJson/TypeTraits/IsUnsignedIntegral.hpp @@ -0,0 +1,33 @@ +// Copyright Benoit Blanchon 2014-2016 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson +// If you like this project, please add a star! + +#pragma once + +#include "../Configuration.hpp" +#include "IsSame.hpp" + +namespace ArduinoJson { +namespace TypeTraits { + +// A meta-function that returns true if T is an integral type. +template +struct IsUnsignedIntegral { + static const bool value = TypeTraits::IsSame::value || + TypeTraits::IsSame::value || + TypeTraits::IsSame::value || + TypeTraits::IsSame::value || +#if ARDUINOJSON_USE_LONG_LONG + TypeTraits::IsSame::value || +#endif + +#if ARDUINOJSON_USE_INT64 + TypeTraits::IsSame::value || +#endif + false; +}; +} +} diff --git a/scripts/create-build-envs.sh b/scripts/create-build-envs.sh index 9617a04d..d5750a40 100755 --- a/scripts/create-build-envs.sh +++ b/scripts/create-build-envs.sh @@ -21,9 +21,9 @@ if [[ $(uname) == MINGW* ]] then build-env "Make" "MinGW Makefiles" build-env "SublimeText" "Sublime Text 2 - Ninja" - build-env "VisualStudio" "Visual Studio 12 2013" + build-env "VisualStudio" "Visual Studio 14 2015" else build-env "SublimeText" "Sublime Text 2 - Ninja" build-env "Make" "Unix Makefiles" build-env "Xcode" "Xcode" -fi \ No newline at end of file +fi diff --git a/test/Issue67.cpp b/test/Issue67.cpp deleted file mode 100644 index 2bbc6421..00000000 --- a/test/Issue67.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// 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 - -class Issue67 : public testing::Test { - public: - void whenInputIs(double value) { _variant = value; } - - void outputMustBe(const char* expected) { - char buffer[1024]; - _variant.printTo(buffer, sizeof(buffer)); - ASSERT_STREQ(expected, buffer); - } - - private: - JsonVariant _variant; -}; - -TEST_F(Issue67, BigPositiveDouble) { - whenInputIs(1e100); - outputMustBe("1e+100"); -} - -TEST_F(Issue67, BigNegativeDouble) { - whenInputIs(-1e100); - outputMustBe("-1e+100"); -} - -TEST_F(Issue67, Zero) { - whenInputIs(0.0); - outputMustBe("0.00"); -} - -TEST_F(Issue67, SmallPositiveDouble) { - whenInputIs(111.111); - outputMustBe("111.11"); -} - -TEST_F(Issue67, SmallNegativeDouble) { - whenInputIs(-111.111); - outputMustBe("-111.11"); -} diff --git a/test/JsonVariant_PrintTo_Tests.cpp b/test/JsonVariant_PrintTo_Tests.cpp index aca8c666..4597f082 100644 --- a/test/JsonVariant_PrintTo_Tests.cpp +++ b/test/JsonVariant_PrintTo_Tests.cpp @@ -7,6 +7,7 @@ #include #include +#include class JsonVariant_PrintTo_Tests : public testing::Test { protected: @@ -15,8 +16,8 @@ class JsonVariant_PrintTo_Tests : public testing::Test { void outputMustBe(const char *expected) { char buffer[256] = ""; size_t n = variant.printTo(buffer, sizeof(buffer)); - EXPECT_STREQ(expected, buffer); - EXPECT_EQ(strlen(expected), n); + ASSERT_STREQ(expected, buffer); + ASSERT_EQ(strlen(expected), n); } }; @@ -47,6 +48,46 @@ TEST_F(JsonVariant_PrintTo_Tests, DoubleFourDigits) { outputMustBe("3.1416"); } +TEST_F(JsonVariant_PrintTo_Tests, Infinity) { + variant = std::numeric_limits::infinity(); + outputMustBe("Infinity"); +} + +TEST_F(JsonVariant_PrintTo_Tests, MinusInfinity) { + variant = -std::numeric_limits::infinity(); + outputMustBe("-Infinity"); +} + +TEST_F(JsonVariant_PrintTo_Tests, SignalingNaN) { + variant = std::numeric_limits::signaling_NaN(); + outputMustBe("NaN"); +} + +TEST_F(JsonVariant_PrintTo_Tests, QuietNaN) { + variant = std::numeric_limits::quiet_NaN(); + outputMustBe("NaN"); +} + +TEST_F(JsonVariant_PrintTo_Tests, VeryBigPositiveDouble) { + variant = JsonVariant(3.14159265358979323846e42, 4); + outputMustBe("3.1416e42"); +} + +TEST_F(JsonVariant_PrintTo_Tests, VeryBigNegativeDouble) { + variant = JsonVariant(-3.14159265358979323846e42, 4); + outputMustBe("-3.1416e42"); +} + +TEST_F(JsonVariant_PrintTo_Tests, VerySmallPositiveDouble) { + variant = JsonVariant(3.14159265358979323846e-42, 4); + outputMustBe("3.1416e-42"); +} + +TEST_F(JsonVariant_PrintTo_Tests, VerySmallNegativeDouble) { + variant = JsonVariant(-3.14159265358979323846e-42, 4); + outputMustBe("-3.1416e-42"); +} + TEST_F(JsonVariant_PrintTo_Tests, Integer) { variant = 42; outputMustBe("42"); @@ -62,11 +103,6 @@ TEST_F(JsonVariant_PrintTo_Tests, UnsignedLong) { outputMustBe("4294967295"); } -TEST_F(JsonVariant_PrintTo_Tests, Char) { - variant = '*'; - outputMustBe("42"); -} - TEST_F(JsonVariant_PrintTo_Tests, True) { variant = true; outputMustBe("true");