diff --git a/Tests/LibCrypto/TestBigInteger.cpp b/Tests/LibCrypto/TestBigInteger.cpp index 117f5e1891..3a94712739 100644 --- a/Tests/LibCrypto/TestBigInteger.cpp +++ b/Tests/LibCrypto/TestBigInteger.cpp @@ -750,7 +750,7 @@ EXPECT_EQUAL_TO(zero, -0.0); TEST_CASE(to_double) { #define EXPECT_TO_EQUAL_DOUBLE(bigint, double_value) \ - EXPECT_EQ((bigint).to_double(), double_value) + EXPECT_EQ((bigint).to_double(Crypto::UnsignedBigInteger::RoundingMode::RoundTowardZero), double_value) EXPECT_TO_EQUAL_DOUBLE(Crypto::UnsignedBigInteger(0), 0.0); // Make sure we don't get negative zero! @@ -825,7 +825,41 @@ TEST_CASE(to_double) EXPECT_TO_EQUAL_DOUBLE(Crypto::SignedBigInteger::from_base(10, "2345678901234567890"sv), 2345678901234567680.0); - EXPECT_EQ(1234567890123456800.0, 1234567890123456768.0); + EXPECT_EQ( + Crypto::UnsignedBigInteger::from_base(16, "1fffffffffffff00"sv).to_double(Crypto::UnsignedBigInteger::RoundingMode::IEEERoundAndTiesToEvenMantissa), + 2305843009213693696.0); + + EXPECT_EQ( + Crypto::UnsignedBigInteger::from_base(16, "1fffffffffffff00"sv).to_double(Crypto::UnsignedBigInteger::RoundingMode::RoundTowardZero), + 2305843009213693696.0); + + EXPECT_EQ( + Crypto::UnsignedBigInteger::from_base(16, "1fffffffffffff80"sv).to_double(Crypto::UnsignedBigInteger::RoundingMode::IEEERoundAndTiesToEvenMantissa), + 2305843009213693952.0); + + EXPECT_EQ(Crypto::UnsignedBigInteger::from_base(16, "20000000000001"sv).to_double(Crypto::UnsignedBigInteger::RoundingMode::IEEERoundAndTiesToEvenMantissa), + 9007199254740992.0); + + EXPECT_EQ(Crypto::UnsignedBigInteger::from_base(16, "20000000000002"sv).to_double(Crypto::UnsignedBigInteger::RoundingMode::IEEERoundAndTiesToEvenMantissa), + 9007199254740994.0); + + // 2^53 = 20000000000000, +3 Rounds up because of tiesRoundToEven + EXPECT_EQ(Crypto::UnsignedBigInteger::from_base(16, "20000000000003"sv).to_double(Crypto::UnsignedBigInteger::RoundingMode::IEEERoundAndTiesToEvenMantissa), + 9007199254740996.0); + + // +4 is exactly 9007199254740996 + EXPECT_EQ(Crypto::UnsignedBigInteger::from_base(16, "20000000000004"sv).to_double(Crypto::UnsignedBigInteger::RoundingMode::IEEERoundAndTiesToEvenMantissa), + 9007199254740996.0); + + // +5 rounds down because of tiesRoundToEven + EXPECT_EQ(Crypto::UnsignedBigInteger::from_base(16, "20000000000005"sv).to_double(Crypto::UnsignedBigInteger::RoundingMode::IEEERoundAndTiesToEvenMantissa), + 9007199254740996.0); + + EXPECT_EQ(Crypto::UnsignedBigInteger::from_base(16, "20000000000006"sv).to_double(Crypto::UnsignedBigInteger::RoundingMode::IEEERoundAndTiesToEvenMantissa), + 9007199254740998.0); + + EXPECT_EQ(Crypto::UnsignedBigInteger::from_base(10, "98382635059784269824"sv).to_double(Crypto::UnsignedBigInteger::RoundingMode::IEEERoundAndTiesToEvenMantissa), + bit_cast(0x4415555555555555ULL)); #undef EXPECT_TO_EQUAL_DOUBLE } diff --git a/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp b/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp index c4a49190e6..5ea14dbbc3 100644 --- a/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp +++ b/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp @@ -65,9 +65,9 @@ u64 SignedBigInteger::to_u64() const return ~(unsigned_value - 1); // equivalent to `-unsigned_value`, but doesn't trigger UBSAN } -double SignedBigInteger::to_double() const +double SignedBigInteger::to_double(UnsignedBigInteger::RoundingMode rounding_mode) const { - double unsigned_value = m_unsigned_data.to_double(); + double unsigned_value = m_unsigned_data.to_double(rounding_mode); if (!m_sign) return unsigned_value; diff --git a/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.h b/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.h index 7b1a6b25f0..4a881df0a4 100644 --- a/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.h +++ b/Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, the SerenityOS developers. + * Copyright (c) 2022, David Tuin * * SPDX-License-Identifier: BSD-2-Clause */ @@ -67,7 +68,7 @@ public: [[nodiscard]] String to_base(u16 N) const; [[nodiscard]] u64 to_u64() const; - [[nodiscard]] double to_double() const; + [[nodiscard]] double to_double(UnsignedBigInteger::RoundingMode rounding_mode = UnsignedBigInteger::RoundingMode::IEEERoundAndTiesToEvenMantissa) const; [[nodiscard]] UnsignedBigInteger const& unsigned_value() const { return m_unsigned_data; } [[nodiscard]] Vector const words() const { return m_unsigned_data.words(); } diff --git a/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp b/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp index d9b8e7566d..e7a82fa7ee 100644 --- a/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp +++ b/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Itamar S. + * Copyright (c) 2022, David Tuin * * SPDX-License-Identifier: BSD-2-Clause */ @@ -113,28 +114,18 @@ u64 UnsignedBigInteger::to_u64() const return value; } -double UnsignedBigInteger::to_double() const +double UnsignedBigInteger::to_double(UnsignedBigInteger::RoundingMode rounding_mode) const { - // 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.) - + VERIFY(!is_invalid()); 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) + if (highest_bit < mantissa_size + 1) 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(); @@ -148,7 +139,7 @@ double UnsignedBigInteger::to_double() const 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 bits_to_read = min(mantissa_size, highest_bit); auto last_word_index = trimmed_length(); VERIFY(last_word_index > 0); @@ -157,12 +148,19 @@ double UnsignedBigInteger::to_double() const 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); + u64 mantissa = m_words[last_word_index - 1]; + if (highest_bit_index_in_top_word != 0) + mantissa <<= (bits_in_u64 - highest_bit_index_in_top_word); + else + mantissa = 0; auto bits_written = highest_bit_index_in_top_word; --last_word_index; + Optional dropped_bits_for_rounding; + u8 bits_dropped_from_final_word = 0; + 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 @@ -172,23 +170,101 @@ double UnsignedBigInteger::to_double() const bits_written += BITS_IN_WORD; --last_word_index; - if (bits_written < bits_to_read && last_word_index > 0) { + if (bits_written > bits_to_read) { + bits_dropped_from_final_word = bits_written - bits_to_read; + dropped_bits_for_rounding = m_words[last_word_index] & ((1 << bits_dropped_from_final_word) - 1); + } else 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]; + --last_word_index; auto bits_to_write = bits_to_read - bits_written; - final_word >>= (BITS_IN_WORD - bits_to_write); + bits_dropped_from_final_word = BITS_IN_WORD - bits_to_write; + dropped_bits_for_rounding = final_word & ((1 << bits_dropped_from_final_word) - 1u); + final_word >>= bits_dropped_from_final_word; // 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); + mantissa |= final_word << (bits_in_u64 - bits_written - bits_to_write); } } // Now the mantissa should be complete so shift it down mantissa >>= bits_in_u64 - mantissa_size; + if (rounding_mode == RoundingMode::IEEERoundAndTiesToEvenMantissa) { + bool round_up = false; + + if (bits_dropped_from_final_word == 0) { + if (last_word_index > 0) { + Word next_word = m_words[last_word_index - 1]; + last_word_index--; + if ((next_word & 0x80000000) != 0) { + // next top bit set check for any other bits + if ((next_word ^ 0x80000000) != 0) { + round_up = true; + } else { + while (last_word_index > 0) { + if (m_words[last_word_index - 1] != 0) { + round_up = true; + break; + } + } + + // All other bits are 0 which is a tie thus round to even exponent + // Since we are halfway, if exponent ends with 1 we round up, if 0 we round down + round_up = (mantissa & 1) != 0; + } + } else { + round_up = false; + } + } else { + // If there are no words left the rest is implicitly 0 so just round down + round_up = false; + } + + } else { + VERIFY(dropped_bits_for_rounding.has_value()); + VERIFY(bits_dropped_from_final_word >= 1); + + // In this case the top bit comes form the dropped bits + auto top_bit_extractor = 1u << (bits_dropped_from_final_word - 1u); + if ((*dropped_bits_for_rounding & top_bit_extractor) != 0) { + // Possible tie again, if any other bit is set we round up + if ((*dropped_bits_for_rounding ^ top_bit_extractor) != 0) { + round_up = true; + } else { + while (last_word_index > 0) { + if (m_words[last_word_index - 1] != 0) { + round_up = true; + break; + } + } + + round_up = (mantissa & 1) != 0; + } + } else { + round_up = false; + } + } + + if (round_up) { + ++mantissa; + if ((mantissa & (1ull << mantissa_size)) != 0) { + // we overflowed the mantissa + mantissa = 0; + highest_bit++; + + // In which case it is possible we have to round to infinity + if (highest_bit > exponent_bias) + return __builtin_huge_val(); + } + } + } else { + VERIFY(rounding_mode == RoundingMode::RoundTowardZero); + } + union FloatExtractor { struct { unsigned long long mantissa : mantissa_size; diff --git a/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.h b/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.h index 7a24ba6517..fd413308db 100644 --- a/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.h +++ b/Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2020, Itamar S. * Copyright (c) 2022, the SerenityOS developers. + * Copyright (c) 2022, David Tuin * * SPDX-License-Identifier: BSD-2-Clause */ @@ -58,7 +59,15 @@ public: [[nodiscard]] String to_base(u16 N) const; [[nodiscard]] u64 to_u64() const; - [[nodiscard]] double to_double() const; + + enum class RoundingMode { + IEEERoundAndTiesToEvenMantissa, + RoundTowardZero, + // “the Number value for x”, https://tc39.es/ecma262/#number-value-for + ECMAScriptNumberValueFor = IEEERoundAndTiesToEvenMantissa, + }; + + [[nodiscard]] double to_double(RoundingMode rounding_mode = RoundingMode::IEEERoundAndTiesToEvenMantissa) const; [[nodiscard]] Vector const& words() const { return m_words; }