dotnet / dotnet/runtime

`double.IsEvenInteger` / `double.IsOddInteger` ~20× slower than `double.IsInteger`

Open
#133,916 1 comment 0 reactions 0 assignees View on GitHub
area-System.Numerics tenet-performance untriaged
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.