-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
LibCrypto: Improve precision of Crypto::BigFraction::to_double()
Before: - FIXME: very naive implementation - was preventing passing some Temporal tests - https://github.com/tc39/test262 - https://github.com/LadybirdBrowser/libjs-test262
- Loading branch information
Showing
2 changed files
with
100 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,18 @@ | ||
/* | ||
* Copyright (c) 2022, Lucas Chollet <[email protected]> | ||
* Copyright (c) 2025, Manuel Zahariev <[email protected]> | ||
* | ||
* SPDX-License-Identifier: BSD-2-Clause | ||
*/ | ||
|
||
#include "BigFraction.h" | ||
#include "LibCrypto/BigInt/UnsignedBigInteger.h" | ||
#include <AK/ByteString.h> | ||
#include <AK/Math.h> | ||
#include <AK/StringBuilder.h> | ||
#include <LibCrypto/NumberTheory/ModularFunctions.h> | ||
#include <cstddef> | ||
#include <limits> | ||
|
||
namespace Crypto { | ||
|
||
|
@@ -134,10 +138,64 @@ BigFraction::BigFraction(double d) | |
m_numerator.set_to(negative ? (m_numerator.negated_value()) : m_numerator); | ||
} | ||
|
||
/* | ||
* Complexity O(N), where N=total number of words in numerator and denominator: | ||
* - shifts: O(N) | ||
* - division: constant (fixed bound on size of shifted numerator and denominator | ||
* - conversion to double: constant (64-bit quotient) | ||
*/ | ||
double BigFraction::to_double() const | ||
{ | ||
// FIXME: very naive implementation | ||
return m_numerator.to_double() / m_denominator.to_double(); | ||
// 1. Shift the numerator and denominator so that: | ||
// - the denominator is at most 64 bits | ||
// - the numerator is exactly 64 bits larger than the denominator | ||
size_t const bit_precision = 64; // divide the fraction to this precision | ||
bool const sign = m_numerator.is_negative(); | ||
|
||
int denominator_right_shift = 0; | ||
|
||
size_t bit_size_numerator = m_numerator.unsigned_value().one_based_index_of_highest_set_bit(); | ||
size_t bit_size_denominator = m_denominator.one_based_index_of_highest_set_bit(); | ||
|
||
if (bit_size_denominator > bit_precision) { // reduce precision of a large denominator | ||
denominator_right_shift = bit_size_denominator - bit_precision; | ||
bit_size_denominator = bit_precision; | ||
} | ||
|
||
int numerator_right_shift = bit_size_numerator - bit_size_denominator - bit_precision; | ||
|
||
UnsignedBigInteger shifted_denominator = m_denominator.shift_right(denominator_right_shift); | ||
UnsignedBigInteger shifted_numerator; | ||
if (numerator_right_shift < 0) | ||
shifted_numerator.set_to(m_numerator.unsigned_value().shift_left(-numerator_right_shift)); // increase numerator to increase precision | ||
else | ||
shifted_numerator.set_to(m_numerator.unsigned_value().shift_right(numerator_right_shift)); // decrease precision of numerator | ||
|
||
// 2. Divide the shifted numerator to the shifted denominator. The result will have 64-bit precision. | ||
// Then, convert the quotient to double. | ||
double result = SignedBigInteger { shifted_numerator.divided_by(shifted_denominator).quotient, sign }.to_double(); | ||
|
||
// 3. Convert the result to a double, including the denominator/numerator shifts in the exponent. | ||
using Extractor = FloatExtractor<double>; | ||
|
||
Extractor double_extractor; | ||
double_extractor.d = result; | ||
|
||
int exponent = double_extractor.exponent + numerator_right_shift - denominator_right_shift; | ||
|
||
if ((exponent < 0) && sign) // undeflow | ||
return -0.0; | ||
if (exponent < 0) | ||
return +0.0; | ||
if ((exponent > int(Extractor::exponent_max)) && sign) // overflow | ||
return -std::numeric_limits<double>::infinity(); | ||
if (exponent > int(Extractor::exponent_max)) | ||
return std::numeric_limits<double>::infinity(); | ||
|
||
double_extractor.sign = sign; | ||
double_extractor.exponent += (numerator_right_shift - denominator_right_shift); | ||
|
||
return double_extractor.d; | ||
} | ||
|
||
bool BigFraction::is_zero() const | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,28 @@ | ||
/* | ||
* Copyright (c) 2024, Tim Ledbetter <[email protected]> | ||
* Copyright (c) 2025, Manuel Zahariev <[email protected]> | ||
* | ||
* SPDX-License-Identifier: BSD-2-Clause | ||
*/ | ||
|
||
#include <LibCrypto/BigFraction/BigFraction.h> | ||
#include <LibCrypto/BigInt/SignedBigInteger.h> | ||
#include <LibCrypto/BigInt/UnsignedBigInteger.h> | ||
#include <LibTest/Macros.h> | ||
#include <LibTest/TestCase.h> | ||
|
||
static Crypto::UnsignedBigInteger bigint_fibonacci(size_t n) | ||
{ | ||
Crypto::UnsignedBigInteger num1(0); | ||
Crypto::UnsignedBigInteger num2(1); | ||
for (size_t i = 0; i < n; ++i) { | ||
Crypto::UnsignedBigInteger t = num1.plus(num2); | ||
num2 = num1; | ||
num1 = t; | ||
} | ||
return num1; | ||
} | ||
|
||
TEST_CASE(roundtrip_from_string) | ||
{ | ||
Array valid_number_strings { | ||
|
@@ -26,3 +42,27 @@ TEST_CASE(roundtrip_from_string) | |
EXPECT_EQ(result.to_string(precision), valid_number_string); | ||
} | ||
} | ||
|
||
TEST_CASE(big_fraction_to_double) | ||
{ | ||
// Golden ratio: | ||
// - limit (inf) ratio of two consecutive fibonacci numbers | ||
// - also ( 1 + sqrt( 5 ))/2 | ||
Crypto::BigFraction phi(Crypto::SignedBigInteger { bigint_fibonacci(500) }, bigint_fibonacci(499)); | ||
// Power 64 of golden ratio: | ||
// - limit ratio of two 64-separated fibonacci numbers | ||
// - also (23725150497407 + 10610209857723 * sqrt( 5 ))/2 | ||
Crypto::BigFraction phi_64(Crypto::SignedBigInteger { bigint_fibonacci(564) }, bigint_fibonacci(500)); | ||
|
||
EXPECT_EQ(phi.to_double(), 1.618033988749895); // 1.6180339887498948482045868343656381177203091798057628621... (https://oeis.org/A001622) | ||
EXPECT_EQ(phi_64.to_double(), 23725150497407); // 23725150497406.9999999999999578506361799772097881088769... (https://www.calculator.net/big-number-calculator.html) | ||
} | ||
|
||
TEST_CASE(big_fraction_temporal_duration_precision_support) | ||
{ | ||
// https://github.com/tc39/test262/blob/main/test/built-ins/Temporal/Duration/prototype/total/precision-exact-mathematical-values-1.js | ||
// Express 4000h and 1ns in hours, as a double | ||
Crypto::BigFraction temporal_duration_precision_test = Crypto::BigFraction { Crypto::SignedBigInteger { "14400000000000001"_bigint }, "3600000000000"_bigint }; | ||
|
||
EXPECT_EQ(temporal_duration_precision_test.to_double(), 4000.0000000000005); | ||
} |