From 8b8cee31728b02a500ee530e081c7b767ebb68cd Mon Sep 17 00:00:00 2001 From: davidot Date: Wed, 24 Aug 2022 10:13:16 +0200 Subject: [PATCH] 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. --- Tests/LibCrypto/TestBigInteger.cpp | 219 ++++++++++++------ .../LibCrypto/BigInt/SignedBigInteger.cpp | 2 + .../LibCrypto/BigInt/UnsignedBigInteger.cpp | 91 +++++++- 3 files changed, 241 insertions(+), 71 deletions(-) diff --git a/Tests/LibCrypto/TestBigInteger.cpp b/Tests/LibCrypto/TestBigInteger.cpp index 308ef61fc9..117f5e1891 100644 --- a/Tests/LibCrypto/TestBigInteger.cpp +++ b/Tests/LibCrypto/TestBigInteger.cpp @@ -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::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::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::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::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(0x406fe00000000001ULL); - double just_below_255 = bit_cast(0x406fdfffffffffffULL); - double double_255 = 255.0; - Crypto::SignedBigInteger bigint_255 { 255 }; +{ + double just_above_255 = bit_cast(0x406fe00000000001ULL); + double just_below_255 = bit_cast(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::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<> diff --git a/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp b/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp index 48e56970a4..c4a49190e6 100644 --- a/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp +++ b/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp @@ -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; } diff --git a/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp b/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp index 23fcd2dde9..d9b8e7566d 100644 --- a/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp +++ b/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp @@ -115,8 +115,95 @@ u64 UnsignedBigInteger::to_u64() const double UnsignedBigInteger::to_double() const { - // FIXME: I am naive - return static_cast(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(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(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()