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");
}
SECTION("Float") {
REQUIRE(sizeof(float) == 4);
check(3.1415927f, "3.141593");
}
SECTION("Zero") {
check(0, "0");
}

View File

@ -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);

View File

@ -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.17549435e38") {
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);

View File

@ -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();
}

View File

@ -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)

View File

@ -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;
}
}
// 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

View File

@ -52,23 +52,19 @@ struct Comparer<
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);
}
CompareResult visit(JsonInteger lhs) {
return arithmeticCompare(lhs, rhs);
template <typename U>
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 {

View File

@ -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: