From 1bc44376c0b276e7094cc3149674a3a6b9d721cc Mon Sep 17 00:00:00 2001 From: Dennis Camera Date: Thu, 4 Jul 2024 11:09:20 +0200 Subject: [PATCH] AK: Implement floating-point conversions for big-endian --- AK/FloatingPoint.h | 24 +++++++++ AK/FloatingPointStringConversions.cpp | 73 ++++++++++++++++----------- AK/Math.h | 22 +++++--- 3 files changed, 83 insertions(+), 36 deletions(-) diff --git a/AK/FloatingPoint.h b/AK/FloatingPoint.h index 3aa1a01e516..86bb813c97e 100644 --- a/AK/FloatingPoint.h +++ b/AK/FloatingPoint.h @@ -25,9 +25,15 @@ union FloatExtractor { 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 { // 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 { // very intuitive and portable behaviour on windows, but it doesn't // work with the msvc ABI. // See +#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 { 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; }; diff --git a/AK/FloatingPointStringConversions.cpp b/AK/FloatingPointStringConversions.cpp index 9298d622410..9fea3f9cda7 100644 --- a/AK/FloatingPointStringConversions.cpp +++ b/AK/FloatingPointStringConversions.cpp @@ -176,8 +176,6 @@ static constexpr auto max_representable_power_of_ten_in_u64 = 19; static_assert(1e19 <= static_cast(NumericLimits::max())); static_assert(1e20 >= static_cast(NumericLimits::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 diff --git a/AK/Math.h b/AK/Math.h index 1a354a7488e..fe6c0c3d35a 100644 --- a/AK/Math.h +++ b/AK/Math.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -765,14 +766,21 @@ constexpr T log2(T x) // FIXME: Handle denormalized numbers separately - FloatExtractor mantissa_ext { - .mantissa = ext.mantissa, - .exponent = FloatExtractor::exponent_bias, - .sign = ext.sign - }; - // (1 <= mantissa < 2) - T m = mantissa_ext.d; + T m; + if constexpr (HostIsLittleEndian) { + m = ((FloatExtractor) { + .mantissa = ext.mantissa, + .exponent = FloatExtractor::exponent_bias, + .sign = ext.sign }) + .d; + } else { + m = ((FloatExtractor) { + .sign = ext.sign, + .exponent = FloatExtractor::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,