Am 25.08.2023 um 08:25 schrieb Megumin:
Here's some fmod()-code I implemented myself for float and double.
It always produces results without precision loss because the result
is always smaller than the dividend.
template<floating_point Float>
static Float myFmodT( Float counter, Float denom )
{
constexpr bool IS_DOUBLE = same_as<Float, double>;
static_assert(IS_DOUBLE || same_as<Float, float>);
constexpr unsigned
BITS = IS_DOUBLE ? 64 : 32,
MANT_BITS = IS_DOUBLE ? 52 : 23,
EXP_BITS = IS_DOUBLE ? 11 : 8;
static_assert(sizeof(Float) * 8 == BITS &&
numeric_limits<Float>::is_iec559);
using bin_t = conditional_t<IS_DOUBLE, uint64_t, uint32_t>;
constexpr int MAX_EXP = (1 << EXP_BITS) - 1;
constexpr bin_t
SIGN_BIT = (bin_t)1 << BITS - 1,
EXP_MASK = (bin_t)MAX_EXP << MANT_BITS,
IMPLCIT_BIT = (bin_t)1 << MANT_BITS,
MANT_MASK = IMPLCIT_BIT - 1;
static auto bin = []( Float f ) -> bin_t { return bit_cast<bin_t>( f ); };
static auto flt = []( bin_t b ) -> Float { return bit_cast<Float>( b ); };
bin_t
bCounter = bin( counter ),
bDenom = bin( denom ) & ~SIGN_BIT,
bSign = bCounter & SIGN_BIT;
bCounter &= ~SIGN_BIT;
static auto maxExp = []( bin_t b ) -> bool { return b >= EXP_MASK; };
static auto infMant = []( bin_t b ) -> bool { return !(b & MANT_MASK); };
auto floatingResult = [&]() -> Float { return (counter * denom) /
(counter * denom); };
if( !bDenom ) [[unlikely]]
// ... % +/-0.0
return floatingResult();
if( maxExp( bCounter ) ) [[unlikely]]
// [Inf|NaN|SNaN] % non-zero
return floatingResult();
if( maxExp( bDenom ) ) [[unlikely]]
if( infMant( bDenom ) ) [[likely]]
// finite % Inf
return counter;
else
// finite % [Inf|NaN|SNaN]
return floatingResult();
if( !bCounter ) [[unlikely]]
// +/-0.0 % non-zero-finite = +/-0.0
return counter;
int
counterExp = bCounter >> MANT_BITS & MAX_EXP,
denomExp = bDenom >> MANT_BITS & MAX_EXP;
bin_t
counterMant = (bin_t)(bool)counterExp << MANT_BITS | bCounter & MANT_MASK,
denomMant = (bin_t)(bool)denomExp << MANT_BITS | bDenom & MANT_MASK;
static auto normalize = []( bin_t &mant, int &exp )
{
unsigned bits = countl_zero( mant ) - EXP_BITS;
mant <<= bits;
exp -= bits;
};
if( !counterExp ) [[unlikely]]
// normalize counter
normalize( counterMant, counterExp ),
++counterExp;
if( !denomExp ) [[unlikely]]
// normalize denominator
normalize( denomMant, denomExp ),
++denomExp;
int remExp = counterExp;
bin_t remMant = counterMant;
for( ; ; )
{
int below = remMant < denomMant;
if( remExp - below < denomExp ) [[unlikely]]
break;
remExp -= below;
remMant <<= below;
if( !(remMant -= denomMant) ) [[unlikely]]
return flt( bSign );
normalize( remMant, remExp );
}
if( remExp <= 0 ) [[unlikely]]
// denormal result
remMant >>= -remExp + 1,
remExp = 0;
return flt( bSign | (bin_t)remExp << MANT_BITS | remMant & MANT_MASK );
}