Handle the edge case of constructing a Number from int64_t::min()

This commit is contained in:
Ed Hennis
2025-12-09 19:53:27 -05:00
parent a035c478e7
commit aecd7ab4b1
3 changed files with 60 additions and 11 deletions

View File

@@ -336,6 +336,13 @@ private:
Number
shiftExponent(int exponentDelta) const;
// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep
// is negative, returns the positive value. This takes a little extra work
// because converting std::numeric_limits<std::int64_t>::min() flirts with
// UB, and can vary across compilers.
static internalrep
externalToInternal(rep mantissa);
class Guard;
};
@@ -374,11 +381,7 @@ inline Number::Number(internalrep mantissa, int exponent, normalized)
}
inline Number::Number(rep mantissa, int exponent)
: Number(
mantissa < 0,
mantissa < 0 ? -mantissa : mantissa,
exponent,
normalized{})
: Number(mantissa < 0, externalToInternal(mantissa), exponent, normalized{})
{
}

View File

@@ -15,11 +15,13 @@
#include <utility>
#ifdef _MSC_VER
#pragma message("Using boost::multiprecision::uint128_t")
#pragma message("Using boost::multiprecision::uint128_t and int128_t")
#include <boost/multiprecision/cpp_int.hpp>
using uint128_t = boost::multiprecision::uint128_t;
using int128_t = boost::multiprecision::int128_t;
#else // !defined(_MSC_VER)
using uint128_t = __uint128_t;
using int128_t = __int128_t;
#endif // !defined(_MSC_VER)
namespace ripple {
@@ -183,6 +185,29 @@ Number::Guard::round() noexcept
// Number
// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep is
// negative, returns the positive value. This takes a little extra work because
// converting std::numeric_limits<std::int64_t>::min() flirts with UB, and can
// vary across compilers.
Number::internalrep
Number::externalToInternal(rep mantissa)
{
// If the mantissa is already positive, just return it
if (mantissa >= 0)
return mantissa;
// If the mantissa is negative, but fits within the positive range of rep,
// return it negated
if (mantissa >= -std::numeric_limits<rep>::max())
return -mantissa;
// If the mantissa doesn't fit within the positive range, convert to
// int128_t, negate that, and cast it back down to the internalrep
// In practice, this is only going to cover the case of
// std::numeric_limits<rep>::min().
int128_t temp = mantissa;
return static_cast<internalrep>(-temp);
}
constexpr Number
Number::oneSmall()
{

View File

@@ -70,11 +70,14 @@ public:
test(
Number{std::numeric_limits<std::int64_t>::min()},
Number{
scale == MantissaRange::small
? -9'223'372'036'854'776
: std::numeric_limits<std::int64_t>::min(),
18 - Number::mantissaLog()},
scale == MantissaRange::small
? Number{-9'223'372'036'854'776, 3}
: Number{true, 9'223'372'036'854'775'808ULL, 0, Number::normalized{}},
__LINE__);
test(
Number{std::numeric_limits<std::int64_t>::min() + 1},
scale == MantissaRange::small ? Number{-9'223'372'036'854'776, 3}
: Number{-9'223'372'036'854'775'807},
__LINE__);
test(
Number{std::numeric_limits<std::int64_t>::max()},
@@ -1303,6 +1306,7 @@ public:
test(Number(-2, 10), "-20000000000");
test(Number(-2, 11), "-2e11");
switch (scale)
{
case MantissaRange::small:
@@ -1336,6 +1340,13 @@ public:
test(
-(Number{std::numeric_limits<std::int64_t>::max(), -3}),
"-9223372036854775");
test(
Number{std::numeric_limits<std::int64_t>::min(), 0},
"-9223372036854775e3");
test(
-(Number{std::numeric_limits<std::int64_t>::min(), 0}),
"9223372036854775e3");
}
break;
case MantissaRange::large:
@@ -1362,6 +1373,16 @@ public:
test(
-(Number{std::numeric_limits<std::int64_t>::max(), 0}),
"-9223372036854775807");
// Because the absolute value of min is larger than max, it
// will be scaled down to fit under max. Since we're
// rounding towards zero, the 8 at the end is dropped.
test(
Number{std::numeric_limits<std::int64_t>::min(), 0},
"-9223372036854775800");
test(
-(Number{std::numeric_limits<std::int64_t>::min(), 0}),
"9223372036854775800");
}
break;
default: