mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 15:10:19 +00:00
AK: Implement floating-point conversions for big-endian
This commit is contained in:
parent
c99674c6ac
commit
1bc44376c0
Notes:
sideshowbarker
2024-07-17 09:56:35 +09:00
Author: https://github.com/sideeffect42 Commit: https://github.com/LadybirdBrowser/ladybird/commit/1bc44376c0 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/415 Reviewed-by: https://github.com/ADKaster Reviewed-by: https://github.com/DanShaders
3 changed files with 83 additions and 36 deletions
|
@ -25,9 +25,15 @@ union FloatExtractor<f128> {
|
|||
static constexpr int exponent_bits = 15;
|
||||
static constexpr unsigned exponent_max = 32767;
|
||||
struct [[gnu::packed]] {
|
||||
# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
ComponentType sign : 1;
|
||||
ComponentType exponent : 15;
|
||||
ComponentType mantissa : 112;
|
||||
# else
|
||||
ComponentType mantissa : 112;
|
||||
ComponentType exponent : 15;
|
||||
ComponentType sign : 1;
|
||||
# endif
|
||||
};
|
||||
f128 d;
|
||||
};
|
||||
|
@ -51,9 +57,15 @@ union FloatExtractor<f80> {
|
|||
// However, since all bit-fiddling float code assumes IEEE floats, it cannot handle this properly.
|
||||
// If we pretend that 80-bit floats are IEEE floats with 64-bit mantissas, almost everything works correctly
|
||||
// and we just need a few special cases.
|
||||
# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
ComponentType sign : 1;
|
||||
ComponentType exponent : 15;
|
||||
ComponentType mantissa : 64;
|
||||
# else
|
||||
ComponentType mantissa : 64;
|
||||
ComponentType exponent : 15;
|
||||
ComponentType sign : 1;
|
||||
# endif
|
||||
};
|
||||
f80 d;
|
||||
};
|
||||
|
@ -76,9 +88,15 @@ union FloatExtractor<f64> {
|
|||
// very intuitive and portable behaviour on windows, but it doesn't
|
||||
// work with the msvc ABI.
|
||||
// See <https://github.com/llvm/llvm-project/issues/24757>
|
||||
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
ComponentType sign : 1;
|
||||
ComponentType exponent : 11;
|
||||
ComponentType mantissa : 52;
|
||||
#else
|
||||
ComponentType mantissa : 52;
|
||||
ComponentType exponent : 11;
|
||||
ComponentType sign : 1;
|
||||
#endif
|
||||
};
|
||||
f64 d;
|
||||
};
|
||||
|
@ -93,9 +111,15 @@ union FloatExtractor<f32> {
|
|||
static constexpr int exponent_bits = 8;
|
||||
static constexpr ComponentType exponent_max = 255;
|
||||
struct [[gnu::packed]] {
|
||||
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
ComponentType sign : 1;
|
||||
ComponentType exponent : 8;
|
||||
ComponentType mantissa : 23;
|
||||
#else
|
||||
ComponentType mantissa : 23;
|
||||
ComponentType exponent : 8;
|
||||
ComponentType sign : 1;
|
||||
#endif
|
||||
};
|
||||
f32 d;
|
||||
};
|
||||
|
|
|
@ -176,8 +176,6 @@ static constexpr auto max_representable_power_of_ten_in_u64 = 19;
|
|||
static_assert(1e19 <= static_cast<double>(NumericLimits<u64>::max()));
|
||||
static_assert(1e20 >= static_cast<double>(NumericLimits<u64>::max()));
|
||||
|
||||
static_assert(HostIsLittleEndian, "Float parsing currently assumes little endian, this fact is only used in fast parsing of 8 digits at a time"
|
||||
"\nyou _should_ only need to change read eight_digits to make this big endian compatible.");
|
||||
constexpr u64 read_eight_digits(char const* string)
|
||||
{
|
||||
u64 val;
|
||||
|
@ -206,40 +204,57 @@ constexpr static u32 eight_digits_to_value(u64 value)
|
|||
{
|
||||
// THIS DOES ABSOLUTELY ASSUME has_eight_digits is true
|
||||
|
||||
// This trick is based on https://johnnylee-sde.github.io/Fast-numeric-string-to-int/
|
||||
// FIXME: fast_float uses a slightly different version, but that is far harder
|
||||
// to understand and does not seem to improve performance substantially.
|
||||
// See https://github.com/fastfloat/fast_float/pull/28
|
||||
if constexpr (AK::HostIsLittleEndian) {
|
||||
// This trick is based on https://johnnylee-sde.github.io/Fast-numeric-string-to-int/
|
||||
// FIXME: fast_float uses a slightly different version, but that is far harder
|
||||
// to understand and does not seem to improve performance substantially.
|
||||
// See https://github.com/fastfloat/fast_float/pull/28
|
||||
|
||||
// First convert the digits to their respectively numbers (0x30 -> 0x00 etc.)
|
||||
value -= 0x3030303030303030;
|
||||
// First convert the digits to their respectively numbers (0x30 -> 0x00 etc.)
|
||||
value -= 0x3030303030303030;
|
||||
|
||||
// Because of little endian the first number will in fact be the least significant
|
||||
// bits of value i.e. "12345678" -> 0x0807060504030201
|
||||
// This means that we need to shift/multiply each digit with 8 - the byte it is in
|
||||
// So the eight need to go down, and the 01 need to be multiplied with 10000000
|
||||
// Because of little endian the first number will in fact be the least significant
|
||||
// bits of value i.e. "12345678" -> 0x0807060504030201
|
||||
// This means that we need to shift/multiply each digit with 8 - the byte it is in
|
||||
// So the eight need to go down, and the 01 need to be multiplied with 10000000
|
||||
|
||||
// We effectively multiply by 10 and then shift those values to the right (2^8 = 256)
|
||||
// We then shift the values back down, this leads to 4 digits pairs in the 2 byte parts
|
||||
// The values between are "garbage" which we will ignore
|
||||
value = (value * (256 * 10 + 1)) >> 8;
|
||||
// So with our example this gives 0x$$4e$$38$$22$$0c, where $$ is garbage/ignored
|
||||
// In decimal this gives 78 56 34 12
|
||||
// We effectively multiply by 10 and then shift those values to the right (2^8 = 256)
|
||||
// We then shift the values back down, this leads to 4 digits pairs in the 2 byte parts
|
||||
// The values between are "garbage" which we will ignore
|
||||
value = (value * (256 * 10 + 1)) >> 8;
|
||||
// So with our example this gives 0x$$4e$$38$$22$$0c, where $$ is garbage/ignored
|
||||
// In decimal this gives 78 56 34 12
|
||||
|
||||
// Now we keep performing the same trick twice more
|
||||
// First * 100 and shift of 16 (2^16 = 65536) and then shift back
|
||||
value = ((value & 0x00FF00FF00FF00FF) * (65536 * 100 + 1)) >> 16;
|
||||
// Now we keep performing the same trick twice more
|
||||
// First * 100 and shift of 16 (2^16 = 65536) and then shift back
|
||||
value = ((value & 0x00FF00FF00FF00FF) * (65536 * 100 + 1)) >> 16;
|
||||
|
||||
// Again with our example this gives 0x$$$$162e$$$$04d2
|
||||
// 5678 1234
|
||||
// Again with our example this gives 0x$$$$162e$$$$04d2
|
||||
// 5678 1234
|
||||
|
||||
// And finally with * 10000 and shift of 32 (2^32 = 4294967296)
|
||||
value = ((value & 0x0000FFFF0000FFFF) * (4294967296 * 10000 + 1)) >> 32;
|
||||
// And finally with * 10000 and shift of 32 (2^32 = 4294967296)
|
||||
value = ((value & 0x0000FFFF0000FFFF) * (4294967296 * 10000 + 1)) >> 32;
|
||||
|
||||
// With the example this gives 0x$$$$$$$$00bc614e
|
||||
// 12345678
|
||||
// Now we just truncate to the lower part
|
||||
return u32(value);
|
||||
// With the example this gives 0x$$$$$$$$00bc614e
|
||||
// 12345678
|
||||
|
||||
// Now we just truncate to the lower part
|
||||
return u32(value);
|
||||
} else {
|
||||
value -= 0x3030303030303030;
|
||||
|
||||
value = (value & 0x0fL)
|
||||
+ ((value & (0x0fL << 8)) >> 8) * 10
|
||||
+ ((value & (0x0fL << 16)) >> 16) * 100
|
||||
+ ((value & (0x0fL << 24)) >> 24) * 1000
|
||||
+ ((value & (0x0fL << 32)) >> 32) * 10000
|
||||
+ ((value & (0x0fL << 40)) >> 40) * 100000
|
||||
+ ((value & (0x0fL << 48)) >> 48) * 1000000
|
||||
+ ((value & (0x0fL << 56)) >> 56) * 10000000;
|
||||
|
||||
// Now we just truncate to the lower part
|
||||
return u32(value);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename IsDoneCallback, typename Has8CharsLeftCallback>
|
||||
|
|
22
AK/Math.h
22
AK/Math.h
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <AK/BuiltinWrappers.h>
|
||||
#include <AK/Concepts.h>
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/FloatingPoint.h>
|
||||
#include <AK/NumericLimits.h>
|
||||
#include <AK/StdLibExtraDetails.h>
|
||||
|
@ -765,14 +766,21 @@ constexpr T log2(T x)
|
|||
|
||||
// FIXME: Handle denormalized numbers separately
|
||||
|
||||
FloatExtractor<T> mantissa_ext {
|
||||
.mantissa = ext.mantissa,
|
||||
.exponent = FloatExtractor<T>::exponent_bias,
|
||||
.sign = ext.sign
|
||||
};
|
||||
|
||||
// (1 <= mantissa < 2)
|
||||
T m = mantissa_ext.d;
|
||||
T m;
|
||||
if constexpr (HostIsLittleEndian) {
|
||||
m = ((FloatExtractor<T>) {
|
||||
.mantissa = ext.mantissa,
|
||||
.exponent = FloatExtractor<T>::exponent_bias,
|
||||
.sign = ext.sign })
|
||||
.d;
|
||||
} else {
|
||||
m = ((FloatExtractor<T>) {
|
||||
.sign = ext.sign,
|
||||
.exponent = FloatExtractor<T>::exponent_bias,
|
||||
.mantissa = ext.mantissa })
|
||||
.d;
|
||||
}
|
||||
|
||||
// This is a reconstruction of one of Sun's algorithms
|
||||
// They use a transformation to lower the problem space,
|
||||
|
|
Loading…
Reference in a new issue