Serialize float with less decimal places than double

This commit is contained in:
Benoit Blanchon
2024-09-03 11:44:35 +02:00
parent 65ba36622c
commit 3b6bf45b8a
8 changed files with 103 additions and 92 deletions

View File

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

View File

@ -36,7 +36,7 @@ TEST_CASE("JsonVariant::as()") {
REQUIRE(variant.as<bool>()); REQUIRE(variant.as<bool>());
REQUIRE(0 == variant.as<const char*>()); 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<long>() == 4L);
REQUIRE(variant.as<float>() == 4.2f); REQUIRE(variant.as<float>() == 4.2f);
REQUIRE(variant.as<unsigned>() == 4U); REQUIRE(variant.as<unsigned>() == 4U);

View File

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

View File

@ -62,7 +62,8 @@ class JsonSerializer : public VariantDataVisitor<size_t> {
return bytesWritten(); 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); formatter_.writeFloat(value);
return bytesWritten(); return bytesWritten();
} }

View File

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

View File

@ -6,83 +6,90 @@
#include <ArduinoJson/Configuration.hpp> #include <ArduinoJson/Configuration.hpp>
#include <ArduinoJson/Numbers/FloatTraits.hpp> #include <ArduinoJson/Numbers/FloatTraits.hpp>
#include <ArduinoJson/Numbers/JsonFloat.hpp>
#include <ArduinoJson/Polyfills/math.hpp> #include <ArduinoJson/Polyfills/math.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
template <typename TFloat>
struct FloatParts { struct FloatParts {
uint32_t integral; uint32_t integral;
uint32_t decimal; uint32_t decimal;
int16_t exponent; int16_t exponent;
int8_t decimalPlaces; 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;
}
}
// remove trailing zeros
while (decimal % 10 == 0 && decimalPlaces > 0) {
decimal /= 10;
decimalPlaces--;
}
}
static 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;
}
}
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;
}
}
return powersOf10;
}
}; };
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;
}
}
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;
}
}
return powersOf10;
}
constexpr uint32_t pow10(int exponent) {
return (exponent == 0) ? 1 : 10 * pow10(exponent - 1);
}
inline FloatParts decomposeFloat(JsonFloat value, int8_t decimalPlaces) {
uint32_t maxDecimalPart = pow10(decimalPlaces);
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;
}
}
// remove trailing zeros
while (decimal % 10 == 0 && decimalPlaces > 0) {
decimal /= 10;
decimalPlaces--;
}
return {integral, decimal, exponent, decimalPlaces};
}
ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

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

View File

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