diff --git a/Tests/LibCrypto/TestBigInteger.cpp b/Tests/LibCrypto/TestBigInteger.cpp index b13e8aafedd..308ef61fc98 100644 --- a/Tests/LibCrypto/TestBigInteger.cpp +++ b/Tests/LibCrypto/TestBigInteger.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include static Crypto::UnsignedBigInteger bigint_fibonacci(size_t n) { @@ -658,3 +658,114 @@ TEST_CASE(test_negative_zero_is_not_allowed) EXPECT(zero.unsigned_value().is_zero()); EXPECT(!zero.is_negative()); } + +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 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); + } + + { + 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); + + 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); + } + + { + 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); + + 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); + } + + { + 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); + } + +#undef EXPECT_LESS_THAN +#undef EXPECT_GREATER_THAN +#undef EXPECT_EQUAL_TO +} + +namespace AK { + +template<> +struct Formatter : Formatter { + ErrorOr format(FormatBuilder& builder, Crypto::SignedBigInteger::CompareResult const& compare_result) + { + switch (compare_result) { + case Crypto::SignedBigInteger::CompareResult::DoubleEqualsBigInt: + return builder.put_string("Equals"sv); + case Crypto::SignedBigInteger::CompareResult::DoubleLessThanBigInt: + return builder.put_string("LessThan"sv); + case Crypto::SignedBigInteger::CompareResult::DoubleGreaterThanBigInt: + return builder.put_string("GreaterThan"sv); + default: + return builder.put_string("???"sv); + } + } +}; + +} diff --git a/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp b/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp index 382939947a4..48e56970a45 100644 --- a/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp +++ b/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp @@ -1,11 +1,13 @@ /* * Copyright (c) 2020, the SerenityOS developers. + * Copyright (c) 2022, David Tuin * * SPDX-License-Identifier: BSD-2-Clause */ #include "SignedBigInteger.h" #include +#include namespace Crypto { @@ -334,6 +336,155 @@ bool SignedBigInteger::operator>=(SignedBigInteger const& other) const return !(*this < other); } +SignedBigInteger::CompareResult SignedBigInteger::compare_to_double(double value) const +{ + VERIFY(!isnan(value)); + + if (isinf(value)) { + bool is_positive_infinity = __builtin_isinf_sign(value) > 0; + return is_positive_infinity ? CompareResult::DoubleGreaterThanBigInt : CompareResult::DoubleLessThanBigInt; + } + + bool bigint_is_negative = m_sign; + + bool value_is_negative = value < 0; + + if (value_is_negative != bigint_is_negative) + return bigint_is_negative ? CompareResult::DoubleGreaterThanBigInt : CompareResult::DoubleLessThanBigInt; + + // Value is zero, and from above the signs must be the same. + if (value == 0.0) { + VERIFY(!value_is_negative && !bigint_is_negative); + // Either we are also zero or value is certainly less than us. + return is_zero() ? CompareResult::DoubleEqualsBigInt : CompareResult::DoubleLessThanBigInt; + } + + // If value is not zero but we are, then since the signs are the same value must be greater. + if (is_zero()) + return CompareResult::DoubleGreaterThanBigInt; + + constexpr u64 mantissa_size = 52; + constexpr u64 exponent_size = 11; + constexpr auto exponent_bias = (1 << (exponent_size - 1)) - 1; + union FloatExtractor { + struct { + unsigned long long mantissa : mantissa_size; + unsigned exponent : exponent_size; + unsigned sign : 1; + }; + double d; + } extractor; + + extractor.d = value; + VERIFY(extractor.exponent != (1 << exponent_size) - 1); + // Exponent cannot be filled as than we must be NaN or infinity. + + i32 real_exponent = extractor.exponent - exponent_bias; + if (real_exponent < 0) { + // |value| is less than 1, and we cannot be zero so if we are negative + // value must be greater and vice versa. + return bigint_is_negative ? CompareResult::DoubleGreaterThanBigInt : CompareResult::DoubleLessThanBigInt; + } + + u64 bigint_bits_needed = m_unsigned_data.one_based_index_of_highest_set_bit(); + VERIFY(bigint_bits_needed > 0); + + // Double value is `-1^sign (1.mantissa) * 2^(exponent - bias)` so we need + // `exponent - bias + 1` bit to represent doubles value, + // for example `exponent - bias` = 3, sign = 0 and mantissa = 0 we get + // `-1^0 * 2^3 * 1 = 8` which needs 4 bits to store 8 (0b1000). + u32 double_bits_needed = real_exponent + 1; + + if (bigint_bits_needed > double_bits_needed) { + // If we need more bits to represent us, we must be of greater magnitude + // this means that if we are negative we are below value and if positive above value. + return bigint_is_negative ? CompareResult::DoubleGreaterThanBigInt : CompareResult::DoubleLessThanBigInt; + } + + if (bigint_bits_needed < double_bits_needed) + return bigint_is_negative ? CompareResult::DoubleLessThanBigInt : CompareResult::DoubleGreaterThanBigInt; + + u64 mantissa_bits = extractor.mantissa; + + // We add the bit which represents the 1. of the double value calculation + constexpr u64 mantissa_extended_bit = 1ull << mantissa_size; + + mantissa_bits |= mantissa_extended_bit; + + // Now we shift value to the left virtually, with `exponent - bias` steps + // we then pretend both it and the big int are extended with virtual zeros. + using Word = UnsignedBigInteger::Word; + auto next_bigint_word = (UnsignedBigInteger::BITS_IN_WORD - 1 + bigint_bits_needed) / UnsignedBigInteger::BITS_IN_WORD; + + VERIFY(next_bigint_word + 1 == trimmed_length()); + + auto msb_in_top_word_index = (bigint_bits_needed - 1) % UnsignedBigInteger::BITS_IN_WORD; + VERIFY(msb_in_top_word_index == (UnsignedBigInteger::BITS_IN_WORD - count_leading_zeroes(words()[next_bigint_word - 1]) - 1)); + + // We will keep the bits which are still valid in the mantissa at the top of mantissa bits. + mantissa_bits <<= 64 - (mantissa_size + 1); + + auto bits_left_in_mantissa = mantissa_size + 1; + + auto get_next_value_bits = [&](size_t num_bits) -> Word { + VERIFY(num_bits < 63); + VERIFY(bits_left_in_mantissa > 0); + if (num_bits > bits_left_in_mantissa) + num_bits = bits_left_in_mantissa; + + bits_left_in_mantissa -= num_bits; + + u64 extracted_bits = mantissa_bits & (((1ull << num_bits) - 1) << (64 - num_bits)); + // Now shift the bits down to put the most significant bit on the num_bits position + // this means the rest will be "virtual" zeros. + extracted_bits >>= 32; + + // Now shift away the used bits and fit the result into a Word. + mantissa_bits <<= num_bits; + + VERIFY(extracted_bits <= NumericLimits::max()); + return static_cast(extracted_bits); + }; + + auto bits_in_next_bigint_word = msb_in_top_word_index + 1; + + while (next_bigint_word > 0 && bits_left_in_mantissa > 0) { + Word bigint_word = words()[next_bigint_word - 1]; + Word double_word = get_next_value_bits(bits_in_next_bigint_word); + + // For the first bit we have to align it with the top bit of bigint + // and for all the other cases bits_in_next_bigint_word is 32 so this does nothing. + double_word >>= 32 - bits_in_next_bigint_word; + + if (bigint_word < double_word) + return value_is_negative ? CompareResult::DoubleLessThanBigInt : CompareResult::DoubleGreaterThanBigInt; + + if (bigint_word > double_word) + return value_is_negative ? CompareResult::DoubleGreaterThanBigInt : CompareResult::DoubleLessThanBigInt; + + --next_bigint_word; + bits_in_next_bigint_word = UnsignedBigInteger::BITS_IN_WORD; + } + + // If there are still bits left in bigint than any non zero bit means it has greater magnitude. + if (next_bigint_word > 0) { + VERIFY(bits_left_in_mantissa == 0); + while (next_bigint_word > 0) { + if (words()[next_bigint_word - 1] != 0) + return value_is_negative ? CompareResult::DoubleGreaterThanBigInt : CompareResult::DoubleLessThanBigInt; + --next_bigint_word; + } + } else if (bits_left_in_mantissa > 0) { + VERIFY(next_bigint_word == 0); + // Similarly if there are still any bits set in the mantissa it has greater magnitude. + if (mantissa_bits != 0) + return value_is_negative ? CompareResult::DoubleLessThanBigInt : CompareResult::DoubleGreaterThanBigInt; + } + + // Otherwise if both don't have bits left or the rest of the bits are zero they are equal. + return CompareResult::DoubleEqualsBigInt; +} + } ErrorOr AK::Formatter::format(FormatBuilder& fmtbuilder, Crypto::SignedBigInteger const& value) diff --git a/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.h b/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.h index 78b69944468..7b1a6b25f0a 100644 --- a/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.h +++ b/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.h @@ -139,6 +139,14 @@ public: [[nodiscard]] bool operator<(UnsignedBigInteger const& other) const; [[nodiscard]] bool operator>(UnsignedBigInteger const& other) const; + enum class CompareResult { + DoubleEqualsBigInt, + DoubleLessThanBigInt, + DoubleGreaterThanBigInt + }; + + [[nodiscard]] CompareResult compare_to_double(double) const; + private: void ensure_sign_is_valid() {