/** * Copyright (C) 2018-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License * along with this program. If not, see * . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the Server Side Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/platform/basic.h" #include #include #include #include #include #include "mongo/unittest/unittest.h" #include "mongo/util/represent_as.h" namespace mongo { namespace { using namespace fmt::literals; // Char values const char kCharMax = std::numeric_limits::max(); const signed char kSCharMax = std::numeric_limits::max(); const int kSCharMaxAsInt = kSCharMax; const int kCharMaxAsInt = kCharMax; // Unsigned char values const unsigned char kUCharMax = std::numeric_limits::max(); const unsigned char kUCharMin = std::numeric_limits::lowest(); const int kUCharMaxAsInt = kUCharMax; // Int values const int kIntMax = std::numeric_limits::max(); const int kIntMin = std::numeric_limits::lowest(); const long long kIntMaxAsLongLong = kIntMax; const long long kIntMinAsLongLong = kIntMin; const unsigned long long kIntMaxAsULongLong = kIntMax; const unsigned long long kIntMinAsULongLong = kIntMin; // 32-bit integer values const int32_t kInt32Zero = 0; const int32_t kInt32Max = std::numeric_limits::max(); const int32_t kInt32Min = std::numeric_limits::lowest(); const uint32_t kInt32MaxAsUInt32 = kInt32Max; const uint64_t kInt32MaxAsUInt64 = kInt32Max; const double kInt32MaxAsDouble = kInt32Max; const double kInt32MinAsDouble = kInt32Min; // Unsigned 32-bit integer values const uint32_t kUInt32Zero = 0; const uint32_t kUInt32Max = std::numeric_limits::max(); const int64_t kUInt32MaxAsInt64 = kUInt32Max; const float kUInt32MaxAsFloat = static_cast(kUInt32Max); const double kUInt32MaxAsDouble = kUInt32Max; // 64-bit integer values const int64_t kInt64Zero = 0; const int64_t kInt64Max = std::numeric_limits::max(); const int64_t kInt64Min = std::numeric_limits::lowest(); const uint64_t kInt64MaxAsUInt64 = kInt64Max; const double kInt64MaxAsDouble = static_cast(kInt64Max); const double kInt64MinAsDouble = kInt64Min; // Unsigned 64-bit integer values const uint64_t kUInt64Zero = 0; const uint64_t kUInt64Max = std::numeric_limits::max(); const float kUInt64MaxAsFloat = static_cast(kUInt64Max); const double kUInt64MaxAsDouble = static_cast(kUInt64Max); // Long long values const long long kLongLongMax = std::numeric_limits::max(); // Unsigned long long values const unsigned long long kULongLongMax = std::numeric_limits::max(); // Float values const float kFloatZero = 0; const float kFloatMax = std::numeric_limits::max(); const float kFloatMin = std::numeric_limits::lowest(); const double kFloatMaxAsDouble = kFloatMax; const double kFloatMinAsDouble = kFloatMin; // Double values const double kDoubleZero = 0; const double kDoubleMax = std::numeric_limits::max(); const double kDoubleMin = std::numeric_limits::lowest(); // Precision values const int kFloatMantissa = std::numeric_limits::digits; const int kDoubleMantissa = std::numeric_limits::digits; const int32_t kInt32TooPreciseForFloat = static_cast(std::ldexp(1, kFloatMantissa + 1)) + 1; const uint32_t kUInt32TooPreciseForFloat = kInt32TooPreciseForFloat; const int64_t kInt64TooPreciseForFloat = kInt32TooPreciseForFloat; const int64_t kInt64TooPreciseForDouble = static_cast(std::ldexp(1, kDoubleMantissa + 1)) + 1; const uint64_t kUInt64TooPreciseForFloat = kInt32TooPreciseForFloat; const uint64_t kUInt64TooPreciseForDouble = kInt64TooPreciseForDouble; } // namespace #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable : 4756) // warning C4756: overflow in constant arithmetic #endif TEST(RepresentAs, Int32ToDouble) { ASSERT(*(representAs(kInt32Zero)) == 0); ASSERT(*(representAs(5)) == 5); } TEST(RepresentAs, Int64ToDouble) { ASSERT(*(representAs(kInt64Zero)) == 0); ASSERT(*(representAs(5)) == 5); // kInt64Max is too precise for double ASSERT(!(representAs(kInt64Max))); ASSERT(*(representAs(kInt64Min)) == kInt64MinAsDouble); } TEST(RepresentAs, DoubleToInt32) { ASSERT(*(representAs(kDoubleZero)) == 0); ASSERT(*(representAs(-12345)) == -12345); ASSERT(!(representAs(10.3))); // Int32 edge cases ASSERT(*(representAs(kInt32Max)) == kInt32Max); ASSERT(!(representAs(kInt32MaxAsDouble + 1))); ASSERT(*(representAs(kInt32Min)) == kInt32Min); ASSERT(!(representAs(kInt32MinAsDouble - 1))); // Very large and small values ASSERT(!(representAs(kDoubleMax))); ASSERT(!(representAs(kDoubleMin))); } TEST(RepresentAs, DoubleToInt64) { ASSERT(*(representAs(kDoubleZero)) == 0); ASSERT(*(representAs(-12345)) == -12345); ASSERT(!(representAs(10.3))); // Int64 edge cases, max can't be represented as doubles, min can ASSERT(!(representAs(kInt64MaxAsDouble))); ASSERT(*(representAs(kInt64MinAsDouble)) == kInt64Min); // Very large and small values ASSERT(!(representAs(kDoubleMax))); ASSERT(!(representAs(kDoubleMin))); } TEST(RepresentAs, DoubleToFloat) { ASSERT(*(representAs(kDoubleZero)) == 0); ASSERT(*(representAs(-12345)) == -12345); // Float edge casees ASSERT(*(representAs(kFloatMax)) == (representAs(kFloatMaxAsDouble + 1))); ASSERT(*(representAs(kFloatMin)) == (representAs(kFloatMinAsDouble - 1))); // Very large and small values ASSERT(!(representAs(kDoubleMax))); ASSERT(!(representAs(kDoubleMin))); } TEST(RepresentAs, DoubleToUnsignedInt) { ASSERT(!(representAs(-1.23))); ASSERT(*(representAs(kDoubleZero)) == kUInt64Zero); ASSERT(!(representAs(kDoubleMax))); ASSERT(!(representAs(kDoubleMax))); } TEST(RepresentAs, FloatToDouble) { ASSERT(*(representAs(kFloatZero)) == 0); ASSERT(*(representAs(-12345)) == -12345); ASSERT(*(representAs(kFloatMax)) == kFloatMax); ASSERT(*(representAs(kFloatMin)) == kFloatMin); } TEST(RepresentAs, FloatToUnsignedInt) { ASSERT(!(representAs(-1.23))); ASSERT(!(representAs(-1))); ASSERT(*(representAs(kUInt64Zero)) == kUInt64Zero); ASSERT(*(representAs(10)) == static_cast(10)); ASSERT(!(representAs(kFloatMax))); ASSERT(!(representAs(kFloatMax))); } TEST(RepresentAs, SignedAndUnsigned32BitIntegers) { ASSERT(!(representAs(kInt32Min))); ASSERT(*(representAs(kInt32Max)) == kInt32MaxAsUInt32); ASSERT(!(representAs(kUInt32Max))); ASSERT(!(representAs(kInt32MaxAsUInt32 + 1))); } TEST(RepresentAs, SignedAndUnsigned64BitIntegers) { ASSERT(!(representAs(kInt64Min))); ASSERT(*(representAs(kInt64Max)) == kInt64MaxAsUInt64); ASSERT(!(representAs(kUInt64Max))); ASSERT(!(representAs(kInt64MaxAsUInt64 + 1))); } TEST(RepresentAs, SignedAndUnsignedMixedSizeIntegers) { ASSERT(!(representAs(kInt64Min))); ASSERT(!(representAs(kInt64Max))); ASSERT(*(representAs(kUInt32Max)) == kUInt32MaxAsInt64); ASSERT(!(representAs(kInt32Min))); ASSERT(*(representAs(kInt32Max)) == kInt32MaxAsUInt64); ASSERT(!(representAs(kUInt64Max))); } TEST(RepresentAs, UnsignedIntToFloat) { // kUInt32Max and kUInt64Max are too precise for float. ASSERT(!(representAs(kUInt32Max))); ASSERT(!(representAs(kUInt64Max))); } TEST(RepresentAs, UnsignedIntToDouble) { // kUInt64Max is too precise for double. ASSERT(*(representAs(kUInt32Max)) == kUInt32MaxAsDouble); ASSERT(!(representAs(kUInt64Max))); } template void decimal128ToNumber() { const auto floor = std::to_string(std::numeric_limits::lowest()); const auto ceiling = std::to_string(std::numeric_limits::max()); ASSERT_EQ(*representAs(Decimal128(floor)), std::numeric_limits::lowest()); ASSERT_EQ(*representAs(Decimal128(ceiling)), std::numeric_limits::max()); ASSERT_EQ(*representAs(Decimal128::kNormalizedZero), static_cast(0)); ASSERT_EQ(*representAs(Decimal128("5")), static_cast(5)); ASSERT(!representAs(Decimal128::kLargestPositive)); ASSERT(!representAs(Decimal128::kLargestNegative)); } template void decimal128ToFloatingPoint() { decimal128ToNumber(); ASSERT_EQ(*representAs(Decimal128("-5")), -5); ASSERT_EQ(*representAs(Decimal128("5.5")), 5.5); ASSERT_EQ(*representAs(Decimal128("-5.5")), -5.5); } TEST(RepresentAs, Decimal128ToFloat) { decimal128ToFloatingPoint(); } TEST(RepresentAs, Decimal128ToDouble) { decimal128ToFloatingPoint(); } template void decimal128ToInteger() { decimal128ToNumber(); ASSERT(!representAs(Decimal128("5.5"))); ASSERT(!representAs(Decimal128("-5.5"))); ASSERT(!representAs(Decimal128::kLargestPositive)); ASSERT(!representAs(Decimal128::kLargestNegative)); ASSERT(!representAs(Decimal128::kSmallestPositive)); ASSERT(!representAs(Decimal128::kSmallestNegative)); if constexpr (std::is_signed()) { ASSERT_EQ(*representAs(Decimal128("-5")), -5); } else { ASSERT(!representAs(Decimal128("-5"))); } } TEST(RepresentAs, Decimal128ToInt8) { decimal128ToInteger(); } TEST(RepresentAs, Decimal128ToUInt8) { decimal128ToInteger(); } TEST(RepresentAs, Decimal128ToInt16) { decimal128ToInteger(); } TEST(RepresentAs, Decimal128ToUInt16) { decimal128ToInteger(); } TEST(RepresentAs, Decimal128ToInt32) { decimal128ToInteger(); } TEST(RepresentAs, Decimal128ToUInt32) { decimal128ToInteger(); } TEST(RepresentAs, Decimal128ToInt64) { decimal128ToInteger(); } TEST(RepresentAs, Decimal128ToUInt64) { decimal128ToInteger(); } TEST(RepresentAs, Decimal128ToDecimal128) { Decimal128 decimals[] = {Decimal128::kNormalizedZero, Decimal128::kLargestPositive, Decimal128::kSmallestPositive, Decimal128::kLargestNegative, Decimal128::kSmallestNegative, Decimal128(5), Decimal128(5.5), Decimal128(-5), Decimal128(-5.5)}; for (const auto& d : decimals) { ASSERT(representAs(d)->isEqual(d)); } } template void integerToDecimal128() { std::vector v{ Integer{5}, std::numeric_limits::lowest(), std::numeric_limits::max()}; if constexpr (std::is_signed_v) { v.emplace_back(-5); } for (const Integer n : v) { auto d = representAs(n); ASSERT(d); if (!d->isEqual(Decimal128(std::to_string(n)))) { FAIL( "Failed expectation, representAs({}) == Decimal128({})," " but !Decimal128({}).isEqual(Decimal128(std::to_string({}))"_format( n, d->toString(), d->toString(), n)); } } } TEST(RepresentAs, Int8ToDecimal128) { integerToDecimal128(); } TEST(RepresentAs, UInt8ToDecimal128) { integerToDecimal128(); } TEST(RepresentAs, Int16ToDecimal128) { integerToDecimal128(); } TEST(RepresentAs, UInt16ToDecimal128) { integerToDecimal128(); } TEST(RepresentAs, Int32ToDecimal128) { integerToDecimal128(); } TEST(RepresentAs, UInt32ToDecimal128) { integerToDecimal128(); } TEST(RepresentAs, Int64ToDecimal128) { integerToDecimal128(); } TEST(RepresentAs, UInt64ToDecimal128) { integerToDecimal128(); } template void floatToDecimal128() { Float x = 5.5; ASSERT(representAs(x)->isEqual(Decimal128(std::to_string(x)))); ASSERT(representAs(-x)->isEqual(Decimal128(std::to_string(-x)))); // Assert extreme numbers can be represented as Decimal128, and one basic property of each. using limits = std::numeric_limits; ASSERT(representAs(limits::lowest())->isLess(Decimal128(0))); ASSERT(representAs(limits::min())->isGreater(Decimal128(0))); ASSERT(representAs(limits::denorm_min())->isGreater(Decimal128(0))); ASSERT(representAs(limits::max())->isGreater(Decimal128(0))); ASSERT(representAs(limits::quiet_NaN())->isNaN()); ASSERT(representAs(-limits::quiet_NaN())->isNaN()); ASSERT(representAs(limits::infinity())->isInfinite()); ASSERT(representAs(-limits::infinity())->isInfinite()); ASSERT(representAs(Float{0})->isEqual(Decimal128(0))); ASSERT(representAs(Float{-0})->isEqual(Decimal128(0))); } TEST(RepresentAs, FloatToDecimal128) { floatToDecimal128(); } TEST(RepresentAs, DoubleToDecimal128) { floatToDecimal128(); } TEST(RepresentAs, PlatformDependent) { // signed char ASSERT(*(representAs(kSCharMax)) == kSCharMaxAsInt); ASSERT(!(representAs(kIntMax))); // unspecified/bare char ASSERT(*(representAs(kCharMax)) == kCharMaxAsInt); // unsigned char ASSERT(*(representAs(kUCharMax)) == kUCharMaxAsInt); ASSERT(!(representAs(kIntMin))); // long long ASSERT(!(representAs(kLongLongMax))); ASSERT(*(representAs(kIntMin)) == kIntMinAsLongLong); // unsigned long long ASSERT(!(representAs(kULongLongMax))); ASSERT(*(representAs(kIntMax)) == kIntMaxAsULongLong); } TEST(RepresentAs, NaN) { ASSERT(!(representAs(std::nanf("1")))); ASSERT(!(representAs(std::nanf("1")))); // NaN Identities ASSERT(std::isnan(*representAs(std::nanf("1")))); ASSERT(std::isnan(*representAs(std::nanf("1")))); ASSERT(std::isnan(*representAs(std::nan("1")))); ASSERT(std::isnan(*representAs(std::nan("1")))); } TEST(RepresentAs, LostPrecision) { // A loss of precision should result in a disengaged optional ASSERT(!(representAs(kInt32TooPreciseForFloat))); ASSERT(!(representAs(kUInt32TooPreciseForFloat))); ASSERT(!(representAs(kInt64TooPreciseForFloat))); ASSERT(!(representAs(kUInt64TooPreciseForFloat))); ASSERT(!(representAs(kInt64TooPreciseForDouble))); ASSERT(!(representAs(kUInt64TooPreciseForDouble))); } TEST(RepresentAs, Identity) { ASSERT(*(representAs(kInt32Max)) == kInt32Max); ASSERT(*(representAs(kInt64Max)) == kInt64Max); ASSERT(*(representAs(50)) == 50); ASSERT(*(representAs(kFloatMin)) == kFloatMin); ASSERT(*(representAs(kDoubleMax)) == kDoubleMax); ASSERT(*(representAs(kUInt32Max)) == kUInt32Max); ASSERT(*(representAs(kUInt64Max)) == kUInt64Max); } #if defined(_MSC_VER) #pragma warning(pop) #endif } // namespace mongo