diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs index 959f7fae749d4..18374c12c775f 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Runtime.InteropServices; namespace System.Numerics { @@ -264,7 +263,7 @@ public BigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigE bool isNegative; if (byteCount > 0) { - byte mostSignificantByte = isBigEndian ? value[0] : value[^1]; + byte mostSignificantByte = isBigEndian ? value[0] : value[byteCount - 1]; isNegative = (mostSignificantByte & 0x80) != 0 && !isUnsigned; if (mostSignificantByte == 0) @@ -640,12 +639,12 @@ public bool IsPowerOfTwo AssertValid(); if (_bits == null) - return BitOperations.IsPow2(_sign); + return (_sign & (_sign - 1)) == 0 && _sign != 0; if (_sign != 1) return false; int iu = _bits.Length - 1; - if (!BitOperations.IsPow2(_bits[iu])) + if ((_bits[iu] & (_bits[iu] - 1)) != 0) return false; while (--iu >= 0) { @@ -819,12 +818,12 @@ public static double Log(BigInteger value, double baseValue) if (value._bits == null) return Math.Log(value._sign, baseValue); - ulong h = value._bits[^1]; - ulong m = value._bits.Length > 1 ? value._bits[^2] : 0; - ulong l = value._bits.Length > 2 ? value._bits[^3] : 0; + ulong h = value._bits[value._bits.Length - 1]; + ulong m = value._bits.Length > 1 ? value._bits[value._bits.Length - 2] : 0; + ulong l = value._bits.Length > 2 ? value._bits[value._bits.Length - 3] : 0; // Measure the exact bit count - int c = BitOperations.LeadingZeroCount((uint)h); + int c = NumericsHelpers.CbitHighZero((uint)h); long b = (long)value._bits.Length * 32 - c; // Extract most significant bits @@ -990,17 +989,19 @@ public override int GetHashCode() if (_bits == null) return _sign; - - HashCode hash = default; - hash.Add(_sign); - hash.AddBytes(MemoryMarshal.AsBytes(_bits.AsSpan())); - return hash.ToHashCode(); + int hash = _sign; + for (int iv = _bits.Length; --iv >= 0;) + hash = NumericsHelpers.CombineHash(hash, unchecked((int)_bits[iv])); + return hash; } public override bool Equals([NotNullWhen(true)] object? obj) { AssertValid(); - return obj is BigInteger other && Equals(other); + + if (!(obj is BigInteger)) + return false; + return Equals((BigInteger)obj); } public bool Equals(long other) @@ -1123,9 +1124,9 @@ public int CompareTo(object? obj) { if (obj == null) return 1; - if (obj is not BigInteger bigInt) + if (!(obj is BigInteger)) throw new ArgumentException(SR.Argument_MustBeBigInt, nameof(obj)); - return CompareTo(bigInt); + return CompareTo((BigInteger)obj); } /// @@ -1293,13 +1294,13 @@ private enum GetBytesMode { AllocateArray, Count, Span } // because a bits array of all zeros would represent 0, and this case // would be encoded as _bits = null and _sign = 0. Debug.Assert(bits.Length > 0); - Debug.Assert(bits[^1] != 0); + Debug.Assert(bits[bits.Length - 1] != 0); while (bits[nonZeroDwordIndex] == 0U) { nonZeroDwordIndex++; } - highDword = ~bits[^1]; + highDword = ~bits[bits.Length - 1]; if (bits.Length - 1 == nonZeroDwordIndex) { // This will not overflow because highDword is less than or equal to uint.MaxValue - 1. @@ -1311,7 +1312,7 @@ private enum GetBytesMode { AllocateArray, Count, Span } { Debug.Assert(sign == 1); highByte = 0x00; - highDword = bits[^1]; + highDword = bits[bits.Length - 1]; } byte msb; @@ -1469,18 +1470,15 @@ private ReadOnlySpan ToUInt32Span(Span scratch) } // Find highest significant byte and ensure high bit is 0 if positive, 1 if negative - int msb = dwords.Length - 1; - while (msb > 0 && dwords[msb] == highDWord) - { - msb--; - } + int msb; + for (msb = dwords.Length - 1; msb > 0 && dwords[msb] == highDWord; msb--); bool needExtraByte = (dwords[msb] & 0x80000000) != (highDWord & 0x80000000); int length = msb + 1 + (needExtraByte ? 1 : 0); bool copyDwordsToScratch = true; if (length <= scratch.Length) { - scratch = scratch[..length]; + scratch = scratch.Slice(0, length); copyDwordsToScratch = !dwordsIsScratch; } else @@ -1490,7 +1488,7 @@ private ReadOnlySpan ToUInt32Span(Span scratch) if (copyDwordsToScratch) { - dwords[..(msb + 1)].CopyTo(scratch); + dwords.Slice(0, msb + 1).CopyTo(scratch); } if (needExtraByte) @@ -1820,11 +1818,11 @@ public static explicit operator double(BigInteger value) return double.NegativeInfinity; } - ulong h = bits[^1]; - ulong m = length > 1 ? bits[^2] : 0; - ulong l = length > 2 ? bits[^3] : 0; + ulong h = bits[length - 1]; + ulong m = length > 1 ? bits[length - 2] : 0; + ulong l = length > 2 ? bits[length - 3] : 0; - int z = BitOperations.LeadingZeroCount((uint)h); + int z = NumericsHelpers.CbitHighZero((uint)h); int exp = (length - 2) * 32 - z; ulong man = (h << 32 + z) | (m << z) | (l >> 32 - z); @@ -2418,7 +2416,7 @@ public long GetBitLength() else { bitsArrayLength = bits.Length; - highValue = bits[^1]; + highValue = bits[bitsArrayLength - 1]; } long bitLength = bitsArrayLength * 32L - BitOperations.LeadingZeroCount(highValue); @@ -2495,7 +2493,7 @@ private void AssertValid() // Wasted space: _bits[0] could have been packed into _sign Debug.Assert(_bits.Length > 1 || _bits[0] >= kuMaskHighBit); // Wasted space: leading zeros could have been truncated - Debug.Assert(_bits[^1] != 0); + Debug.Assert(_bits[_bits.Length - 1] != 0); } else { diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.DivRem.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.DivRem.cs index de1001d7f4f38..bb75e77a228d0 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.DivRem.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.DivRem.cs @@ -173,7 +173,7 @@ private static unsafe void Divide(uint* left, int leftLength, uint divLo = rightLength > 1 ? right[rightLength - 2] : 0; // We measure the leading zeros of the divisor - int shift = BitOperations.LeadingZeroCount(divHi); + int shift = LeadingZeros(divHi); int backShift = 32 - shift; // And, we make sure the most significant bit is set @@ -316,5 +316,39 @@ private static bool DivideGuessTooBig(ulong q, ulong valHi, uint valLo, return false; } + + private static int LeadingZeros(uint value) + { + if (value == 0) + return 32; + + int count = 0; + if ((value & 0xFFFF0000) == 0) + { + count += 16; + value = value << 16; + } + if ((value & 0xFF000000) == 0) + { + count += 8; + value = value << 8; + } + if ((value & 0xF0000000) == 0) + { + count += 4; + value = value << 4; + } + if ((value & 0xC0000000) == 0) + { + count += 2; + value = value << 2; + } + if ((value & 0x80000000) == 0) + { + count += 1; + } + + return count; + } } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.GcdInv.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.GcdInv.cs index 9368891c0428e..d984912ec6246 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.GcdInv.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.GcdInv.cs @@ -238,7 +238,7 @@ private static void ExtractDigits(ref BitsBuffer xBuffer, } // Use all the bits but one, see [hac] 14.58 (ii) - int z = BitOperations.LeadingZeroCount((uint)xh); + int z = LeadingZeros((uint)xh); x = ((xh << 32 + z) | (xm << z) | (xl >> 32 - z)) >> 1; y = ((yh << 32 + z) | (ym << z) | (yl >> 32 - z)) >> 1; diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs index 8ec356e6dc215..9325be0d806c2 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs @@ -370,7 +370,8 @@ public static Complex Reciprocal(Complex value) public override bool Equals([NotNullWhen(true)] object? obj) { - return obj is Complex other && Equals(other); + if (!(obj is Complex)) return false; + return Equals((Complex)obj); } public bool Equals(Complex value) @@ -378,7 +379,14 @@ public bool Equals(Complex value) return m_real.Equals(value.m_real) && m_imaginary.Equals(value.m_imaginary); } - public override int GetHashCode() => HashCode.Combine(m_real, m_imaginary); + public override int GetHashCode() + { + int n1 = 99999997; + int realHash = m_real.GetHashCode() % n1; + int imaginaryHash = m_imaginary.GetHashCode(); + int finalHash = realHash ^ imaginaryHash; + return finalHash; + } public override string ToString() => $"({m_real}, {m_imaginary})"; diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs index 0926f69e2ba68..e89bc4acd13e8 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/NumericsHelpers.cs @@ -2,20 +2,32 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Runtime.InteropServices; namespace System.Numerics { + [StructLayout(LayoutKind.Explicit)] + internal struct DoubleUlong + { + [FieldOffset(0)] + public double dbl; + [FieldOffset(0)] + public ulong uu; + } + internal static class NumericsHelpers { private const int kcbitUint = 32; public static void GetDoubleParts(double dbl, out int sign, out int exp, out ulong man, out bool fFinite) { - ulong bits = BitConverter.DoubleToUInt64Bits(dbl); + DoubleUlong du; + du.uu = 0; + du.dbl = dbl; - sign = 1 - ((int)(bits >> 62) & 2); - man = bits & 0x000FFFFFFFFFFFFF; - exp = (int)(bits >> 52) & 0x7FF; + sign = 1 - ((int)(du.uu >> 62) & 2); + man = du.uu & 0x000FFFFFFFFFFFFF; + exp = (int)(du.uu >> 52) & 0x7FF; if (exp == 0) { // Denormalized number. @@ -39,14 +51,15 @@ public static void GetDoubleParts(double dbl, out int sign, out int exp, out ulo public static double GetDoubleFromParts(int sign, int exp, ulong man) { - ulong bits; + DoubleUlong du; + du.dbl = 0; if (man == 0) - bits = 0; + du.uu = 0; else { // Normalize so that 0x0010 0000 0000 0000 is the highest bit set. - int cbitShift = BitOperations.LeadingZeroCount(man) - 11; + int cbitShift = CbitHighZero(man) - 11; if (cbitShift < 0) man >>= -cbitShift; else @@ -61,7 +74,7 @@ public static double GetDoubleFromParts(int sign, int exp, ulong man) if (exp >= 0x7FF) { // Infinity. - bits = 0x7FF0000000000000; + du.uu = 0x7FF0000000000000; } else if (exp <= 0) { @@ -70,25 +83,25 @@ public static double GetDoubleFromParts(int sign, int exp, ulong man) if (exp < -52) { // Underflow to zero. - bits = 0; + du.uu = 0; } else { - bits = man >> -exp; - Debug.Assert(bits != 0); + du.uu = man >> -exp; + Debug.Assert(du.uu != 0); } } else { // Mask off the implicit high bit. - bits = (man & 0x000FFFFFFFFFFFFF) | ((ulong)exp << 52); + du.uu = (man & 0x000FFFFFFFFFFFFF) | ((ulong)exp << 52); } } if (sign < 0) - bits |= 0x8000000000000000; + du.uu |= 0x8000000000000000; - return BitConverter.UInt64BitsToDouble(bits); + return du.dbl; } // Do an in-place two's complement. "Dangerous" because it causes @@ -126,5 +139,53 @@ public static uint Abs(int a) return ((uint)a ^ mask) - mask; } } + + public static uint CombineHash(uint u1, uint u2) + { + return ((u1 << 7) | (u1 >> 25)) ^ u2; + } + + public static int CombineHash(int n1, int n2) + { + return unchecked((int)CombineHash((uint)n1, (uint)n2)); + } + + public static int CbitHighZero(uint u) + { + if (u == 0) + return 32; + + int cbit = 0; + if ((u & 0xFFFF0000) == 0) + { + cbit += 16; + u <<= 16; + } + if ((u & 0xFF000000) == 0) + { + cbit += 8; + u <<= 8; + } + if ((u & 0xF0000000) == 0) + { + cbit += 4; + u <<= 4; + } + if ((u & 0xC0000000) == 0) + { + cbit += 2; + u <<= 2; + } + if ((u & 0x80000000) == 0) + cbit += 1; + return cbit; + } + + public static int CbitHighZero(ulong uu) + { + if ((uu & 0xFFFFFFFF00000000) == 0) + return 32 + CbitHighZero((uint)uu); + return CbitHighZero((uint)(uu >> 32)); + } } }