LibCrypto: Implement a (mostly) proper to_double for UnsignedBigInteger

SignedBigInteger can immediately use this by just negating the double if
the sign bit is set.
For simple cases (below 2^53) we can just convert via an u64, however
above that we need to extract the top 53 bits and use those as the
mantissa.

This function currently does not behave exactly as the JS spec specifies
however it is much less naive than the previous implementation.
This commit is contained in:
davidot 2022-08-24 10:13:16 +02:00 committed by Linus Groh
parent 2290fbc2a0
commit 8b8cee3172
Notes: sideshowbarker 2024-07-17 07:47:44 +09:00
3 changed files with 241 additions and 71 deletions

View File

@ -659,96 +659,177 @@ TEST_CASE(test_negative_zero_is_not_allowed)
EXPECT(!zero.is_negative());
}
TEST_CASE(double_comparisons)
{
TEST_CASE(double_comparisons) {
#define EXPECT_LESS_THAN(bigint, double_value) EXPECT_EQ(bigint.compare_to_double(double_value), Crypto::SignedBigInteger::CompareResult::DoubleGreaterThanBigInt)
#define EXPECT_GREATER_THAN(bigint, double_value) EXPECT_EQ(bigint.compare_to_double(double_value), Crypto::SignedBigInteger::CompareResult::DoubleLessThanBigInt)
#define EXPECT_EQUAL_TO(bigint, double_value) EXPECT_EQ(bigint.compare_to_double(double_value), Crypto::SignedBigInteger::CompareResult::DoubleEqualsBigInt)
{
Crypto::SignedBigInteger zero { 0 };
EXPECT_EQUAL_TO(zero, 0.0);
EXPECT_EQUAL_TO(zero, -0.0);
}
{ Crypto::SignedBigInteger zero { 0 };
EXPECT_EQUAL_TO(zero, 0.0);
EXPECT_EQUAL_TO(zero, -0.0);
}
{
Crypto::SignedBigInteger one { 1 };
EXPECT_EQUAL_TO(one, 1.0);
EXPECT_GREATER_THAN(one, -1.0);
EXPECT_GREATER_THAN(one, 0.5);
EXPECT_GREATER_THAN(one, -0.5);
EXPECT_LESS_THAN(one, 1.000001);
{
Crypto::SignedBigInteger one { 1 };
EXPECT_EQUAL_TO(one, 1.0);
EXPECT_GREATER_THAN(one, -1.0);
EXPECT_GREATER_THAN(one, 0.5);
EXPECT_GREATER_THAN(one, -0.5);
EXPECT_LESS_THAN(one, 1.000001);
one.negate();
auto const& negative_one = one;
EXPECT_EQUAL_TO(negative_one, -1.0);
EXPECT_LESS_THAN(negative_one, 1.0);
EXPECT_LESS_THAN(one, 0.5);
EXPECT_LESS_THAN(one, -0.5);
EXPECT_GREATER_THAN(one, -1.5);
EXPECT_LESS_THAN(one, 1.000001);
EXPECT_GREATER_THAN(one, -1.000001);
}
one.negate();
auto const& negative_one = one;
EXPECT_EQUAL_TO(negative_one, -1.0);
EXPECT_LESS_THAN(negative_one, 1.0);
EXPECT_LESS_THAN(one, 0.5);
EXPECT_LESS_THAN(one, -0.5);
EXPECT_GREATER_THAN(one, -1.5);
EXPECT_LESS_THAN(one, 1.000001);
EXPECT_GREATER_THAN(one, -1.000001);
}
{
double double_max_value = NumericLimits<double>::max();
double double_below_max_value = nextafter(double_max_value, 0.0);
VERIFY(double_below_max_value < double_max_value);
VERIFY(double_below_max_value < (double_max_value - 1.0));
auto max_value_in_bigint = Crypto::SignedBigInteger::from_base(16, "fffffffffffff800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"sv);
auto max_value_plus_one = max_value_in_bigint.plus(Crypto::SignedBigInteger { 1 });
auto max_value_minus_one = max_value_in_bigint.minus(Crypto::SignedBigInteger { 1 });
{
double double_max_value = NumericLimits<double>::max();
double double_below_max_value = nextafter(double_max_value, 0.0);
VERIFY(double_below_max_value < double_max_value);
VERIFY(double_below_max_value < (double_max_value - 1.0));
auto max_value_in_bigint = Crypto::SignedBigInteger::from_base(16, "fffffffffffff800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"sv);
auto max_value_plus_one = max_value_in_bigint.plus(Crypto::SignedBigInteger { 1 });
auto max_value_minus_one = max_value_in_bigint.minus(Crypto::SignedBigInteger { 1 });
auto below_max_value_in_bigint = Crypto::SignedBigInteger::from_base(16, "fffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"sv);
auto below_max_value_in_bigint = Crypto::SignedBigInteger::from_base(16, "fffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"sv);
EXPECT_EQUAL_TO(max_value_in_bigint, double_max_value);
EXPECT_LESS_THAN(max_value_minus_one, double_max_value);
EXPECT_GREATER_THAN(max_value_plus_one, double_max_value);
EXPECT_LESS_THAN(below_max_value_in_bigint, double_max_value);
EXPECT_EQUAL_TO(max_value_in_bigint, double_max_value);
EXPECT_LESS_THAN(max_value_minus_one, double_max_value);
EXPECT_GREATER_THAN(max_value_plus_one, double_max_value);
EXPECT_LESS_THAN(below_max_value_in_bigint, double_max_value);
EXPECT_GREATER_THAN(max_value_in_bigint, double_below_max_value);
EXPECT_GREATER_THAN(max_value_minus_one, double_below_max_value);
EXPECT_GREATER_THAN(max_value_plus_one, double_below_max_value);
EXPECT_EQUAL_TO(below_max_value_in_bigint, double_below_max_value);
}
EXPECT_GREATER_THAN(max_value_in_bigint, double_below_max_value);
EXPECT_GREATER_THAN(max_value_minus_one, double_below_max_value);
EXPECT_GREATER_THAN(max_value_plus_one, double_below_max_value);
EXPECT_EQUAL_TO(below_max_value_in_bigint, double_below_max_value);
}
{
double double_min_value = NumericLimits<double>::lowest();
double double_above_min_value = nextafter(double_min_value, 0.0);
VERIFY(double_above_min_value > double_min_value);
VERIFY(double_above_min_value > (double_min_value + 1.0));
auto min_value_in_bigint = Crypto::SignedBigInteger::from_base(16, "-fffffffffffff800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"sv);
auto min_value_plus_one = min_value_in_bigint.plus(Crypto::SignedBigInteger { 1 });
auto min_value_minus_one = min_value_in_bigint.minus(Crypto::SignedBigInteger { 1 });
{
double double_min_value = NumericLimits<double>::lowest();
double double_above_min_value = nextafter(double_min_value, 0.0);
VERIFY(double_above_min_value > double_min_value);
VERIFY(double_above_min_value > (double_min_value + 1.0));
auto min_value_in_bigint = Crypto::SignedBigInteger::from_base(16, "-fffffffffffff800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"sv);
auto min_value_plus_one = min_value_in_bigint.plus(Crypto::SignedBigInteger { 1 });
auto min_value_minus_one = min_value_in_bigint.minus(Crypto::SignedBigInteger { 1 });
auto above_min_value_in_bigint = Crypto::SignedBigInteger::from_base(16, "-fffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"sv);
auto above_min_value_in_bigint = Crypto::SignedBigInteger::from_base(16, "-fffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"sv);
EXPECT_EQUAL_TO(min_value_in_bigint, double_min_value);
EXPECT_LESS_THAN(min_value_minus_one, double_min_value);
EXPECT_GREATER_THAN(min_value_plus_one, double_min_value);
EXPECT_GREATER_THAN(above_min_value_in_bigint, double_min_value);
EXPECT_EQUAL_TO(min_value_in_bigint, double_min_value);
EXPECT_LESS_THAN(min_value_minus_one, double_min_value);
EXPECT_GREATER_THAN(min_value_plus_one, double_min_value);
EXPECT_GREATER_THAN(above_min_value_in_bigint, double_min_value);
EXPECT_LESS_THAN(min_value_in_bigint, double_above_min_value);
EXPECT_LESS_THAN(min_value_minus_one, double_above_min_value);
EXPECT_LESS_THAN(min_value_plus_one, double_above_min_value);
EXPECT_EQUAL_TO(above_min_value_in_bigint, double_above_min_value);
}
EXPECT_LESS_THAN(min_value_in_bigint, double_above_min_value);
EXPECT_LESS_THAN(min_value_minus_one, double_above_min_value);
EXPECT_LESS_THAN(min_value_plus_one, double_above_min_value);
EXPECT_EQUAL_TO(above_min_value_in_bigint, double_above_min_value);
}
{
double just_above_255 = bit_cast<double>(0x406fe00000000001ULL);
double just_below_255 = bit_cast<double>(0x406fdfffffffffffULL);
double double_255 = 255.0;
Crypto::SignedBigInteger bigint_255 { 255 };
{
double just_above_255 = bit_cast<double>(0x406fe00000000001ULL);
double just_below_255 = bit_cast<double>(0x406fdfffffffffffULL);
double double_255 = 255.0;
Crypto::SignedBigInteger bigint_255 { 255 };
EXPECT_EQUAL_TO(bigint_255, double_255);
EXPECT_GREATER_THAN(bigint_255, just_below_255);
EXPECT_LESS_THAN(bigint_255, just_above_255);
}
EXPECT_EQUAL_TO(bigint_255, double_255);
EXPECT_GREATER_THAN(bigint_255, just_below_255);
EXPECT_LESS_THAN(bigint_255, just_above_255);
}
#undef EXPECT_LESS_THAN
#undef EXPECT_GREATER_THAN
#undef EXPECT_EQUAL_TO
}
TEST_CASE(to_double)
{
#define EXPECT_TO_EQUAL_DOUBLE(bigint, double_value) \
EXPECT_EQ((bigint).to_double(), double_value)
EXPECT_TO_EQUAL_DOUBLE(Crypto::UnsignedBigInteger(0), 0.0);
// Make sure we don't get negative zero!
EXPECT_EQ(signbit(Crypto::UnsignedBigInteger(0).to_double()), 0);
{
Crypto::SignedBigInteger zero { 0 };
EXPECT(!zero.is_negative());
EXPECT_TO_EQUAL_DOUBLE(zero, 0.0);
EXPECT_EQ(signbit(zero.to_double()), 0);
zero.negate();
EXPECT(!zero.is_negative());
EXPECT_TO_EQUAL_DOUBLE(zero, 0.0);
EXPECT_EQ(signbit(zero.to_double()), 0);
}
EXPECT_TO_EQUAL_DOUBLE(Crypto::UnsignedBigInteger(9682), 9682.0);
EXPECT_TO_EQUAL_DOUBLE(Crypto::SignedBigInteger(-9660), -9660.0);
double double_max_value = NumericLimits<double>::max();
double infinity = INFINITY;
EXPECT_TO_EQUAL_DOUBLE(
Crypto::UnsignedBigInteger::from_base(16, "fffffffffffff800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"sv),
double_max_value);
EXPECT_TO_EQUAL_DOUBLE(
Crypto::UnsignedBigInteger::from_base(16, "ffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"sv),
double_max_value);
EXPECT_TO_EQUAL_DOUBLE(
Crypto::UnsignedBigInteger::from_base(16, "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"sv),
double_max_value);
EXPECT_TO_EQUAL_DOUBLE(
Crypto::UnsignedBigInteger::from_base(16, "10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"sv),
infinity);
EXPECT_TO_EQUAL_DOUBLE(
Crypto::SignedBigInteger::from_base(16, "-fffffffffffff800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"sv),
-double_max_value);
EXPECT_TO_EQUAL_DOUBLE(
Crypto::SignedBigInteger::from_base(16, "-ffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"sv),
-double_max_value);
EXPECT_TO_EQUAL_DOUBLE(
Crypto::SignedBigInteger::from_base(16, "-ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"sv),
-double_max_value);
EXPECT_TO_EQUAL_DOUBLE(
Crypto::SignedBigInteger::from_base(16, "-10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"sv),
-infinity);
EXPECT_TO_EQUAL_DOUBLE(
Crypto::UnsignedBigInteger::from_base(16, "ffffffffffffffff"sv),
18446744073709549568.0);
EXPECT_TO_EQUAL_DOUBLE(
Crypto::UnsignedBigInteger::from_base(16, "fffffffffffff800"sv),
18446744073709549568.0);
EXPECT_TO_EQUAL_DOUBLE(
Crypto::UnsignedBigInteger::from_base(16, "fffffffffffff8ff"sv),
18446744073709549568.0);
EXPECT_TO_EQUAL_DOUBLE(Crypto::SignedBigInteger::from_base(10, "1234567890123456789"sv),
1234567890123456800.0);
EXPECT_TO_EQUAL_DOUBLE(Crypto::SignedBigInteger::from_base(10, "2345678901234567890"sv),
2345678901234567680.0);
EXPECT_EQ(1234567890123456800.0, 1234567890123456768.0);
#undef EXPECT_TO_EQUAL_DOUBLE
}
namespace AK {
template<>

View File

@ -70,6 +70,8 @@ double SignedBigInteger::to_double() const
double unsigned_value = m_unsigned_data.to_double();
if (!m_sign)
return unsigned_value;
VERIFY(!is_zero());
return -unsigned_value;
}

View File

@ -115,8 +115,95 @@ u64 UnsignedBigInteger::to_u64() const
double UnsignedBigInteger::to_double() const
{
// FIXME: I am naive
return static_cast<double>(to_u64());
// NOTE: This function rounds toward zero!
// FIXME: Which is not exactly what we should do for JS when converting to number:
// See: https://tc39.es/ecma262/#sec-number-constructor-number-value
// Which has step 1.b If Type(prim) is BigInt, let n be 𝔽((prim)).
// Which then references: https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type
// Which is equivalent to (This procedure corresponds exactly to the behaviour of the IEEE 754-2019 roundTiesToEven mode.)
auto highest_bit = one_based_index_of_highest_set_bit();
if (highest_bit == 0)
return 0;
--highest_bit;
// Simple case if less than 2^53 since those number are all exactly representable in doubles
if (highest_bit < 53)
return static_cast<double>(to_u64());
constexpr u64 mantissa_size = 52;
constexpr u64 exponent_size = 11;
constexpr auto exponent_bias = (1 << (exponent_size - 1)) - 1;
// If it uses too many bit to represent in a double return infinity
if (highest_bit > exponent_bias)
return __builtin_huge_val();
// Otherwise we have to take the top 53 bits, use those as the mantissa,
// and the amount of bits as the exponent. Note that the mantissa has an implicit top bit of 1
// so we have to ignore the very top bit.
// Since we extract at most 53 bits it will take at most 3 words
static_assert(BITS_IN_WORD * 3 >= (mantissa_size + 1));
constexpr auto bits_in_u64 = 64;
static_assert(bits_in_u64 > mantissa_size + 1);
auto bits_to_read = min(mantissa_size + 1, highest_bit);
auto last_word_index = trimmed_length();
VERIFY(last_word_index > 0);
// Note that highest bit is 0-indexed at this point.
auto highest_bit_index_in_top_word = highest_bit % BITS_IN_WORD;
// Shift initial word until highest bit is just beyond top of u64.
u64 mantissa = static_cast<u64>(m_words[last_word_index - 1]) << (bits_in_u64 - highest_bit_index_in_top_word);
auto bits_written = highest_bit_index_in_top_word;
--last_word_index;
if (bits_written < bits_to_read && last_word_index > 0) {
// Second word can always just cleanly be shifted upto the final bit of the first word
// since the first has at most BIT_IN_WORD - 1, 31
u64 next_word = m_words[last_word_index - 1];
VERIFY((mantissa & (next_word << (bits_in_u64 - bits_written - BITS_IN_WORD))) == 0);
mantissa |= next_word << (bits_in_u64 - bits_written - BITS_IN_WORD);
bits_written += BITS_IN_WORD;
--last_word_index;
if (bits_written < bits_to_read && last_word_index > 0) {
// The final word has to be shifted down first to discard any excess bits.
u64 final_word = m_words[last_word_index - 1];
auto bits_to_write = bits_to_read - bits_written;
final_word >>= (BITS_IN_WORD - bits_to_write);
// Then move the bits right up to the lowest bits of the second word
VERIFY((mantissa & (final_word << (bits_in_u64 - bits_written - bits_to_write))) == 0);
mantissa |= final_word << (bits_in_u64 - bits_written - BITS_IN_WORD);
}
}
// Now the mantissa should be complete so shift it down
mantissa >>= bits_in_u64 - mantissa_size;
union FloatExtractor {
struct {
unsigned long long mantissa : mantissa_size;
unsigned exponent : exponent_size;
unsigned sign : 1;
};
double double_value = 0;
} extractor;
extractor.exponent = highest_bit + exponent_bias;
VERIFY((mantissa & 0xfff0000000000000) == 0);
extractor.mantissa = mantissa;
return extractor.double_value;
}
void UnsignedBigInteger::set_to_0()