Explorar o código

LibCrypto: Add a constructor to (Un)SignedBigInteger taking a double

For now this will assume that the double given is exactly representable
as an integer, so no NaN, infinity or rounding.
davidot %!s(int64=2) %!d(string=hai) anos
pai
achega
528891bf69

+ 74 - 0
Tests/LibCrypto/TestBigInteger.cpp

@@ -864,6 +864,80 @@ TEST_CASE(to_double)
 #undef EXPECT_TO_EQUAL_DOUBLE
 #undef EXPECT_TO_EQUAL_DOUBLE
 }
 }
 
 
+TEST_CASE(bigint_from_double)
+{
+    {
+        Crypto::UnsignedBigInteger from_zero { 0.0 };
+        EXPECT(from_zero.is_zero());
+        EXPECT(!from_zero.is_invalid());
+    }
+
+#define SURVIVES_ROUND_TRIP_UNSIGNED(double_value)            \
+    {                                                         \
+        Crypto::UnsignedBigInteger bigint { (double_value) }; \
+        EXPECT_EQ(bigint.to_double(), (double_value));        \
+    }
+
+    SURVIVES_ROUND_TRIP_UNSIGNED(0.0);
+    SURVIVES_ROUND_TRIP_UNSIGNED(1.0);
+    SURVIVES_ROUND_TRIP_UNSIGNED(100000.0);
+    SURVIVES_ROUND_TRIP_UNSIGNED(1000000000000.0);
+    SURVIVES_ROUND_TRIP_UNSIGNED(10000000000000000000.0);
+    SURVIVES_ROUND_TRIP_UNSIGNED(NumericLimits<double>::max());
+
+    SURVIVES_ROUND_TRIP_UNSIGNED(bit_cast<double>(0x4340000000000002ULL));
+    SURVIVES_ROUND_TRIP_UNSIGNED(bit_cast<double>(0x4340000000000001ULL));
+    SURVIVES_ROUND_TRIP_UNSIGNED(bit_cast<double>(0x4340000000000000ULL));
+
+    // Failed on last bits of mantissa
+    SURVIVES_ROUND_TRIP_UNSIGNED(bit_cast<double>(0x7EDFFFFFFFFFFFFFULL));
+    SURVIVES_ROUND_TRIP_UNSIGNED(bit_cast<double>(0x7ed5555555555555ULL));
+    SURVIVES_ROUND_TRIP_UNSIGNED(bit_cast<double>(0x7EDCBA9876543210ULL));
+
+    // Has exactly exponent of 32
+    SURVIVES_ROUND_TRIP_UNSIGNED(bit_cast<double>(0x41f22f74e0000000ULL));
+
+#define SURVIVES_ROUND_TRIP_SIGNED(double_value)                      \
+    {                                                                 \
+        Crypto::SignedBigInteger bigint_positive { (double_value) };  \
+        EXPECT_EQ(bigint_positive.to_double(), (double_value));       \
+        Crypto::SignedBigInteger bigint_negative { -(double_value) }; \
+        EXPECT_EQ(bigint_negative.to_double(), -(double_value));      \
+        EXPECT(bigint_positive != bigint_negative);                   \
+        bigint_positive.negate();                                     \
+        EXPECT(bigint_positive == bigint_negative);                   \
+    }
+
+    {
+        // Negative zero should be converted to positive zero
+        double const negative_zero = bit_cast<double>(0x8000000000000000);
+
+        // However it should give a bit exact +0.0
+        Crypto::SignedBigInteger from_negative_zero { negative_zero };
+        EXPECT(from_negative_zero.is_zero());
+        EXPECT(!from_negative_zero.is_negative());
+        double result = from_negative_zero.to_double();
+        EXPECT_EQ(result, 0.0);
+        EXPECT_EQ(bit_cast<u64>(result), 0ULL);
+    }
+
+    SURVIVES_ROUND_TRIP_SIGNED(1.0);
+    SURVIVES_ROUND_TRIP_SIGNED(100000.0);
+    SURVIVES_ROUND_TRIP_SIGNED(-1000000000000.0);
+    SURVIVES_ROUND_TRIP_SIGNED(10000000000000000000.0);
+    SURVIVES_ROUND_TRIP_SIGNED(NumericLimits<double>::max());
+    SURVIVES_ROUND_TRIP_SIGNED(NumericLimits<double>::lowest());
+
+    SURVIVES_ROUND_TRIP_SIGNED(bit_cast<double>(0x4340000000000002ULL));
+    SURVIVES_ROUND_TRIP_SIGNED(bit_cast<double>(0x4340000000000001ULL));
+    SURVIVES_ROUND_TRIP_SIGNED(bit_cast<double>(0x4340000000000000ULL));
+    SURVIVES_ROUND_TRIP_SIGNED(bit_cast<double>(0x7EDFFFFFFFFFFFFFULL));
+    SURVIVES_ROUND_TRIP_SIGNED(bit_cast<double>(0x7ed5555555555555ULL));
+    SURVIVES_ROUND_TRIP_SIGNED(bit_cast<double>(0x7EDCBA9876543210ULL));
+
+#undef SURVIVES_ROUND_TRIP_SIGNED
+#undef SURVIVES_ROUND_TRIP_UNSIGNED
+}
 namespace AK {
 namespace AK {
 
 
 template<>
 template<>

+ 6 - 0
Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.cpp

@@ -11,6 +11,12 @@
 
 
 namespace Crypto {
 namespace Crypto {
 
 
+SignedBigInteger::SignedBigInteger(double value)
+    : m_sign(value < 0.0)
+    , m_unsigned_data(fabs(value))
+{
+}
+
 SignedBigInteger SignedBigInteger::import_data(u8 const* ptr, size_t length)
 SignedBigInteger SignedBigInteger::import_data(u8 const* ptr, size_t length)
 {
 {
     bool sign = *ptr;
     bool sign = *ptr;

+ 2 - 0
Userland/Libraries/LibCrypto/BigInt/SignedBigInteger.h

@@ -43,6 +43,8 @@ public:
     {
     {
     }
     }
 
 
+    explicit SignedBigInteger(double value);
+
     [[nodiscard]] static SignedBigInteger create_invalid()
     [[nodiscard]] static SignedBigInteger create_invalid()
     {
     {
         return { UnsignedBigInteger::create_invalid(), false };
         return { UnsignedBigInteger::create_invalid(), false };

+ 77 - 8
Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp

@@ -11,6 +11,7 @@
 #include <AK/StringBuilder.h>
 #include <AK/StringBuilder.h>
 #include <AK/StringHash.h>
 #include <AK/StringHash.h>
 #include <LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h>
 #include <LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h>
+#include <math.h>
 
 
 namespace Crypto {
 namespace Crypto {
 
 
@@ -33,6 +34,81 @@ UnsignedBigInteger::UnsignedBigInteger(u8 const* ptr, size_t length)
     }
     }
 }
 }
 
 
+static constexpr u64 mantissa_size = 52;
+static constexpr u64 exponent_size = 11;
+static constexpr auto exponent_bias = (1 << (exponent_size - 1)) - 1;
+
+union DoubleExtractor {
+    struct {
+        unsigned long long mantissa : mantissa_size;
+        unsigned exponent : exponent_size;
+        unsigned sign : 1;
+    };
+    double double_value = 0;
+};
+
+UnsignedBigInteger::UnsignedBigInteger(double value)
+{
+    // Because this is currently only used for LibJS we VERIFY some preconditions
+    // also these values don't have a clear BigInteger representation.
+    VERIFY(!isnan(value));
+    VERIFY(!isinf(value));
+    VERIFY(trunc(value) == value);
+    VERIFY(value >= 0.0);
+
+    if (value <= NumericLimits<u32>::max()) {
+        m_words.append(static_cast<u32>(value));
+        return;
+    }
+
+    DoubleExtractor extractor;
+    extractor.double_value = value;
+    VERIFY(!extractor.sign);
+
+    i32 real_exponent = extractor.exponent - exponent_bias;
+    VERIFY(real_exponent > 0);
+
+    // Ensure we have enough space, we will need 2^exponent bits, so round up in words
+    auto word_index = (real_exponent + BITS_IN_WORD) / BITS_IN_WORD;
+    m_words.resize_and_keep_capacity(word_index);
+
+    // Now we just need to put the mantissa with explicit 1 bit at the top at the proper location
+    u64 raw_mantissa = extractor.mantissa | (1ull << mantissa_size);
+    VERIFY((raw_mantissa & 0xfff0000000000000) == 0x0010000000000000);
+    // Shift it so the bits we need are at the top
+    raw_mantissa <<= 64 - mantissa_size - 1;
+
+    // The initial bit needs to be exactly aligned with exponent, this is 1-indexed
+    auto top_word_bit_offset = real_exponent % BITS_IN_WORD + 1;
+
+    auto top_word_bits_from_mantissa = raw_mantissa >> (64 - top_word_bit_offset);
+    VERIFY(top_word_bits_from_mantissa <= NumericLimits<Word>::max());
+    m_words[word_index - 1] = top_word_bits_from_mantissa;
+
+    --word_index;
+    // Shift used bits away
+    raw_mantissa <<= top_word_bit_offset;
+    i32 bits_in_mantissa = mantissa_size + 1 - top_word_bit_offset;
+    // Now just put everything at the top of the next words
+
+    constexpr auto to_word_shift = 64 - BITS_IN_WORD;
+
+    while (word_index > 0 && bits_in_mantissa > 0) {
+        VERIFY((raw_mantissa >> to_word_shift) <= NumericLimits<Word>::max());
+        m_words[word_index - 1] = raw_mantissa >> to_word_shift;
+        raw_mantissa <<= to_word_shift;
+
+        bits_in_mantissa -= BITS_IN_WORD;
+        --word_index;
+    }
+
+    VERIFY(m_words.size() > word_index);
+    VERIFY((m_words.size() - word_index) <= 3);
+
+    // No bits left, otherwise we would have to round
+    VERIFY(raw_mantissa == 0);
+}
+
 UnsignedBigInteger UnsignedBigInteger::create_invalid()
 UnsignedBigInteger UnsignedBigInteger::create_invalid()
 {
 {
     UnsignedBigInteger invalid(0);
     UnsignedBigInteger invalid(0);
@@ -265,14 +341,7 @@ double UnsignedBigInteger::to_double(UnsignedBigInteger::RoundingMode rounding_m
         VERIFY(rounding_mode == RoundingMode::RoundTowardZero);
         VERIFY(rounding_mode == RoundingMode::RoundTowardZero);
     }
     }
 
 
-    union FloatExtractor {
-        struct {
-            unsigned long long mantissa : mantissa_size;
-            unsigned exponent : exponent_size;
-            unsigned sign : 1;
-        };
-        double double_value = 0;
-    } extractor;
+    DoubleExtractor extractor;
 
 
     extractor.exponent = highest_bit + exponent_bias;
     extractor.exponent = highest_bit + exponent_bias;
 
 

+ 2 - 0
Userland/Libraries/LibCrypto/BigInt/UnsignedBigInteger.h

@@ -39,6 +39,8 @@ public:
 
 
     explicit UnsignedBigInteger(u8 const* ptr, size_t length);
     explicit UnsignedBigInteger(u8 const* ptr, size_t length);
 
 
+    explicit UnsignedBigInteger(double value);
+
     UnsignedBigInteger() = default;
     UnsignedBigInteger() = default;
 
 
     [[nodiscard]] static UnsignedBigInteger create_invalid();
     [[nodiscard]] static UnsignedBigInteger create_invalid();