diff --git a/AK/Math.h b/AK/Math.h index 6719a4b2523..8636e990c05 100644 --- a/AK/Math.h +++ b/AK/Math.h @@ -81,6 +81,214 @@ constexpr size_t product_odd() { return value * product_odd(); } return res; \ } +namespace Rounding { +template +constexpr T ceil(T num) +{ + if (is_constant_evaluated()) { + if (num < NumericLimits::min() || num > NumericLimits::max()) + return num; + return (static_cast(static_cast(num)) == num) + ? static_cast(num) + : static_cast(num) + ((num > 0) ? 1 : 0); + } +#if ARCH(AARCH64) + AARCH64_INSTRUCTION(frintp, num); +#else + return __builtin_ceil(num); +#endif +} + +template +constexpr T floor(T num) +{ + if (is_constant_evaluated()) { + if (num < NumericLimits::min() || num > NumericLimits::max()) + return num; + return (static_cast(static_cast(num)) == num) + ? static_cast(num) + : static_cast(num) - ((num > 0) ? 0 : 1); + } +#if ARCH(AARCH64) + AARCH64_INSTRUCTION(frintm, num); +#else + return __builtin_floor(num); +#endif +} + +template +constexpr T round(T x) +{ + CONSTEXPR_STATE(round, x); + // Note: This is break-tie-away-from-zero, so not the hw's understanding of + // "nearest", which would be towards even. + if (x == 0.) + return x; + if (x > 0.) + return floor(x + .5); + return ceil(x - .5); +} + +template +ALWAYS_INLINE I round_to(P value); + +#if ARCH(X86_64) +template +ALWAYS_INLINE I round_to(long double value) +{ + // Note: fistps outputs into a signed integer location (i16, i32, i64), + // so lets be nice and tell the compiler that. + Conditional= sizeof(i16), MakeSigned, i16> ret; + if constexpr (sizeof(I) == sizeof(i64)) { + asm("fistpll %0" + : "=m"(ret) + : "t"(value) + : "st"); + } else if constexpr (sizeof(I) == sizeof(i32)) { + asm("fistpl %0" + : "=m"(ret) + : "t"(value) + : "st"); + } else { + asm("fistps %0" + : "=m"(ret) + : "t"(value) + : "st"); + } + return static_cast(ret); +} + +template +ALWAYS_INLINE I round_to(float value) +{ + // FIXME: round_to might will cause issues, aka the indefinite value being set, + // if the value surpasses the i64 limit, even if the result could fit into an u64 + // To solve this we would either need to detect that value or do a range check and + // then do a more specialized conversion, which might include a division (which is expensive) + if constexpr (sizeof(I) == sizeof(i64) || IsSame) { + i64 ret; + asm("cvtss2si %1, %0" + : "=r"(ret) + : "xm"(value)); + return static_cast(ret); + } + i32 ret; + asm("cvtss2si %1, %0" + : "=r"(ret) + : "xm"(value)); + return static_cast(ret); +} + +template +ALWAYS_INLINE I round_to(double value) +{ + // FIXME: round_to might will cause issues, aka the indefinite value being set, + // if the value surpasses the i64 limit, even if the result could fit into an u64 + // To solve this we would either need to detect that value or do a range check and + // then do a more specialized conversion, which might include a division (which is expensive) + if constexpr (sizeof(I) == sizeof(i64) || IsSame) { + i64 ret; + asm("cvtsd2si %1, %0" + : "=r"(ret) + : "xm"(value)); + return static_cast(ret); + } + i32 ret; + asm("cvtsd2si %1, %0" + : "=r"(ret) + : "xm"(value)); + return static_cast(ret); +} + +#elif ARCH(AARCH64) +template +ALWAYS_INLINE I round_to(float value) +{ + if constexpr (sizeof(I) <= sizeof(u32)) { + i32 res; + asm("fcvtns %w0, %s1" + : "=r"(res) + : "w"(value)); + return static_cast(res); + } + i64 res; + asm("fcvtns %0, %s1" + : "=r"(res) + : "w"(value)); + return static_cast(res); +} + +template +ALWAYS_INLINE I round_to(double value) +{ + if constexpr (sizeof(I) <= sizeof(u32)) { + i32 res; + asm("fcvtns %w0, %d1" + : "=r"(res) + : "w"(value)); + return static_cast(res); + } + i64 res; + asm("fcvtns %0, %d1" + : "=r"(res) + : "w"(value)); + return static_cast(res); +} + +template +ALWAYS_INLINE U round_to(float value) +{ + if constexpr (sizeof(U) <= sizeof(u32)) { + u32 res; + asm("fcvtnu %w0, %s1" + : "=r"(res) + : "w"(value)); + return static_cast(res); + } + i64 res; + asm("fcvtnu %0, %s1" + : "=r"(res) + : "w"(value)); + return static_cast(res); +} + +template +ALWAYS_INLINE U round_to(double value) +{ + if constexpr (sizeof(U) <= sizeof(u32)) { + u32 res; + asm("fcvtns %w0, %d1" + : "=r"(res) + : "w"(value)); + return static_cast(res); + } + i64 res; + asm("fcvtns %0, %d1" + : "=r"(res) + : "w"(value)); + return static_cast(res); +} + +#else +template +ALWAYS_INLINE I round_to(P value) +{ + if constexpr (IsSame) + return static_cast(__builtin_llrintl(value)); + if constexpr (IsSame) + return static_cast(__builtin_llrint(value)); + if constexpr (IsSame) + return static_cast(__builtin_llrintf(value)); +} +#endif + +} + +using Rounding::ceil; +using Rounding::floor; +using Rounding::round; +using Rounding::round_to; + namespace Division { template constexpr T fmod(T x, T y) @@ -680,159 +888,6 @@ using Hyperbolic::cosh; using Hyperbolic::sinh; using Hyperbolic::tanh; -template -ALWAYS_INLINE I round_to(P value); - -#if ARCH(X86_64) -template -ALWAYS_INLINE I round_to(long double value) -{ - // Note: fistps outputs into a signed integer location (i16, i32, i64), - // so lets be nice and tell the compiler that. - Conditional= sizeof(i16), MakeSigned, i16> ret; - if constexpr (sizeof(I) == sizeof(i64)) { - asm("fistpll %0" - : "=m"(ret) - : "t"(value) - : "st"); - } else if constexpr (sizeof(I) == sizeof(i32)) { - asm("fistpl %0" - : "=m"(ret) - : "t"(value) - : "st"); - } else { - asm("fistps %0" - : "=m"(ret) - : "t"(value) - : "st"); - } - return static_cast(ret); -} - -template -ALWAYS_INLINE I round_to(float value) -{ - // FIXME: round_to might will cause issues, aka the indefinite value being set, - // if the value surpasses the i64 limit, even if the result could fit into an u64 - // To solve this we would either need to detect that value or do a range check and - // then do a more specialized conversion, which might include a division (which is expensive) - if constexpr (sizeof(I) == sizeof(i64) || IsSame) { - i64 ret; - asm("cvtss2si %1, %0" - : "=r"(ret) - : "xm"(value)); - return static_cast(ret); - } - i32 ret; - asm("cvtss2si %1, %0" - : "=r"(ret) - : "xm"(value)); - return static_cast(ret); -} - -template -ALWAYS_INLINE I round_to(double value) -{ - // FIXME: round_to might will cause issues, aka the indefinite value being set, - // if the value surpasses the i64 limit, even if the result could fit into an u64 - // To solve this we would either need to detect that value or do a range check and - // then do a more specialized conversion, which might include a division (which is expensive) - if constexpr (sizeof(I) == sizeof(i64) || IsSame) { - i64 ret; - asm("cvtsd2si %1, %0" - : "=r"(ret) - : "xm"(value)); - return static_cast(ret); - } - i32 ret; - asm("cvtsd2si %1, %0" - : "=r"(ret) - : "xm"(value)); - return static_cast(ret); -} - -#elif ARCH(AARCH64) -template -ALWAYS_INLINE I round_to(float value) -{ - if constexpr (sizeof(I) <= sizeof(u32)) { - i32 res; - asm("fcvtns %w0, %s1" - : "=r"(res) - : "w"(value)); - return static_cast(res); - } - i64 res; - asm("fcvtns %0, %s1" - : "=r"(res) - : "w"(value)); - return static_cast(res); -} - -template -ALWAYS_INLINE I round_to(double value) -{ - if constexpr (sizeof(I) <= sizeof(u32)) { - i32 res; - asm("fcvtns %w0, %d1" - : "=r"(res) - : "w"(value)); - return static_cast(res); - } - i64 res; - asm("fcvtns %0, %d1" - : "=r"(res) - : "w"(value)); - return static_cast(res); -} - -template -ALWAYS_INLINE U round_to(float value) -{ - if constexpr (sizeof(U) <= sizeof(u32)) { - u32 res; - asm("fcvtnu %w0, %s1" - : "=r"(res) - : "w"(value)); - return static_cast(res); - } - i64 res; - asm("fcvtnu %0, %s1" - : "=r"(res) - : "w"(value)); - return static_cast(res); -} - -template -ALWAYS_INLINE U round_to(double value) -{ - if constexpr (sizeof(U) <= sizeof(u32)) { - u32 res; - asm("fcvtns %w0, %d1" - : "=r"(res) - : "w"(value)); - return static_cast(res); - } - i64 res; - asm("fcvtns %0, %d1" - : "=r"(res) - : "w"(value)); - return static_cast(res); -} - -#else -template -ALWAYS_INLINE I round_to(P value) -{ - if constexpr (IsSame) - return static_cast(__builtin_llrintl(value)); - if constexpr (IsSame) - return static_cast(__builtin_llrint(value)); - if constexpr (IsSame) - return static_cast(__builtin_llrintf(value)); -} -#endif - template constexpr T pow(T x, T y) { @@ -859,53 +914,6 @@ constexpr T pow(T x, T y) return exp2(y * log2(x)); } -template -constexpr T ceil(T num) -{ - if (is_constant_evaluated()) { - if (num < NumericLimits::min() || num > NumericLimits::max()) - return num; - return (static_cast(static_cast(num)) == num) - ? static_cast(num) - : static_cast(num) + ((num > 0) ? 1 : 0); - } -#if ARCH(AARCH64) - AARCH64_INSTRUCTION(frintp, num); -#else - return __builtin_ceil(num); -#endif -} - -template -constexpr T floor(T num) -{ - if (is_constant_evaluated()) { - if (num < NumericLimits::min() || num > NumericLimits::max()) - return num; - return (static_cast(static_cast(num)) == num) - ? static_cast(num) - : static_cast(num) - ((num > 0) ? 0 : 1); - } -#if ARCH(AARCH64) - AARCH64_INSTRUCTION(frintm, num); -#else - return __builtin_floor(num); -#endif -} - -template -constexpr T round(T x) -{ - CONSTEXPR_STATE(round, x); - // Note: This is break-tie-away-from-zero, so not the hw's understanding of - // "nearest", which would be towards even. - if (x == 0.) - return x; - if (x > 0.) - return floor(x + .5); - return ceil(x - .5); -} - template constexpr int clamp_to_int(T value) {