Skip to content

Commit fa35e49

Browse files
committed
Fix rounding error for single search contexts.
1 parent c571949 commit fa35e49

File tree

1 file changed

+65
-1
lines changed

1 file changed

+65
-1
lines changed

Motely/MotelySingleSearchContext.cs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
using System.Diagnostics;
3+
using System.Numerics;
34
using System.Runtime.CompilerServices;
45
using System.Runtime.Intrinsics;
56

@@ -127,12 +128,75 @@ private double InternalPseudoHashSeed(int keyLength)
127128
return num;
128129
}
129130

131+
#if !DEBUG
132+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
133+
#endif
134+
private static double Fract(double x)
135+
{
136+
ref ulong xInt = ref Unsafe.As<double, ulong>(ref x);
137+
138+
const ulong DblExpo = 0x7FF0000000000000;
139+
const ulong DblMant = 0x000FFFFFFFFFFFFF;
140+
141+
const int DblMantSZ = 52;
142+
143+
const int DblExpoBias = 1023;
144+
145+
ulong expo = (xInt & DblExpo) >> DblMantSZ;
146+
147+
if (expo < DblExpoBias) return x;
148+
149+
// We don't have to worry about this edge case
150+
151+
// const int DblExpoSZ = 11;
152+
// if (expo == ((1 << DblExpoSZ) - 1)) return double.NaN;
153+
154+
ulong expoBiased = expo - DblExpoBias;
155+
156+
if (expoBiased > DblMantSZ) return 0;
157+
158+
ulong mant = xInt & DblMant;
159+
ulong fractMant = mant & ((1ul << (int)(DblMantSZ - expoBiased)) - 1);
160+
161+
if (fractMant == 0) return 0;
162+
163+
int fractLzcnt = BitOperations.LeadingZeroCount(fractMant) - (64 - DblMantSZ);
164+
ulong resExpo = (expo - (ulong)fractLzcnt - 1) << DblMantSZ;
165+
ulong resMant = (fractMant << (fractLzcnt + 1)) & DblMant;
166+
167+
ulong res = resExpo | resMant;
168+
169+
return Unsafe.As<ulong, double>(ref res);
170+
}
171+
172+
private static readonly double InvPrec = Math.Pow(10.0, 13);
173+
private static readonly double TwoInvPrec = Math.Pow(2.0, 13);
174+
private static readonly double FiveInvPrec = Math.Pow(5.0, 13);
175+
176+
#if !DEBUG
177+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
178+
#endif
179+
private static double Round13(double x)
180+
{
181+
double normalCase = Math.Round(x * InvPrec, MidpointRounding.AwayFromZero) / InvPrec;
182+
183+
if (normalCase == Math.Round(Math.BitDecrement(x) * InvPrec, MidpointRounding.AwayFromZero) / InvPrec)
184+
return normalCase;
185+
186+
double truncated = Fract(x * TwoInvPrec) * FiveInvPrec;
187+
188+
if (Fract(truncated) >= 0.5)
189+
return (Math.Floor(x * InvPrec) + 1) / InvPrec;
190+
191+
return Math.Floor(x * InvPrec) / InvPrec;
192+
}
193+
130194
#if !DEBUG
131195
[MethodImpl(MethodImplOptions.AggressiveInlining)]
132196
#endif
133197
private static double IteratePRNG(double state)
134198
{
135-
return Math.Round((state * 1.72431234 + 2.134453429141) % 1, 13);
199+
return Round13(Fract(state * 1.72431234 + 2.134453429141));
136200
}
137201

138202
#if !DEBUG

0 commit comments

Comments
 (0)