Эх сурвалжийг харах

Serialize `float` with less decimal places than `double`

Benoit Blanchon 1 жил өмнө
parent
commit
3b6bf45b8a

+ 5 - 0
extras/tests/JsonSerializer/JsonVariant.cpp

@@ -83,6 +83,11 @@ TEST_CASE("serializeJson(JsonVariant)") {
     check(3.1415927, "3.1415927");
   }
 
+  SECTION("Float") {
+    REQUIRE(sizeof(float) == 4);
+    check(3.1415927f, "3.141593");
+  }
+
   SECTION("Zero") {
     check(0, "0");
   }

+ 1 - 1
extras/tests/JsonVariant/as.cpp

@@ -36,7 +36,7 @@ TEST_CASE("JsonVariant::as()") {
 
     REQUIRE(variant.as<bool>());
     REQUIRE(0 == variant.as<const char*>());
-    REQUIRE(variant.as<std::string>() == "4.199999809");  // TODO
+    REQUIRE(variant.as<std::string>() == "4.2");
     REQUIRE(variant.as<long>() == 4L);
     REQUIRE(variant.as<float>() == 4.2f);
     REQUIRE(variant.as<unsigned>() == 4U);

+ 5 - 7
extras/tests/Misc/FloatParts.cpp

@@ -7,9 +7,9 @@
 
 using namespace ArduinoJson::detail;
 
-TEST_CASE("FloatParts<double>") {
+TEST_CASE("decomposeFloat()") {
   SECTION("1.7976931348623157E+308") {
-    FloatParts<double> parts(1.7976931348623157E+308);
+    auto parts = decomposeFloat(1.7976931348623157E+308, 9);
     REQUIRE(parts.integral == 1);
     REQUIRE(parts.decimal == 797693135);
     REQUIRE(parts.decimalPlaces == 9);
@@ -17,17 +17,15 @@ TEST_CASE("FloatParts<double>") {
   }
 
   SECTION("4.94065645841247e-324") {
-    FloatParts<double> parts(4.94065645841247e-324);
+    auto parts = decomposeFloat(4.94065645841247e-324, 9);
     REQUIRE(parts.integral == 4);
     REQUIRE(parts.decimal == 940656458);
     REQUIRE(parts.decimalPlaces == 9);
     REQUIRE(parts.exponent == -324);
   }
-}
 
-TEST_CASE("FloatParts<float>") {
   SECTION("3.4E+38") {
-    FloatParts<float> parts(3.4E+38f);
+    auto parts = decomposeFloat(3.4E+38f, 6);
     REQUIRE(parts.integral == 3);
     REQUIRE(parts.decimal == 4);
     REQUIRE(parts.decimalPlaces == 1);
@@ -35,7 +33,7 @@ TEST_CASE("FloatParts<float>") {
   }
 
   SECTION("1.17549435e−38") {
-    FloatParts<float> parts(1.17549435e-38f);
+    auto parts = decomposeFloat(1.17549435e-38f, 6);
     REQUIRE(parts.integral == 1);
     REQUIRE(parts.decimal == 175494);
     REQUIRE(parts.decimalPlaces == 6);

+ 2 - 1
src/ArduinoJson/Json/JsonSerializer.hpp

@@ -62,7 +62,8 @@ class JsonSerializer : public VariantDataVisitor<size_t> {
     return bytesWritten();
   }
 
-  size_t visit(JsonFloat value) {
+  template <typename T>
+  enable_if_t<is_floating_point<T>::value, size_t> visit(T value) {
     formatter_.writeFloat(value);
     return bytesWritten();
   }

+ 5 - 1
src/ArduinoJson/Json/TextFormatter.hpp

@@ -66,6 +66,10 @@ class TextFormatter {
 
   template <typename T>
   void writeFloat(T value) {
+    writeFloat(JsonFloat(value), sizeof(T) >= 8 ? 9 : 6);
+  }
+
+  void writeFloat(JsonFloat value, int8_t decimalPlaces) {
     if (isnan(value))
       return writeRaw(ARDUINOJSON_ENABLE_NAN ? "NaN" : "null");
 
@@ -87,7 +91,7 @@ class TextFormatter {
     }
 #endif
 
-    FloatParts<T> parts(value);
+    auto parts = decomposeFloat(value, decimalPlaces);
 
     writeInteger(parts.integral);
     if (parts.decimalPlaces)

+ 63 - 56
src/ArduinoJson/Numbers/FloatParts.hpp

@@ -6,83 +6,90 @@
 
 #include <ArduinoJson/Configuration.hpp>
 #include <ArduinoJson/Numbers/FloatTraits.hpp>
+#include <ArduinoJson/Numbers/JsonFloat.hpp>
 #include <ArduinoJson/Polyfills/math.hpp>
 
 ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
-template <typename TFloat>
 struct FloatParts {
   uint32_t integral;
   uint32_t decimal;
   int16_t exponent;
   int8_t decimalPlaces;
+};
 
-  FloatParts(TFloat value) {
-    uint32_t maxDecimalPart = sizeof(TFloat) >= 8 ? 1000000000 : 1000000;
-    decimalPlaces = sizeof(TFloat) >= 8 ? 9 : 6;
-
-    exponent = normalize(value);
-
-    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--;
-    }
-
-    TFloat remainder = (value - TFloat(integral)) * TFloat(maxDecimalPart);
-
-    decimal = uint32_t(remainder);
-    remainder = remainder - TFloat(decimal);
-
-    // 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;
+template <typename TFloat>
+inline int16_t normalize(TFloat& value) {
+  typedef FloatTraits<TFloat> traits;
+  int16_t powersOf10 = 0;
+
+  int8_t index = sizeof(TFloat) == 8 ? 8 : 5;
+  int bit = 1 << index;
+
+  if (value >= ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD) {
+    for (; index >= 0; index--) {
+      if (value >= traits::positiveBinaryPowersOfTen()[index]) {
+        value *= traits::negativeBinaryPowersOfTen()[index];
+        powersOf10 = int16_t(powersOf10 + bit);
       }
+      bit >>= 1;
     }
+  }
 
-    // remove trailing zeros
-    while (decimal % 10 == 0 && decimalPlaces > 0) {
-      decimal /= 10;
-      decimalPlaces--;
+  if (value > 0 && value <= ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD) {
+    for (; index >= 0; index--) {
+      if (value < traits::negativeBinaryPowersOfTen()[index] * 10) {
+        value *= traits::positiveBinaryPowersOfTen()[index];
+        powersOf10 = int16_t(powersOf10 - bit);
+      }
+      bit >>= 1;
     }
   }
 
-  static int16_t normalize(TFloat& value) {
-    typedef FloatTraits<TFloat> traits;
-    int16_t powersOf10 = 0;
+  return powersOf10;
+}
 
-    int8_t index = sizeof(TFloat) == 8 ? 8 : 5;
-    int bit = 1 << index;
+constexpr uint32_t pow10(int exponent) {
+  return (exponent == 0) ? 1 : 10 * pow10(exponent - 1);
+}
 
-    if (value >= ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD) {
-      for (; index >= 0; index--) {
-        if (value >= traits::positiveBinaryPowersOfTen()[index]) {
-          value *= traits::negativeBinaryPowersOfTen()[index];
-          powersOf10 = int16_t(powersOf10 + bit);
-        }
-        bit >>= 1;
-      }
-    }
+inline FloatParts decomposeFloat(JsonFloat value, int8_t decimalPlaces) {
+  uint32_t maxDecimalPart = pow10(decimalPlaces);
 
-    if (value > 0 && value <= ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD) {
-      for (; index >= 0; index--) {
-        if (value < traits::negativeBinaryPowersOfTen()[index] * 10) {
-          value *= traits::positiveBinaryPowersOfTen()[index];
-          powersOf10 = int16_t(powersOf10 - bit);
-        }
-        bit >>= 1;
-      }
+  int16_t exponent = normalize(value);
+
+  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--;
+  }
+
+  JsonFloat remainder =
+      (value - JsonFloat(integral)) * JsonFloat(maxDecimalPart);
+
+  uint32_t decimal = uint32_t(remainder);
+  remainder = remainder - JsonFloat(decimal);
+
+  // 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;
     }
+  }
 
-    return powersOf10;
+  // remove trailing zeros
+  while (decimal % 10 == 0 && decimalPlaces > 0) {
+    decimal /= 10;
+    decimalPlaces--;
   }
-};
+
+  return {integral, decimal, exponent, decimalPlaces};
+}
 
 ARDUINOJSON_END_PRIVATE_NAMESPACE

+ 9 - 13
src/ArduinoJson/Variant/VariantCompare.hpp

@@ -52,23 +52,19 @@ struct Comparer<
 
   explicit Comparer(T value) : rhs(value) {}
 
-  CompareResult visit(JsonFloat lhs) {
-    return arithmeticCompare(lhs, rhs);
-  }
-
-  CompareResult visit(JsonInteger lhs) {
-    return arithmeticCompare(lhs, rhs);
-  }
-
-  CompareResult visit(JsonUInt lhs) {
+  template <typename U>
+  enable_if_t<is_floating_point<U>::value || is_integral<U>::value,
+              CompareResult>
+  visit(const U& lhs) {
     return arithmeticCompare(lhs, rhs);
   }
 
-  CompareResult visit(bool lhs) {
-    return visit(static_cast<JsonUInt>(lhs));
+  template <typename U>
+  enable_if_t<!is_floating_point<U>::value && !is_integral<U>::value,
+              CompareResult>
+  visit(const U& lhs) {
+    return ComparerBase::visit(lhs);
   }
-
-  using ComparerBase::visit;
 };
 
 struct NullComparer : ComparerBase {

+ 1 - 1
src/ArduinoJson/Variant/VariantData.hpp

@@ -46,7 +46,7 @@ class VariantData {
     (void)resources;  // silence warning
     switch (type_) {
       case VariantType::Float:
-        return visit.visit(static_cast<JsonFloat>(content_.asFloat));
+        return visit.visit(content_.asFloat);
 
 #if ARDUINOJSON_USE_DOUBLE
       case VariantType::Double: