Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Commit

Permalink
Fixed and optimized Decimal.GetHashCode (#18288)
Browse files Browse the repository at this point in the history
  • Loading branch information
pentp authored and jkotas committed Jun 8, 2018
1 parent 58998ff commit 1b2040c
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 31 deletions.
69 changes: 69 additions & 0 deletions src/System.Private.CoreLib/src/System/Decimal.DecCalc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,74 @@ uint D32DivMod1E9(uint hi32, ref uint lo32)
return (uint)(n % 1000000000);
}
}

private static int GetHashCode(ref decimal d)
{
if ((d.Low | d.Mid | d.High) == 0)
return 0;

uint flags = (uint)d.flags;
if ((flags & ScaleMask) == 0 || (d.Low & 1) != 0)
return (int)(flags ^ d.High ^ d.Mid ^ d.Low);

int scale = (byte)(flags >> ScaleShift);
uint low = d.Low;
ulong high64 = ((ulong)d.High << 32) | d.Mid;

Unscale(ref low, ref high64, ref scale);

flags = ((flags) & ~(uint)ScaleMask) | (uint)scale << ScaleShift;
return (int)(flags ^ (uint)(high64 >> 32) ^ (uint)high64 ^ low);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool Div96ByConst(ref ulong high64, ref uint low, uint pow)
{
ulong div64;
#if !BIT64
if (high64 <= uint.MaxValue)
{
div64 = ((high64 << 32) | low) / pow;
if (low == (uint)div64 * pow)
{
low = (uint)div64;
high64 = div64 >> 32;
return true;
}
return false;
}
#endif
div64 = high64 / pow;
uint div = (uint)((((high64 - (uint)div64 * pow) << 32) | low) / pow);
if (low == div * pow)
{
high64 = div64;
low = div;
return true;
}
return false;
}

// Normalize (unscale) the number by trying to divide out 10^8, 10^4, 10^2, and 10^1.
// If a division by one of these powers returns a zero remainder, then we keep the quotient.
//
// Since 10 = 2 * 5, there must be a factor of 2 for every power of 10 we can extract.
// We use this as a quick test on whether to try a given power.
//
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Unscale(ref uint low, ref ulong high64, ref int scale)
{
while ((byte)low == 0 && scale >= 8 && Div96ByConst(ref high64, ref low, 100000000))
scale -= 8;

if ((low & 0xF) == 0 && scale >= 4 && Div96ByConst(ref high64, ref low, 10000))
scale -= 4;

if ((low & 3) == 0 && scale >= 2 && Div96ByConst(ref high64, ref low, 100))
scale -= 2;

if ((low & 1) == 0 && scale >= 1 && Div96ByConst(ref high64, ref low, 10))
scale--;
}
}
}
3 changes: 1 addition & 2 deletions src/System.Private.CoreLib/src/System/Decimal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,7 @@ public bool Equals(Decimal value)

// Returns the hash code for this Decimal.
//
[MethodImplAttribute(MethodImplOptions.InternalCall)]
public extern override int GetHashCode();
public override int GetHashCode() => GetHashCode(ref this);

// Compares two Decimal values for equality. Returns true if the two
// Decimal values are equal, or false if they are not equal.
Expand Down
27 changes: 0 additions & 27 deletions src/classlibnative/bcltype/decimal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,33 +99,6 @@ FCIMPL1(void, COMDecimal::DoFloor, DECIMAL * d)
}
FCIMPLEND

FCIMPL1(INT32, COMDecimal::GetHashCode, DECIMAL *d)
{
FCALL_CONTRACT;

ENSURE_OLEAUT32_LOADED();

_ASSERTE(d != NULL);
double dbl;
VarR8FromDec(d, &dbl);
if (dbl == 0.0) {
// Ensure 0 and -0 have the same hash code
return 0;
}
// conversion to double is lossy and produces rounding errors so we mask off the lowest 4 bits
//
// For example these two numerically equal decimals with different internal representations produce
// slightly different results when converted to double:
//
// decimal a = new decimal(new int[] { 0x76969696, 0x2fdd49fa, 0x409783ff, 0x00160000 });
// => (decimal)1999021.176470588235294117647000000000 => (double)1999021.176470588
// decimal b = new decimal(new int[] { 0x3f0f0f0f, 0x1e62edcc, 0x06758d33, 0x00150000 });
// => (decimal)1999021.176470588235294117647000000000 => (double)1999021.1764705882
//
return ((((int *)&dbl)[0]) & 0xFFFFFFF0) ^ ((int *)&dbl)[1];
}
FCIMPLEND

FCIMPL3(void, COMDecimal::DoMultiply, DECIMAL * d1, DECIMAL * d2, CLR_BOOL * overflowed)
{
FCALL_CONTRACT;
Expand Down
1 change: 0 additions & 1 deletion src/classlibnative/bcltype/decimal.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ class COMDecimal {
static FCDECL2_IV(void, InitSingle, DECIMAL *_this, float value);
static FCDECL2_IV(void, InitDouble, DECIMAL *_this, double value);
static FCDECL2(INT32, DoCompare, DECIMAL * d1, DECIMAL * d2);
static FCDECL1(INT32, GetHashCode, DECIMAL *d);

static FCDECL3(void, DoAddSubThrow, DECIMAL * d1, DECIMAL * d2, UINT8 bSign);
static FCDECL2(void, DoDivideThrow, DECIMAL * d1, DECIMAL * d2);
Expand Down
1 change: 0 additions & 1 deletion src/vm/ecalllist.h
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,6 @@ FCFuncStart(gDecimalFuncs)
FCFuncElement("FCallDivide", COMDecimal::DoDivideThrow)
FCFuncElement("FCallCompare", COMDecimal::DoCompare)
FCFuncElement("FCallFloor", COMDecimal::DoFloor)
FCFuncElement("GetHashCode", COMDecimal::GetHashCode)
FCFuncElement("FCallRound", COMDecimal::DoRound)
FCFuncElement("FCallToCurrency", COMDecimal::DoToCurrency)
FCFuncElement("FCallToInt32", COMDecimal::ToInt32)
Expand Down

0 comments on commit 1b2040c

Please sign in to comment.