From 23508bedd90213c1e219251b54b758262ee8b346 Mon Sep 17 00:00:00 2001 From: Jordan Peck Date: Wed, 14 Oct 2026 23:08:06 +0100 Subject: [PATCH 1/3] Exp, Log, Pow: drop redundant ops (bit-identical results) - Exp: Floor( fx ) is never above fx, so the MaskedDecrement fix-up after it was a no-op (left over from a truncation-based floor). - Log: compute the exponent in the integer domain (i - 126, int masked decrement) and convert once, instead of convert, +1 and a float masked decrement. The values are small integers, so the result is identical. - Pow: Log's own a > 0 mask is redundant under Pow's mask, use an unmasked log internally. --- include/FastSIMD/ToolSet/Generic/Functions.h | 43 +++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/include/FastSIMD/ToolSet/Generic/Functions.h b/include/FastSIMD/ToolSet/Generic/Functions.h index ed5ca34..3d581a5 100644 --- a/include/FastSIMD/ToolSet/Generic/Functions.h +++ b/include/FastSIMD/ToolSet/Generic/Functions.h @@ -550,8 +550,7 @@ namespace FS RegisterF fx = value * RegisterF( 1.44269504088896341f ); fx += RegisterF( 0.5f ); - RegisterF flr = Floor( fx ); - fx = MaskedDecrement( flr > fx, flr ); + fx = Floor( fx ); value -= fx * RegisterF( 0.693359375f ); value -= fx * RegisterF( -2.12194440e-4f ); @@ -585,15 +584,14 @@ namespace FS } } - template - FS_FORCEINLINE Register Log( const Register& a ) + namespace impl { - if constexpr( IsNativeV> ) + // Natural log of a native register, without zeroing the a <= 0 lanes (they hold finite garbage) + template + FS_FORCEINLINE Register LogUnmasked( const Register& a ) { using RegisterF = Register; using RegisterI = Register; - - auto validMask = a > RegisterF( 0 ); RegisterF value = Max( a, Cast( RegisterI( 0x00800000 ) ) ); /* cut off denormalized stuff */ @@ -602,16 +600,14 @@ namespace FS /* keep only the fractional part */ value &= Cast( RegisterI( ~0x7f800000 ) ); value |= RegisterF( 0.5f ); - - i -= RegisterI( 0x7f ); - RegisterF e = Convert( i ); - - e += RegisterF( 1 ); auto mask = value < RegisterF( 0.707106781186547524f ); value = MaskedAdd( mask, value, value ); value -= RegisterF( 1 ); - e = MaskedDecrement( mask, e ); + + /* unbiased exponent + 1, minus 1 where the mantissa was doubled (small integers, exact in int or float) */ + i = MaskedDecrement( mask, i - RegisterI( 0x7e ) ); + RegisterF e = Convert( i ); RegisterF y = RegisterF( 7.0376836292E-2f ); y *= value; @@ -640,7 +636,16 @@ namespace FS value += y; value += e * RegisterF( 0.693359375f ); - return Masked( validMask, value ); + return value; + } + } + + template + FS_FORCEINLINE Register Log( const Register& a ) + { + if constexpr( IsNativeV> ) + { + return Masked( a > Register( 0 ), impl::LogUnmasked( a ) ); } else { @@ -654,7 +659,15 @@ namespace FS { if constexpr( IsNativeV> ) { - return Masked( value > Register( 0 ), Exp( pow * Log( value ) ) ); + if constexpr( std::is_same_v ) + { + // The outer mask already zeroes value <= 0, so skip Log's own mask + return Masked( value > Register( 0 ), Exp( pow * impl::LogUnmasked( value ) ) ); + } + else + { + return Masked( value > Register( 0 ), Exp( pow * Log( value ) ) ); + } } else { From 4d1b8d2730b754ede620202e73e53c6969efdb38 Mon Sep 17 00:00:00 2001 From: Jordan Peck Date: Wed, 14 Oct 2026 23:17:54 +0100 Subject: [PATCH 2/3] Log, Exp: cheaper exponent/mantissa handling (bit-identical results) - Log: split x into 2^e * m with m in [sqrt(0.5), sqrt(2)) by subtracting the bits of sqrt(0.5) (arithmetic shift for e, low bits + sqrt(0.5)'s bits for m). This gives exactly the e and m of the [0.5, 1) mantissa doubled below sqrt(0.5), without the compare, masked add and masked decrement. - Exp: fx is an integer in [-127, 128], so fx + 2^23 + 127 holds fx + 127 in its low mantissa bits; shift that instead of converting and adding 127. --- include/FastSIMD/ToolSet/Generic/Functions.h | 22 +++++++------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/include/FastSIMD/ToolSet/Generic/Functions.h b/include/FastSIMD/ToolSet/Generic/Functions.h index 3d581a5..049a230 100644 --- a/include/FastSIMD/ToolSet/Generic/Functions.h +++ b/include/FastSIMD/ToolSet/Generic/Functions.h @@ -569,10 +569,9 @@ namespace FS y *= value * value; y += value + RegisterF( 1 ); - /* build 2^n */ - RegisterI i = Convert( fx ); + /* build 2^n: fx is an integer in [-127, 128], adding 2^23 + 127 puts fx + 127 in the low mantissa bits */ + RegisterI i = Cast( fx + RegisterF( 8388608.0f + 127.0f ) ); - i += RegisterI( 0x7f ); i <<= 23; RegisterF pow2n = Cast( i ); @@ -594,21 +593,16 @@ namespace FS using RegisterI = Register; RegisterF value = Max( a, Cast( RegisterI( 0x00800000 ) ) ); /* cut off denormalized stuff */ - - RegisterI i = BitShiftRightZeroExtend( Cast( value ), 23 ); - /* keep only the fractional part */ - value &= Cast( RegisterI( ~0x7f800000 ) ); - value |= RegisterF( 0.5f ); + /* split value into 2^e * m with m in [sqrt(0.5), sqrt(2)): subtracting the bits of sqrt(0.5) borrows from + the exponent when the mantissa is below sqrt(2), then the low bits plus sqrt(0.5)'s give m (same e and m as + taking the [0.5, 1) mantissa and doubling it below sqrt(0.5)) */ + RegisterI bits = Cast( value ) - RegisterI( 0x3f3504f3 ); + RegisterF e = Convert( bits >> 23 ); - auto mask = value < RegisterF( 0.707106781186547524f ); - value = MaskedAdd( mask, value, value ); + value = Cast( ( bits & RegisterI( 0x007fffff ) ) + RegisterI( 0x3f3504f3 ) ); value -= RegisterF( 1 ); - /* unbiased exponent + 1, minus 1 where the mantissa was doubled (small integers, exact in int or float) */ - i = MaskedDecrement( mask, i - RegisterI( 0x7e ) ); - RegisterF e = Convert( i ); - RegisterF y = RegisterF( 7.0376836292E-2f ); y *= value; y += RegisterF( -1.1514610310E-1f ); From 169e5cf28fbc0b6142cbee720032c3ae256c5c46 Mon Sep 17 00:00:00 2001 From: Jordan Peck Date: Wed, 14 Oct 2026 23:20:08 +0100 Subject: [PATCH 3/3] Exp: cheaper floor on SSE2 (bit-identical results) Without SSE4.1's round instruction FS::Floor needs an int round trip plus overflow handling and a fix-up. The clamped exponent here is within [-127, 128], so round through int and fix up without the overflow select. --- include/FastSIMD/ToolSet/Generic/Functions.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/include/FastSIMD/ToolSet/Generic/Functions.h b/include/FastSIMD/ToolSet/Generic/Functions.h index 049a230..919dce1 100644 --- a/include/FastSIMD/ToolSet/Generic/Functions.h +++ b/include/FastSIMD/ToolSet/Generic/Functions.h @@ -550,7 +550,16 @@ namespace FS RegisterF fx = value * RegisterF( 1.44269504088896341f ); fx += RegisterF( 0.5f ); - fx = Floor( fx ); + if constexpr( ( SIMD & FastSIMD::FeatureFlag::SSE2 ) && !( SIMD & FastSIMD::FeatureFlag::SSE41 ) ) + { + /* no round instruction: fx is in [-127, 128], so floor via int round trip without SSE2 Floor's overflow handling */ + RegisterF rounded = Convert( Convert( fx ) ); + fx = MaskedDecrement( rounded > fx, rounded ); + } + else + { + fx = Floor( fx ); + } value -= fx * RegisterF( 0.693359375f ); value -= fx * RegisterF( -2.12194440e-4f );