`double.IsEvenInteger` / `double.IsOddInteger` ~20× slower than `double.IsInteger`
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
### Description
`double.IsEvenInteger(x)` and `double.IsOddInteger(x)` exhibit a severe performance penalty compared to `double.IsInteger(x)`.
Benchmarking shows both even/odd checks are **~20× slower**, despite being derivable from the same integer‑classification logic.
### Configuration
arch: x64
### Regression?
Unknown
### Analysis
I suggest using an implementation similar to the code below, except for `Single.IsInteger` (since its current implementation is already optimized).
```C#
private enum IntegerKind {
NotInteger = 0,
Odd = 1,
Even = 2,
}
internal static bool IsInteger(TFloat value)
where TFloat : unmanaged, IBinaryFloatingPointIeee754
where TUIntBits : unmanaged, IBinaryInteger, IUnsignedNumber {
return GetIntegerKind(value) != IntegerKind.NotInteger;
}
internal static bool IsEvenInteger(TFloat value)
where TFloat : unmanaged, IBinaryFloatingPointIeee754
where TUIntBits : unmanaged, IBinaryInteger, IUnsignedNumber {
return GetIntegerKind(value) == IntegerKind.Even;
}
internal static bool IsOddInteger(TFloat value)
where TFloat : unmanaged, IBinaryFloatingPointIeee754
where TUIntBits : unmanaged, IBinaryInteger, IUnsignedNumber {
return GetIntegerKind(value) == IntegerKind.Odd;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static IntegerKind GetIntegerKind(TFloat value)
where TFloat : unmanaged, IBinaryFloatingPointIeee754
where TUIntBits : unmanaged, IBinaryInteger, IUnsignedNumber {
TUIntBits bits = Unsafe.BitCast(value);
int rawExponent = GetRawExponent(bits);
int maxRawExponent =
BinaryFloatingPointIeee754TypeTraitsInternal.MaxRawExponent;
int exponentBias =
BinaryFloatingPointIeee754TypeTraitsInternal.MaxExponent;
int fractionBits =
BinaryFloatingPointIeee754TypeTraitsInternal
.TrailingSignificandFieldBitWidth;
// NaN / infinity
if (rawExponent == maxRawExponent) {
return IntegerKind.NotInteger;
}
TUIntBits mantissa =
bits & ((TUIntBits.One << fractionBits) - TUIntBits.One);
// Zero
if (rawExponent == 0) {
return mantissa == TUIntBits.Zero
? IntegerKind.Even
: IntegerKind.NotInteger;
}
int e = rawExponent - exponentBias;
// |x| < 1
if (e < 0) {
return IntegerKind.NotInteger;
}
// Spacing >= 2. Every representable integer is even.
if (e > fractionBits) {
return IntegerKind.Even;
}
int fractionalBits = fractionBits - e;
// Has fractional part.
if (!LowBitsAreZero(mantissa, fractionalBits)) {
return IntegerKind.NotInteger;
}
// Units bit is the hidden leading 1.
if (fractionalBits == fractionBits) {
return IntegerKind.Odd;
}
return ((mantissa >> fractionalBits) & TUIntBits.One) != TUIntBits.Zero
? IntegerKind.Odd
: IntegerKind.Even;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetRawExponent(TUIntBits bits)
where TFloat : unmanaged, IBinaryFloatingPointIeee754
where TUIntBits : unmanaged, IBinaryInteger, IUnsignedNumber {
int shift =
BinaryFloatingPointIeee754TypeTraitsInternal
.TrailingSignificandFieldBitWidth;
int exponentBits =
BinaryFloatingPointIeee754TypeTraitsInternal
.ExponentFieldBitWidth;
return int.CreateTruncating(bits >> shift)
& ((1 << exponentBits) - 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool LowBitsAreZero(
TUIntBits value,
int count)
where TUIntBits : unmanaged, IBinaryInteger, IUnsignedNumber {
if (count == 0) {
return true;
}
if (count >= Unsafe.SizeOf() * 8) {
return value == TUIntBits.Zero;
}
return (value & ((TUIntBits.One << count) - TUIntBits.One))
== TUIntBits.Zero;
}
```
Contributor guide
Research direction
Locate the implementations of double.IsInteger, double.IsEvenInteger, and double.IsOddInteger in the .NET runtime, then compare them with the proposed shared integer-classification approach. Reproduce the reported benchmark on x64 and verify that even, odd, non-integer, zero, NaN, and infinity cases retain their expected behavior while the performance gap is reduced.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 56/100