Changed integer storage from positive/negative to signed/unsigned

This commit is contained in:
Benoit Blanchon
2021-04-14 11:42:53 +02:00
parent a002393716
commit fc4f5fd05f
16 changed files with 251 additions and 171 deletions

View File

@ -76,11 +76,28 @@ TEST_CASE("JsonVariant::as()") {
REQUIRE(variant.as<std::string>() == "-42");
}
SECTION("set(42UL)") {
variant.set(42UL);
REQUIRE(variant.as<bool>() == true);
REQUIRE(variant.as<double>() == 42.0);
REQUIRE(variant.as<std::string>() == "42");
}
SECTION("set(0L)") {
variant.set(0L);
REQUIRE(variant.as<bool>() == false);
REQUIRE(variant.as<double>() == 0.0);
REQUIRE(variant.as<std::string>() == "0");
}
SECTION("set(0UL)") {
variant.set(0UL);
REQUIRE(variant.as<bool>() == false);
REQUIRE(variant.as<double>() == 0.0);
REQUIRE(variant.as<std::string>() == "0");
}
SECTION("set(null)") {

View File

@ -46,8 +46,14 @@ TEST_CASE("serialize MsgPack value") {
}
SECTION("positive fixint") {
checkVariant(0, "\x00");
checkVariant(127, "\x7F");
SECTION("signed") {
checkVariant(0, "\x00");
checkVariant(127, "\x7F");
}
SECTION("unsigned") {
checkVariant(0U, "\x00");
checkVariant(127U, "\x7F");
}
}
SECTION("uint 8") {

View File

@ -47,6 +47,7 @@ TEST_CASE("parseNumber<int32_t>()") {
TEST_CASE("parseNumber<uint8_t>()") {
checkInteger<uint8_t>("0", 0);
checkInteger<uint8_t>("-0", 0);
checkInteger<uint8_t>("255", 255);
checkInteger<uint8_t>("+255", 255);
checkInteger<uint8_t>("3.14", 3);

View File

@ -23,7 +23,27 @@ TEST_CASE("Test unsigned integer overflow") {
parseNumber("4294967296", second);
}
REQUIRE(first.type() == uint8_t(VALUE_IS_POSITIVE_INTEGER));
REQUIRE(first.type() == uint8_t(VALUE_IS_UNSIGNED_INTEGER));
REQUIRE(second.type() == uint8_t(VALUE_IS_FLOAT));
}
TEST_CASE("Test signed integer overflow") {
VariantData first, second;
first.init();
second.init();
// Avoids MSVC warning C4127 (conditional expression is constant)
size_t integerSize = sizeof(Integer);
if (integerSize == 8) {
parseNumber("-9223372036854775808", first);
parseNumber("-9223372036854775809", second);
} else {
parseNumber("-2147483648", first);
parseNumber("-2147483649", second);
}
REQUIRE(first.type() == uint8_t(VALUE_IS_SIGNED_INTEGER));
REQUIRE(second.type() == uint8_t(VALUE_IS_FLOAT));
}

View File

@ -4,6 +4,7 @@
add_executable(TextFormatterTests
writeFloat.cpp
writeInteger.cpp
writeString.cpp
)

View File

@ -0,0 +1,55 @@
// ArduinoJson - https://arduinojson.org
// Copyright Benoit Blanchon 2014-2021
// MIT License
#include <catch.hpp>
#include <limits>
#include <string>
#include <ArduinoJson/Json/TextFormatter.hpp>
#include <ArduinoJson/Serialization/Writer.hpp>
using namespace ARDUINOJSON_NAMESPACE;
template <typename T>
void checkWriteInteger(T value, std::string expected) {
char output[1024];
StaticStringWriter sb(output, sizeof(output));
TextFormatter<StaticStringWriter> writer(sb);
writer.writeInteger<T>(value);
REQUIRE(expected == output);
REQUIRE(writer.bytesWritten() == expected.size());
}
TEST_CASE("int8_t") {
checkWriteInteger<int8_t>(0, "0");
checkWriteInteger<int8_t>(-128, "-128");
checkWriteInteger<int8_t>(127, "127");
}
TEST_CASE("uint8_t") {
checkWriteInteger<uint8_t>(0, "0");
checkWriteInteger<uint8_t>(255, "255");
}
TEST_CASE("int16_t") {
checkWriteInteger<int16_t>(0, "0");
checkWriteInteger<int16_t>(-32768, "-32768");
checkWriteInteger<int16_t>(32767, "32767");
}
TEST_CASE("uint16_t") {
checkWriteInteger<uint16_t>(0, "0");
checkWriteInteger<uint16_t>(65535, "65535");
}
TEST_CASE("int32_t") {
checkWriteInteger<int32_t>(0, "0");
checkWriteInteger<int32_t>(-2147483647 - 1, "-2147483648");
checkWriteInteger<int32_t>(2147483647, "2147483647");
}
TEST_CASE("uint32_t") {
checkWriteInteger<uint32_t>(0, "0");
checkWriteInteger<uint32_t>(4294967295U, "4294967295");
}

View File

@ -71,13 +71,13 @@ class JsonSerializer : public Visitor<size_t> {
return bytesWritten();
}
size_t visitNegativeInteger(UInt value) {
_formatter.writeNegativeInteger(value);
size_t visitSignedInteger(Integer value) {
_formatter.writeInteger(value);
return bytesWritten();
}
size_t visitPositiveInteger(UInt value) {
_formatter.writePositiveInteger(value);
size_t visitUnsignedInteger(UInt value) {
_formatter.writeInteger(value);
return bytesWritten();
}

View File

@ -12,6 +12,7 @@
#include <ArduinoJson/Numbers/Integer.hpp>
#include <ArduinoJson/Polyfills/assert.hpp>
#include <ArduinoJson/Polyfills/attributes.hpp>
#include <ArduinoJson/Polyfills/type_traits.hpp>
#include <ArduinoJson/Serialization/CountingDecorator.hpp>
namespace ARDUINOJSON_NAMESPACE {
@ -75,28 +76,31 @@ class TextFormatter {
FloatParts<T> parts(value);
writePositiveInteger(parts.integral);
writeInteger(parts.integral);
if (parts.decimalPlaces)
writeDecimals(parts.decimal, parts.decimalPlaces);
if (parts.exponent < 0) {
writeRaw("e-");
writePositiveInteger(-parts.exponent);
}
if (parts.exponent > 0) {
if (parts.exponent) {
writeRaw('e');
writePositiveInteger(parts.exponent);
writeInteger(parts.exponent);
}
}
void writeNegativeInteger(UInt value) {
writeRaw('-');
writePositiveInteger(value);
}
template <typename T>
void writePositiveInteger(T value) {
typename enable_if<is_signed<T>::value>::type writeInteger(T value) {
typedef typename make_unsigned<T>::type unsigned_type;
unsigned_type unsigned_value;
if (value < 0) {
writeRaw('-');
unsigned_value = static_cast<unsigned_type>(-value);
} else {
unsigned_value = static_cast<unsigned_type>(value);
}
writeInteger(unsigned_value);
}
template <typename T>
typename enable_if<is_unsigned<T>::value>::type writeInteger(T value) {
char buffer[22];
char *end = buffer + sizeof(buffer);
char *begin = end;

View File

@ -101,30 +101,37 @@ class MsgPackSerializer : public Visitor<size_t> {
return bytesWritten();
}
size_t visitNegativeInteger(UInt value) {
UInt negated = UInt(~value + 1);
if (value <= 0x20) {
writeInteger(int8_t(negated));
} else if (value <= 0x80) {
size_t visitSignedInteger(Integer value) {
if (value > 0) {
visitUnsignedInteger(static_cast<UInt>(value));
} else if (value >= -0x20) {
writeInteger(int8_t(value));
} else if (value >= -0x80) {
writeByte(0xD0);
writeInteger(int8_t(negated));
} else if (value <= 0x8000) {
writeInteger(int8_t(value));
} else if (value >= -0x8000) {
writeByte(0xD1);
writeInteger(int16_t(negated));
} else if (value <= 0x80000000) {
writeInteger(int16_t(value));
}
#if ARDUINOJSON_USE_LONG_LONG
else if (value >= -0x80000000LL)
#else
else
#endif
{
writeByte(0xD2);
writeInteger(int32_t(negated));
writeInteger(int32_t(value));
}
#if ARDUINOJSON_USE_LONG_LONG
else {
writeByte(0xD3);
writeInteger(int64_t(negated));
writeInteger(int64_t(value));
}
#endif
return bytesWritten();
}
size_t visitPositiveInteger(UInt value) {
size_t visitUnsignedInteger(UInt value) {
if (value <= 0x7F) {
writeInteger(uint8_t(value));
} else if (value <= 0xFF) {

View File

@ -15,84 +15,86 @@
#endif
#include <ArduinoJson/Numbers/Float.hpp>
#include <ArduinoJson/Numbers/FloatTraits.hpp>
#include <ArduinoJson/Numbers/Integer.hpp>
#include <ArduinoJson/Polyfills/limits.hpp>
#include <ArduinoJson/Polyfills/type_traits.hpp>
namespace ARDUINOJSON_NAMESPACE {
// uint32 -> int32
// uint64 -> int32
template <typename TOut, typename TIn>
typename enable_if<is_integral<TOut>::value && sizeof(TOut) <= sizeof(TIn),
typename enable_if<is_integral<TIn>::value && is_unsigned<TIn>::value &&
is_integral<TOut>::value && sizeof(TOut) <= sizeof(TIn),
bool>::type
canStorePositiveInteger(TIn value) {
canConvertNumber(TIn value) {
return value <= TIn(numeric_limits<TOut>::highest());
}
// uint32 -> int64
template <typename TOut, typename TIn>
typename enable_if<is_integral<TOut>::value && sizeof(TIn) < sizeof(TOut),
typename enable_if<is_integral<TIn>::value && is_unsigned<TIn>::value &&
is_integral<TOut>::value && sizeof(TIn) < sizeof(TOut),
bool>::type
canStorePositiveInteger(TIn) {
canConvertNumber(TIn) {
return true;
}
// uint32 -> float
// int32 -> float
template <typename TOut, typename TIn>
typename enable_if<is_floating_point<TOut>::value, bool>::type
canStorePositiveInteger(TIn) {
typename enable_if<is_integral<TIn>::value && is_floating_point<TOut>::value,
bool>::type
canConvertNumber(TIn) {
return true;
}
// int64 -> int32
template <typename TOut, typename TIn>
typename enable_if<is_floating_point<TOut>::value, bool>::type
canStoreNegativeInteger(TIn) {
typename enable_if<is_integral<TIn>::value && is_signed<TIn>::value &&
is_integral<TOut>::value && is_signed<TOut>::value &&
sizeof(TOut) < sizeof(TIn),
bool>::type
canConvertNumber(TIn value) {
return value >= TIn(numeric_limits<TOut>::lowest()) &&
value <= TIn(numeric_limits<TOut>::highest());
}
// int32 -> int32
// int32 -> int64
template <typename TOut, typename TIn>
typename enable_if<is_integral<TIn>::value && is_signed<TIn>::value &&
is_integral<TOut>::value && is_signed<TOut>::value &&
sizeof(TIn) <= sizeof(TOut),
bool>::type
canConvertNumber(TIn) {
return true;
}
// int32 -> uint32
template <typename TOut, typename TIn>
typename enable_if<is_integral<TOut>::value && is_signed<TOut>::value &&
sizeof(TOut) <= sizeof(TIn),
typename enable_if<is_integral<TIn>::value && is_signed<TIn>::value &&
is_integral<TOut>::value && is_unsigned<TOut>::value,
bool>::type
canStoreNegativeInteger(TIn value) {
return value <= TIn(numeric_limits<TOut>::highest()) + 1;
canConvertNumber(TIn value) {
if (value < 0)
return false;
return value <= TIn(numeric_limits<TOut>::highest());
}
// float -> int32
// float -> int64
template <typename TOut, typename TIn>
typename enable_if<is_integral<TOut>::value && is_signed<TOut>::value &&
sizeof(TIn) < sizeof(TOut),
typename enable_if<is_floating_point<TIn>::value &&
!is_floating_point<TOut>::value,
bool>::type
canStoreNegativeInteger(TIn) {
return true;
}
template <typename TOut, typename TIn>
typename enable_if<is_integral<TOut>::value && is_unsigned<TOut>::value,
bool>::type
canStoreNegativeInteger(TIn) {
return false;
}
template <typename TOut, typename TIn>
TOut convertPositiveInteger(TIn value) {
return canStorePositiveInteger<TOut>(value) ? TOut(value) : 0;
}
template <typename TOut, typename TIn>
TOut convertNegativeInteger(TIn value) {
return canStoreNegativeInteger<TOut>(value) ? TOut(~value + 1) : 0;
}
template <typename TOut, typename TIn>
typename enable_if<is_floating_point<TOut>::value, TOut>::type convertFloat(
TIn value) {
return TOut(value);
}
template <typename TOut, typename TIn>
typename enable_if<!is_floating_point<TOut>::value, TOut>::type convertFloat(
TIn value) {
canConvertNumber(TIn value) {
return value >= numeric_limits<TOut>::lowest() &&
value <= numeric_limits<TOut>::highest()
? TOut(value)
: 0;
value <= numeric_limits<TOut>::highest();
}
template <typename TOut, typename TIn>
TOut convertNumber(TIn value) {
return canConvertNumber<TOut>(value) ? TOut(value) : 0;
}
} // namespace ARDUINOJSON_NAMESPACE

View File

@ -69,11 +69,17 @@ inline bool parseNumber(const char* s, VariantData& result) {
}
if (*s == '\0') {
if (is_negative)
result.setNegativeInteger(UInt(mantissa));
else
result.setPositiveInteger(UInt(mantissa));
return true;
if (is_negative) {
const mantissa_t sintMantissaMax = mantissa_t(1)
<< (sizeof(Integer) * 8 - 1);
if (mantissa <= sintMantissaMax) {
result.setInteger(Integer(~mantissa + 1));
return true;
}
} else {
result.setInteger(UInt(mantissa));
return true;
}
}
// avoid mantissa overflow

View File

@ -57,16 +57,16 @@ struct Comparer<T, typename enable_if<is_integral<T>::value ||
return arithmeticCompare(lhs, rhs);
}
CompareResult visitNegativeInteger(UInt lhs) {
return arithmeticCompareNegateLeft(lhs, rhs);
CompareResult visitSignedInteger(Integer lhs) {
return arithmeticCompare(lhs, rhs);
}
CompareResult visitPositiveInteger(UInt lhs) {
CompareResult visitUnsignedInteger(UInt lhs) {
return arithmeticCompare(lhs, rhs);
}
CompareResult visitBoolean(bool lhs) {
return visitPositiveInteger(static_cast<UInt>(lhs));
return visitUnsignedInteger(static_cast<UInt>(lhs));
}
};
@ -96,28 +96,6 @@ struct ArrayComparer : ComparerBase {
}
};
struct NegativeIntegerComparer : ComparerBase {
UInt _rhs;
explicit NegativeIntegerComparer(UInt rhs) : _rhs(rhs) {}
CompareResult visitFloat(Float lhs) {
return arithmeticCompareNegateRight(lhs, _rhs);
}
CompareResult visitNegativeInteger(UInt lhs) {
return arithmeticCompare(_rhs, lhs);
}
CompareResult visitPositiveInteger(UInt) {
return COMPARE_RESULT_GREATER;
}
CompareResult visitBoolean(bool) {
return COMPARE_RESULT_GREATER;
}
};
struct ObjectComparer : ComparerBase {
const CollectionData *_rhs;
@ -182,12 +160,12 @@ struct Comparer<T, typename enable_if<IsVisitable<T>::value>::type>
return accept(comparer);
}
CompareResult visitNegativeInteger(UInt lhs) {
NegativeIntegerComparer comparer(lhs);
CompareResult visitSignedInteger(Integer lhs) {
Comparer<Integer> comparer(lhs);
return accept(comparer);
}
CompareResult visitPositiveInteger(UInt lhs) {
CompareResult visitUnsignedInteger(UInt lhs) {
Comparer<UInt> comparer(lhs);
return accept(comparer);
}

View File

@ -25,8 +25,8 @@ enum {
// CAUTION: no VALUE_IS_OWNED below
VALUE_IS_BOOLEAN = 0x06,
VALUE_IS_POSITIVE_INTEGER = 0x08,
VALUE_IS_NEGATIVE_INTEGER = 0x0A,
VALUE_IS_UNSIGNED_INTEGER = 0x08,
VALUE_IS_SIGNED_INTEGER = 0x0A,
VALUE_IS_FLOAT = 0x0C,
COLLECTION_MASK = 0x60,
@ -43,7 +43,9 @@ struct RawData {
union VariantContent {
Float asFloat;
UInt asInteger;
bool asBoolean;
UInt asUnsignedInteger;
Integer asSignedInteger;
CollectionData asCollection;
const char *asString;
struct {

View File

@ -56,14 +56,14 @@ class VariantData {
case VALUE_IS_LINKED_RAW:
return visitor.visitRawJson(_content.asRaw.data, _content.asRaw.size);
case VALUE_IS_NEGATIVE_INTEGER:
return visitor.visitNegativeInteger(_content.asInteger);
case VALUE_IS_SIGNED_INTEGER:
return visitor.visitSignedInteger(_content.asSignedInteger);
case VALUE_IS_POSITIVE_INTEGER:
return visitor.visitPositiveInteger(_content.asInteger);
case VALUE_IS_UNSIGNED_INTEGER:
return visitor.visitUnsignedInteger(_content.asUnsignedInteger);
case VALUE_IS_BOOLEAN:
return visitor.visitBoolean(_content.asInteger != 0);
return visitor.visitBoolean(_content.asBoolean != 0);
default:
return visitor.visitNull();
@ -129,11 +129,11 @@ class VariantData {
template <typename T>
bool isInteger() const {
switch (type()) {
case VALUE_IS_POSITIVE_INTEGER:
return canStorePositiveInteger<T>(_content.asInteger);
case VALUE_IS_UNSIGNED_INTEGER:
return canConvertNumber<T>(_content.asUnsignedInteger);
case VALUE_IS_NEGATIVE_INTEGER:
return canStoreNegativeInteger<T>(_content.asInteger);
case VALUE_IS_SIGNED_INTEGER:
return canConvertNumber<T>(_content.asSignedInteger);
default:
return false;
@ -141,8 +141,8 @@ class VariantData {
}
bool isFloat() const {
return type() == VALUE_IS_FLOAT || type() == VALUE_IS_POSITIVE_INTEGER ||
type() == VALUE_IS_NEGATIVE_INTEGER;
return type() == VALUE_IS_FLOAT || type() == VALUE_IS_UNSIGNED_INTEGER ||
type() == VALUE_IS_SIGNED_INTEGER;
}
bool isString() const {
@ -174,7 +174,7 @@ class VariantData {
void setBoolean(bool value) {
setType(VALUE_IS_BOOLEAN);
_content.asInteger = static_cast<UInt>(value);
_content.asBoolean = value;
}
void setFloat(Float value) {
@ -208,36 +208,14 @@ class VariantData {
template <typename T>
typename enable_if<is_unsigned<T>::value>::type setInteger(T value) {
setUnsignedInteger(value);
setType(VALUE_IS_UNSIGNED_INTEGER);
_content.asUnsignedInteger = static_cast<UInt>(value);
}
template <typename T>
typename enable_if<is_signed<T>::value>::type setInteger(T value) {
setSignedInteger(value);
}
template <typename T>
void setSignedInteger(T value) {
if (value >= 0) {
setPositiveInteger(static_cast<UInt>(value));
} else {
setNegativeInteger(~static_cast<UInt>(value) + 1);
}
}
void setUnsignedInteger(UInt value) {
setType(VALUE_IS_POSITIVE_INTEGER);
_content.asInteger = static_cast<UInt>(value);
}
void setPositiveInteger(UInt value) {
setType(VALUE_IS_POSITIVE_INTEGER);
_content.asInteger = value;
}
void setNegativeInteger(UInt value) {
setType(VALUE_IS_NEGATIVE_INTEGER);
_content.asInteger = value;
setType(VALUE_IS_SIGNED_INTEGER);
_content.asSignedInteger = value;
}
void setNull() {

View File

@ -18,16 +18,17 @@ namespace ARDUINOJSON_NAMESPACE {
template <typename T>
inline T VariantData::asIntegral() const {
switch (type()) {
case VALUE_IS_POSITIVE_INTEGER:
case VALUE_IS_BOOLEAN:
return convertPositiveInteger<T>(_content.asInteger);
case VALUE_IS_NEGATIVE_INTEGER:
return convertNegativeInteger<T>(_content.asInteger);
return _content.asBoolean;
case VALUE_IS_UNSIGNED_INTEGER:
return convertNumber<T>(_content.asUnsignedInteger);
case VALUE_IS_SIGNED_INTEGER:
return convertNumber<T>(_content.asSignedInteger);
case VALUE_IS_LINKED_STRING:
case VALUE_IS_OWNED_STRING:
return parseNumber<T>(_content.asString);
case VALUE_IS_FLOAT:
return convertFloat<T>(_content.asFloat);
return convertNumber<T>(_content.asFloat);
default:
return 0;
}
@ -35,10 +36,11 @@ inline T VariantData::asIntegral() const {
inline bool VariantData::asBoolean() const {
switch (type()) {
case VALUE_IS_POSITIVE_INTEGER:
case VALUE_IS_BOOLEAN:
case VALUE_IS_NEGATIVE_INTEGER:
return _content.asInteger != 0;
return _content.asBoolean;
case VALUE_IS_SIGNED_INTEGER:
case VALUE_IS_UNSIGNED_INTEGER:
return _content.asUnsignedInteger != 0;
case VALUE_IS_FLOAT:
return _content.asFloat != 0;
case VALUE_IS_NULL:
@ -52,11 +54,12 @@ inline bool VariantData::asBoolean() const {
template <typename T>
inline T VariantData::asFloat() const {
switch (type()) {
case VALUE_IS_POSITIVE_INTEGER:
case VALUE_IS_BOOLEAN:
return static_cast<T>(_content.asInteger);
case VALUE_IS_NEGATIVE_INTEGER:
return -static_cast<T>(_content.asInteger);
return static_cast<T>(_content.asBoolean);
case VALUE_IS_UNSIGNED_INTEGER:
return static_cast<T>(_content.asUnsignedInteger);
case VALUE_IS_SIGNED_INTEGER:
return static_cast<T>(_content.asSignedInteger);
case VALUE_IS_LINKED_STRING:
case VALUE_IS_OWNED_STRING:
return parseNumber<T>(_content.asString);

View File

@ -26,7 +26,7 @@ struct Visitor {
return TResult();
}
TResult visitNegativeInteger(UInt) {
TResult visitSignedInteger(Integer) {
return TResult();
}
@ -38,7 +38,7 @@ struct Visitor {
return TResult();
}
TResult visitPositiveInteger(UInt) {
TResult visitUnsignedInteger(UInt) {
return TResult();
}