diff --git a/src/coreclr/jit/utils.cpp b/src/coreclr/jit/utils.cpp index d17d0431f9ee80..d4bd1b6ebeb238 100644 --- a/src/coreclr/jit/utils.cpp +++ b/src/coreclr/jit/utils.cpp @@ -2373,65 +2373,34 @@ double FloatingPointUtils::round(double x) // MathF.Round(float), and FloatingPointUtils::round(float) // ************************************************************************************ - // This is based on the 'Berkeley SoftFloat Release 3e' algorithm + // This represents the boundary at which point we can only represent whole integers + const double IntegerBoundary = 4503599627370496.0; // 2^52 - uint64_t bits = *reinterpret_cast(&x); - int32_t exponent = (int32_t)(bits >> 52) & 0x07FF; - - if (exponent <= 0x03FE) - { - if ((bits << 1) == 0) - { - // Exactly +/- zero should return the original value - return x; - } - - // Any value less than or equal to 0.5 will always round to exactly zero - // and any value greater than 0.5 will always round to exactly one. However, - // we need to preserve the original sign for IEEE compliance. - - double result = ((exponent == 0x03FE) && ((bits & UI64(0x000FFFFFFFFFFFFF)) != 0)) ? 1.0 : 0.0; - return _copysign(result, x); - } - - if (exponent >= 0x0433) + if (fabs(x) >= IntegerBoundary) { - // Any value greater than or equal to 2^52 cannot have a fractional part, - // So it will always round to exactly itself. - + // Values above this boundary don't have a fractional + // portion and so we can simply return them as-is. return x; } - // The absolute value should be greater than or equal to 1.0 and less than 2^52 - assert((0x03FF <= exponent) && (exponent <= 0x0432)); - - // Determine the last bit that represents the integral portion of the value - // and the bits representing the fractional portion - - uint64_t lastBitMask = UI64(1) << (0x0433 - exponent); - uint64_t roundBitsMask = lastBitMask - 1; - - // Increment the first fractional bit, which represents the midpoint between - // two integral values in the current window. - - bits += lastBitMask >> 1; - - if ((bits & roundBitsMask) == 0) - { - // If that overflowed and the rest of the fractional bits are zero - // then we were exactly x.5 and we want to round to the even result - - bits &= ~lastBitMask; - } - else - { - // Otherwise, we just want to strip the fractional bits off, truncating - // to the current integer value. - - bits &= ~roundBitsMask; - } + // Otherwise, since floating-point takes the inputs, performs + // the computation as if to infinite precision and unbounded + // range, and then rounds to the nearest representable result + // using the current rounding mode, we can rely on this to + // cheaply round. + // + // In particular, .NET doesn't support changing the rounding + // mode and defaults to "round to nearest, ties to even", thus + // by adding the original value to the IntegerBoundary we get + // an exactly represented whole integer that is precisely the + // IntegerBoundary greater in magnitude than the answer we want. + // + // We can then simply remove that offset to get the correct answer, + // noting that we also need to copy back the original sign to + // correctly handle -0.0 - return *reinterpret_cast(&bits); + double temp = _copysign(IntegerBoundary, x); + return _copysign((x + temp) - temp, x); } // Windows x86 and Windows ARM/ARM64 may not define _copysignf() but they do define _copysign(). @@ -2455,65 +2424,40 @@ float FloatingPointUtils::round(float x) // Math.Round(double), and FloatingPointUtils::round(double) // ************************************************************************************ - // This is based on the 'Berkeley SoftFloat Release 3e' algorithm - - uint32_t bits = *reinterpret_cast(&x); - int32_t exponent = (int32_t)(bits >> 23) & 0xFF; - - if (exponent <= 0x7E) - { - if ((bits << 1) == 0) - { - // Exactly +/- zero should return the original value - return x; - } - - // Any value less than or equal to 0.5 will always round to exactly zero - // and any value greater than 0.5 will always round to exactly one. However, - // we need to preserve the original sign for IEEE compliance. + // This code is based on `nearbyint` from amd/aocl-libm-ose + // Copyright (C) 2008-2022 Advanced Micro Devices, Inc. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text - float result = ((exponent == 0x7E) && ((bits & 0x007FFFFF) != 0)) ? 1.0f : 0.0f; - return _copysignf(result, x); - } + // This represents the boundary at which point we can only represent whole integers + const float IntegerBoundary = 8388608.0f; // 2^23 - if (exponent >= 0x96) + if (fabsf(x) >= IntegerBoundary) { - // Any value greater than or equal to 2^52 cannot have a fractional part, - // So it will always round to exactly itself. - + // Values above this boundary don't have a fractional + // portion and so we can simply return them as-is. return x; } - // The absolute value should be greater than or equal to 1.0 and less than 2^52 - assert((0x7F <= exponent) && (exponent <= 0x95)); - - // Determine the last bit that represents the integral portion of the value - // and the bits representing the fractional portion - - uint32_t lastBitMask = 1U << (0x96 - exponent); - uint32_t roundBitsMask = lastBitMask - 1; - - // Increment the first fractional bit, which represents the midpoint between - // two integral values in the current window. - - bits += lastBitMask >> 1; - - if ((bits & roundBitsMask) == 0) - { - // If that overflowed and the rest of the fractional bits are zero - // then we were exactly x.5 and we want to round to the even result - - bits &= ~lastBitMask; - } - else - { - // Otherwise, we just want to strip the fractional bits off, truncating - // to the current integer value. - - bits &= ~roundBitsMask; - } + // Otherwise, since floating-point takes the inputs, performs + // the computation as if to infinite precision and unbounded + // range, and then rounds to the nearest representable result + // using the current rounding mode, we can rely on this to + // cheaply round. + // + // In particular, .NET doesn't support changing the rounding + // mode and defaults to "round to nearest, ties to even", thus + // by adding the original value to the IntegerBoundary we get + // an exactly represented whole integer that is precisely the + // IntegerBoundary greater in magnitude than the answer we want. + // + // We can then simply remove that offset to get the correct answer, + // noting that we also need to copy back the original sign to + // correctly handle -0.0 - return *reinterpret_cast(&bits); + float temp = _copysignf(IntegerBoundary, x); + return _copysignf((x + temp) - temp, x); } bool FloatingPointUtils::isNormal(double x) diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index b4177addb4ab31..71d73546bc1364 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -84,6 +84,7 @@ public readonly struct Double internal const ulong BiasedExponentMask = 0x7FF0_0000_0000_0000; internal const int BiasedExponentShift = 52; + internal const int BiasedExponentLength = 11; internal const ushort ShiftedExponentMask = (ushort)(BiasedExponentMask >> BiasedExponentShift); internal const ulong TrailingSignificandMask = 0x000F_FFFF_FFFF_FFFF; @@ -105,8 +106,17 @@ public readonly struct Double internal const int TrailingSignificandLength = 52; internal const int SignificandLength = TrailingSignificandLength + 1; - internal const long PositiveInfinityBits = 0x7FF00000_00000000; - internal const long SmallestNormalBits = 0x00100000_00000000; + // Constants representing the private bit-representation for various default values + + internal const ulong PositiveZeroBits = 0x0000_0000_0000_0000; + internal const ulong NegativeZeroBits = 0x8000_0000_0000_0000; + + internal const ulong EpsilonBits = 0x0000_0000_0000_0001; + + internal const ulong PositiveInfinityBits = 0x7FF0_0000_0000_0000; + internal const ulong NegativeInfinityBits = 0xFFF0_0000_0000_0000; + + internal const ulong SmallestNormalBits = 0x0010_0000_0000_0000; internal ushort BiasedExponent { @@ -153,27 +163,28 @@ internal static ulong ExtractTrailingSignificandFromBits(ulong bits) } /// Determines whether the specified value is finite (zero, subnormal, or normal). + /// This effectively checks the value is not NaN and not infinite. [NonVersionable] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe bool IsFinite(double d) + public static bool IsFinite(double d) { - long bits = BitConverter.DoubleToInt64Bits(d); - return (bits & 0x7FFFFFFFFFFFFFFF) < 0x7FF0000000000000; + ulong bits = BitConverter.DoubleToUInt64Bits(d); + return (~bits & PositiveInfinityBits) != 0; } /// Determines whether the specified value is infinite. [NonVersionable] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe bool IsInfinity(double d) + public static bool IsInfinity(double d) { - long bits = BitConverter.DoubleToInt64Bits(d); - return (bits & 0x7FFFFFFFFFFFFFFF) == 0x7FF0000000000000; + ulong bits = BitConverter.DoubleToUInt64Bits(d); + return (bits & ~SignMask) == PositiveInfinityBits; } /// Determines whether the specified value is NaN. [NonVersionable] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe bool IsNaN(double d) + public static bool IsNaN(double d) { // A NaN will never equal itself so this is an // easy and efficient way to check for NaN. @@ -183,10 +194,18 @@ public static unsafe bool IsNaN(double d) #pragma warning restore CS1718 } + [NonVersionable] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsNaNOrZero(double d) + { + ulong bits = BitConverter.DoubleToUInt64Bits(d); + return ((bits - 1) & ~SignMask) >= PositiveInfinityBits; + } + /// Determines whether the specified value is negative. [NonVersionable] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe bool IsNegative(double d) + public static bool IsNegative(double d) { return BitConverter.DoubleToInt64Bits(d) < 0; } @@ -199,14 +218,14 @@ public static bool IsNegativeInfinity(double d) return d == NegativeInfinity; } - /// Determines whether the specified value is normal. + /// Determines whether the specified value is normal (finite, but not zero or subnormal). + /// This effectively checks the value is not NaN, not infinite, not subnormal, and not zero. [NonVersionable] - // This is probably not worth inlining, it has branches and should be rarely called - public static unsafe bool IsNormal(double d) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNormal(double d) { - long bits = BitConverter.DoubleToInt64Bits(d); - bits &= 0x7FFFFFFFFFFFFFFF; - return (bits < 0x7FF0000000000000) && (bits != 0) && ((bits & 0x7FF0000000000000) != 0); + ulong bits = BitConverter.DoubleToUInt64Bits(d); + return ((bits & ~SignMask) - SmallestNormalBits) < (PositiveInfinityBits - SmallestNormalBits); } /// Determines whether the specified value is positive infinity. @@ -217,14 +236,21 @@ public static bool IsPositiveInfinity(double d) return d == PositiveInfinity; } - /// Determines whether the specified value is subnormal. + /// Determines whether the specified value is subnormal (finite, but not zero or normal). + /// This effectively checks the value is not NaN, not infinite, not normal, and not zero. + [NonVersionable] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsSubnormal(double d) + { + ulong bits = BitConverter.DoubleToUInt64Bits(d); + return ((bits & ~SignMask) - 1) < MaxTrailingSignificand; + } + [NonVersionable] - // This is probably not worth inlining, it has branches and should be rarely called - public static unsafe bool IsSubnormal(double d) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsZero(double d) { - long bits = BitConverter.DoubleToInt64Bits(d); - bits &= 0x7FFFFFFFFFFFFFFF; - return (bits < 0x7FF0000000000000) && (bits != 0) && ((bits & 0x7FF0000000000000) == 0); + return d == 0; } // Compares this object to another object, returning an instance of System.Relation. @@ -236,56 +262,45 @@ public static unsafe bool IsSubnormal(double d) // public int CompareTo(object? value) { - if (value == null) + if (value is not double other) { - return 1; - } - - if (value is double d) - { - if (m_value < d) return -1; - if (m_value > d) return 1; - if (m_value == d) return 0; - - // At least one of the values is NaN. - if (IsNaN(m_value)) - return IsNaN(d) ? 0 : -1; - else - return 1; + return (value is null) ? 1 : throw new ArgumentException(SR.Arg_MustBeDouble); } - - throw new ArgumentException(SR.Arg_MustBeDouble); + return CompareTo(other); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public int CompareTo(double value) { - if (m_value < value) return -1; - if (m_value > value) return 1; - if (m_value == value) return 0; + if (m_value < value) + { + return -1; + } + + if (m_value > value) + { + return 1; + } + + if (m_value == value) + { + return 0; + } - // At least one of the values is NaN. if (IsNaN(m_value)) + { return IsNaN(value) ? 0 : -1; - else - return 1; + } + + Debug.Assert(IsNaN(value)); + return 1; } // True if obj is another Double with the same value as the current instance. This is // a method of object equality, that only returns true if obj is also a double. public override bool Equals([NotNullWhen(true)] object? obj) { - if (!(obj is double)) - { - return false; - } - double temp = ((double)obj).m_value; - // This code below is written this way for performance reasons i.e the != and == check is intentional. - if (temp == m_value) - { - return true; - } - return IsNaN(temp) && IsNaN(m_value); + return (obj is double other) && Equals(other); } /// @@ -326,13 +341,12 @@ public bool Equals(double obj) [MethodImpl(MethodImplOptions.AggressiveInlining)] // 64-bit constants make the IL unusually large that makes the inliner to reject the method public override int GetHashCode() { - long bits = Unsafe.As(ref Unsafe.AsRef(in m_value)); + ulong bits = BitConverter.DoubleToUInt64Bits(m_value); - // Optimized check for IsNan() || IsZero() - if (((bits - 1) & 0x7FFFFFFFFFFFFFFF) >= 0x7FF0000000000000) + if (IsNaNOrZero(m_value)) { // Ensure that all NaNs and both zeros have the same hash code - bits &= 0x7FF0000000000000; + bits &= PositiveInfinityBits; } return unchecked((int)bits) ^ ((int)(bits >> 32)); @@ -1116,7 +1130,7 @@ public static bool IsRealNumber(double value) } /// - static bool INumberBase.IsZero(double value) => (value == 0); + static bool INumberBase.IsZero(double value) => IsZero(value); /// [Intrinsic] diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index cd3e6ab3ed73c3..adc7df07932f7f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -8,7 +8,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; namespace System { @@ -38,6 +37,7 @@ public readonly struct Half internal const ushort BiasedExponentMask = 0x7C00; internal const int BiasedExponentShift = 10; + internal const int BiasedExponentLength = 5; internal const byte ShiftedBiasedExponentMask = BiasedExponentMask >> BiasedExponentShift; internal const ushort TrailingSignificandMask = 0x03FF; @@ -78,6 +78,8 @@ public readonly struct Half private const ushort PositiveOneBits = 0x3C00; private const ushort NegativeOneBits = 0xBC00; + private const ushort SmallestNormalBits = 0x0400; + private const ushort EBits = 0x4170; private const ushort PiBits = 0x4248; private const ushort TauBits = 0x4648; @@ -227,59 +229,81 @@ internal static ushort ExtractTrailingSignificandFromBits(ushort bits) } /// Determines whether the specified value is finite (zero, subnormal, or normal). + /// This effectively checks the value is not NaN and not infinite. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsFinite(Half value) { - return StripSign(value) < PositiveInfinityBits; + uint bits = value._value; + return (~bits & PositiveInfinityBits) != 0; } /// Determines whether the specified value is infinite. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsInfinity(Half value) { - return StripSign(value) == PositiveInfinityBits; + uint bits = value._value; + return (bits & ~SignMask) == PositiveInfinityBits; } /// Determines whether the specified value is NaN. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNaN(Half value) { - return StripSign(value) > PositiveInfinityBits; + uint bits = value._value; + return (bits & ~SignMask) > PositiveInfinityBits; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsNaNOrZero(Half value) + { + uint bits = value._value; + return ((bits - 1) & ~SignMask) >= PositiveInfinityBits; } /// Determines whether the specified value is negative. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNegative(Half value) { return (short)(value._value) < 0; } /// Determines whether the specified value is negative infinity. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNegativeInfinity(Half value) { return value._value == NegativeInfinityBits; } - /// Determines whether the specified value is normal. - // This is probably not worth inlining, it has branches and should be rarely called + /// Determines whether the specified value is normal (finite, but not zero or subnormal). + /// This effectively checks the value is not NaN, not infinite, not subnormal, and not zero. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNormal(Half value) { - uint absValue = StripSign(value); - return (absValue < PositiveInfinityBits) // is finite - && (absValue != 0) // is not zero - && ((absValue & BiasedExponentMask) != 0); // is not subnormal (has a non-zero exponent) + uint bits = value._value; + return (ushort)((bits & ~SignMask) - SmallestNormalBits) < (PositiveInfinityBits - SmallestNormalBits); } /// Determines whether the specified value is positive infinity. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsPositiveInfinity(Half value) { return value._value == PositiveInfinityBits; } - /// Determines whether the specified value is subnormal. - // This is probably not worth inlining, it has branches and should be rarely called + /// Determines whether the specified value is subnormal (finite, but not zero or normal). + /// This effectively checks the value is not NaN, not infinite, not normal, and not zero. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsSubnormal(Half value) { - uint absValue = StripSign(value); - return (absValue < PositiveInfinityBits) // is finite - && (absValue != 0) // is not zero - && ((absValue & BiasedExponentMask) == 0); // is subnormal (has a zero exponent) + uint bits = value._value; + return (ushort)((bits & ~SignMask) - 1) < MaxTrailingSignificand; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsZero(Half value) + { + uint bits = value._value; + return (bits & ~SignMask) == 0; } /// @@ -395,17 +419,7 @@ private static bool AreZero(Half left, Half right) // IEEE defines that positive and negative zero are equal, this gives us a quick equality check // for two values by or'ing the private bits together and stripping the sign. They are both zero, // and therefore equivalent, if the resulting value is still zero. - return (ushort)((left._value | right._value) & ~SignMask) == 0; - } - - private static bool IsNaNOrZero(Half value) - { - return ((value._value - 1) & ~SignMask) >= PositiveInfinityBits; - } - - private static uint StripSign(Half value) - { - return (ushort)(value._value & ~SignMask); + return ((left._value | right._value) & ~SignMask) == 0; } /// @@ -415,11 +429,11 @@ private static uint StripSign(Half value) /// Thrown when is not of type . public int CompareTo(object? obj) { - if (!(obj is Half)) + if (obj is not Half other) { return (obj is null) ? 1 : throw new ArgumentException(SR.Arg_MustBeHalf); } - return CompareTo((Half)(obj)); + return CompareTo(other); } /// @@ -475,12 +489,15 @@ public bool Equals(Half other) /// public override int GetHashCode() { + uint bits = _value; + if (IsNaNOrZero(this)) { - // All NaNs should have the same hash code, as should both Zeros. - return _value & PositiveInfinityBits; + // Ensure that all NaNs and both zeros have the same hash code + bits &= PositiveInfinityBits; } - return _value; + + return (int)bits; } /// @@ -592,7 +609,7 @@ public static explicit operator Half(double value) /// converted to its nearest representable half-precision floating-point value. public static explicit operator Half(long value) => (Half)(float)value; - /// Explicitly converts a value to its nearest representable half-precision floating-point value. + /// Explicitly converts a value to its nearest representable half-precision floating-point value. /// The value to convert. /// converted to its nearest representable half-precision floating-point value. public static explicit operator Half(nint value) => (Half)(float)value; @@ -781,7 +798,7 @@ public static explicit operator Half(float value) [CLSCompliant(false)] public static explicit operator Half(ulong value) => (Half)(float)value; - /// Explicitly converts a value to its nearest representable half-precision floating-point value. + /// Explicitly converts a value to its nearest representable half-precision floating-point value. /// The value to convert. /// converted to its nearest representable half-precision floating-point value. [CLSCompliant(false)] @@ -1228,29 +1245,25 @@ public static bool IsPow2(Half value) /// static Half IBitwiseOperators.operator &(Half left, Half right) { - ushort bits = (ushort)(BitConverter.HalfToUInt16Bits(left) & BitConverter.HalfToUInt16Bits(right)); - return BitConverter.UInt16BitsToHalf(bits); + return new Half((ushort)(left._value & right._value)); } /// static Half IBitwiseOperators.operator |(Half left, Half right) { - ushort bits = (ushort)(BitConverter.HalfToUInt16Bits(left) | BitConverter.HalfToUInt16Bits(right)); - return BitConverter.UInt16BitsToHalf(bits); + return new Half((ushort)(left._value | right._value)); } /// static Half IBitwiseOperators.operator ^(Half left, Half right) { - ushort bits = (ushort)(BitConverter.HalfToUInt16Bits(left) ^ BitConverter.HalfToUInt16Bits(right)); - return BitConverter.UInt16BitsToHalf(bits); + return new Half((ushort)(left._value ^ right._value)); } /// static Half IBitwiseOperators.operator ~(Half value) { - ushort bits = (ushort)(~BitConverter.HalfToUInt16Bits(value)); - return BitConverter.UInt16BitsToHalf(bits); + return new Half((ushort)(~value._value)); } // @@ -1456,9 +1469,9 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destination /// public static Half BitDecrement(Half x) { - ushort bits = x._value; + uint bits = x._value; - if ((bits & PositiveInfinityBits) >= PositiveInfinityBits) + if (!IsFinite(x)) { // NaN returns NaN // -Infinity returns -Infinity @@ -1475,16 +1488,23 @@ public static Half BitDecrement(Half x) // Negative values need to be incremented // Positive values need to be decremented - bits += (ushort)(((short)bits < 0) ? +1 : -1); - return new Half(bits); + if (IsNegative(x)) + { + bits += 1; + } + else + { + bits -= 1; + } + return new Half((ushort)bits); } /// public static Half BitIncrement(Half x) { - ushort bits = x._value; + uint bits = x._value; - if ((bits & PositiveInfinityBits) >= PositiveInfinityBits) + if (!IsFinite(x)) { // NaN returns NaN // -Infinity returns MinValue @@ -1501,8 +1521,15 @@ public static Half BitIncrement(Half x) // Negative values need to be decremented // Positive values need to be incremented - bits += (ushort)(((short)bits < 0) ? -1 : +1); - return new Half(bits); + if (IsNegative(x)) + { + bits -= 1; + } + else + { + bits += 1; + } + return new Half((ushort)bits); } /// @@ -1512,7 +1539,32 @@ public static Half BitIncrement(Half x) public static Half Ieee754Remainder(Half left, Half right) => (Half)MathF.IEEERemainder((float)left, (float)right); /// - public static int ILogB(Half x) => MathF.ILogB((float)x); + public static int ILogB(Half x) + { + // This code is based on `ilogbf` from amd/aocl-libm-ose + // Copyright (C) 2008-2022 Advanced Micro Devices, Inc. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + if (!IsNormal(x)) // x is zero, subnormal, infinity, or NaN + { + if (IsZero(x)) + { + return int.MinValue; + } + + if (!IsFinite(x)) // infinity or NaN + { + return int.MaxValue; + } + + Debug.Assert(IsSubnormal(x)); + return MinExponent - (BitOperations.TrailingZeroCount(x.TrailingSignificand) - BiasedExponentLength); + } + + return x.Exponent; + } /// public static Half Lerp(Half value1, Half value2, Half amount) => (Half)float.Lerp((float)value1, (float)value2, (float)amount); @@ -1614,7 +1666,17 @@ public static Half BitIncrement(Half x) public static Half Clamp(Half value, Half min, Half max) => (Half)Math.Clamp((float)value, (float)min, (float)max); /// - public static Half CopySign(Half value, Half sign) => (Half)MathF.CopySign((float)value, (float)sign); + public static Half CopySign(Half value, Half sign) + { + // This method is required to work for all inputs, + // including NaN, so we operate on the raw bits. + uint xbits = value._value; + uint ybits = sign._value; + + // Remove the sign from x, and remove everything but the sign from y + // Then, simply OR them to get the correct sign + return new Half((ushort)((xbits & ~SignMask) | (ybits & SignMask))); + } /// public static Half Max(Half x, Half y) => (Half)MathF.Max((float)x, (float)y); @@ -1667,7 +1729,24 @@ public static Half MinNumber(Half x, Half y) } /// - public static int Sign(Half value) => MathF.Sign((float)value); + public static int Sign(Half value) + { + if (IsNaN(value)) + { + throw new ArithmeticException(SR.Arithmetic_NaN); + } + + if (IsZero(value)) + { + return 0; + } + else if (IsNegative(value)) + { + return -1; + } + + return +1; + } // // INumberBase @@ -1683,7 +1762,7 @@ public static Half MinNumber(Half x, Half y) public static Half Zero => new Half(PositiveZeroBits); /// - public static Half Abs(Half value) => (Half)MathF.Abs((float)value); + public static Half Abs(Half value) => new Half((ushort)(value._value & ~SignMask)); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1775,7 +1854,7 @@ public static bool IsRealNumber(Half value) } /// - static bool INumberBase.IsZero(Half value) => (value == Zero); + static bool INumberBase.IsZero(Half value) => IsZero(value); /// public static Half MaxMagnitude(Half x, Half y) => (Half)MathF.MaxMagnitude((float)x, (float)y); diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index 5bf8b74de416d4..d1ba7b0dca27c5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -44,10 +44,6 @@ public static partial class Math private const double SCALEB_C3 = 9007199254740992; // 0x1p53 - private const int ILogB_NaN = 0x7FFFFFFF; - - private const int ILogB_Zero = (-1 - 0x7FFFFFFF); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static short Abs(short value) { @@ -241,17 +237,17 @@ public static long BigMul(long a, long b, out long low) public static double BitDecrement(double x) { - long bits = BitConverter.DoubleToInt64Bits(x); + ulong bits = BitConverter.DoubleToUInt64Bits(x); - if (((bits >> 32) & 0x7FF00000) >= 0x7FF00000) + if (!double.IsFinite(x)) { // NaN returns NaN // -Infinity returns -Infinity - // +Infinity returns double.MaxValue - return (bits == 0x7FF00000_00000000) ? double.MaxValue : x; + // +Infinity returns MaxValue + return (bits == double.PositiveInfinityBits) ? double.MaxValue : x; } - if (bits == 0x00000000_00000000) + if (bits == double.PositiveZeroBits) { // +0.0 returns -double.Epsilon return -double.Epsilon; @@ -260,33 +256,47 @@ public static double BitDecrement(double x) // Negative values need to be incremented // Positive values need to be decremented - bits += ((bits < 0) ? +1 : -1); - return BitConverter.Int64BitsToDouble(bits); + if (double.IsNegative(x)) + { + bits += 1; + } + else + { + bits -= 1; + } + return BitConverter.UInt64BitsToDouble(bits); } public static double BitIncrement(double x) { - long bits = BitConverter.DoubleToInt64Bits(x); + ulong bits = BitConverter.DoubleToUInt64Bits(x); - if (((bits >> 32) & 0x7FF00000) >= 0x7FF00000) + if (!double.IsFinite(x)) { // NaN returns NaN - // -Infinity returns double.MinValue + // -Infinity returns MinValue // +Infinity returns +Infinity - return (bits == unchecked((long)(0xFFF00000_00000000))) ? double.MinValue : x; + return (bits == double.NegativeInfinityBits) ? double.MinValue : x; } - if (bits == unchecked((long)(0x80000000_00000000))) + if (bits == double.NegativeZeroBits) { - // -0.0 returns double.Epsilon + // -0.0 returns Epsilon return double.Epsilon; } // Negative values need to be decremented // Positive values need to be incremented - bits += ((bits < 0) ? -1 : +1); - return BitConverter.Int64BitsToDouble(bits); + if (double.IsNegative(x)) + { + bits -= 1; + } + else + { + bits += 1; + } + return BitConverter.UInt64BitsToDouble(bits); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -303,19 +313,14 @@ public static double CopySign(double x, double y) static double SoftwareFallback(double x, double y) { - const long signMask = 1L << 63; - // This method is required to work for all inputs, // including NaN, so we operate on the raw bits. - long xbits = BitConverter.DoubleToInt64Bits(x); - long ybits = BitConverter.DoubleToInt64Bits(y); + ulong xbits = BitConverter.DoubleToUInt64Bits(x); + ulong ybits = BitConverter.DoubleToUInt64Bits(y); // Remove the sign from x, and remove everything but the sign from y - xbits &= ~signMask; - ybits &= signMask; - - // Simply OR them to get the correct sign - return BitConverter.Int64BitsToDouble(xbits | ybits); + // Then, simply OR them to get the correct sign + return BitConverter.UInt64BitsToDouble((xbits & ~double.SignMask) | (ybits & double.SignMask)); } } @@ -822,34 +827,29 @@ public static double IEEERemainder(double x, double y) public static int ILogB(double x) { - // Implementation based on https://git.musl-libc.org/cgit/musl/tree/src/math/ilogb.c - - if (double.IsNaN(x)) - { - return ILogB_NaN; - } - - ulong i = BitConverter.DoubleToUInt64Bits(x); - int e = (int)((i >> 52) & 0x7FF); + // This code is based on `ilogb` from amd/aocl-libm-ose + // Copyright (C) 2008-2022 Advanced Micro Devices, Inc. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text - if (e == 0) + if (!double.IsNormal(x)) // x is zero, subnormal, infinity, or NaN { - i <<= 12; - if (i == 0) + if (double.IsZero(x)) { - return ILogB_Zero; + return int.MinValue; } - for (e = -0x3FF; (i >> 63) == 0; e--, i <<= 1) ; - return e; - } + if (!double.IsFinite(x)) // infinity or NaN + { + return int.MaxValue; + } - if (e == 0x7FF) - { - return (i << 12) != 0 ? ILogB_Zero : int.MaxValue; + Debug.Assert(double.IsSubnormal(x)); + return double.MinExponent - (BitOperations.TrailingZeroCount(x.TrailingSignificand) - double.BiasedExponentLength); } - return e - 0x3FF; + return x.Exponent; } public static double Log(double a, double newBase) @@ -1253,65 +1253,40 @@ public static double Round(double a) // FloatingPointUtils::round(double), and FloatingPointUtils::round(float) // ************************************************************************************ - // This is based on the 'Berkeley SoftFloat Release 3e' algorithm - - ulong bits = BitConverter.DoubleToUInt64Bits(a); - ushort biasedExponent = double.ExtractBiasedExponentFromBits(bits); - - if (biasedExponent <= 0x03FE) - { - if ((bits << 1) == 0) - { - // Exactly +/- zero should return the original value - return a; - } - - // Any value less than or equal to 0.5 will always round to exactly zero - // and any value greater than 0.5 will always round to exactly one. However, - // we need to preserve the original sign for IEEE compliance. + // This code is based on `nearbyint` from amd/aocl-libm-ose + // Copyright (C) 2008-2022 Advanced Micro Devices, Inc. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text - double result = ((biasedExponent == 0x03FE) && (double.ExtractTrailingSignificandFromBits(bits) != 0)) ? 1.0 : 0.0; - return CopySign(result, a); - } + // This represents the boundary at which point we can only represent whole integers + const double IntegerBoundary = 4503599627370496.0; // 2^52 - if (biasedExponent >= 0x0433) + if (Abs(a) >= IntegerBoundary) { - // Any value greater than or equal to 2^52 cannot have a fractional part, - // So it will always round to exactly itself. - + // Values above this boundary don't have a fractional + // portion and so we can simply return them as-is. return a; } - // The absolute value should be greater than or equal to 1.0 and less than 2^52 - Debug.Assert((0x03FF <= biasedExponent) && (biasedExponent <= 0x0432)); - - // Determine the last bit that represents the integral portion of the value - // and the bits representing the fractional portion - - ulong lastBitMask = 1UL << (0x0433 - biasedExponent); - ulong roundBitsMask = lastBitMask - 1; - - // Increment the first fractional bit, which represents the midpoint between - // two integral values in the current window. - - bits += lastBitMask >> 1; - - if ((bits & roundBitsMask) == 0) - { - // If that overflowed and the rest of the fractional bits are zero - // then we were exactly x.5 and we want to round to the even result - - bits &= ~lastBitMask; - } - else - { - // Otherwise, we just want to strip the fractional bits off, truncating - // to the current integer value. - - bits &= ~roundBitsMask; - } + // Otherwise, since floating-point takes the inputs, performs + // the computation as if to infinite precision and unbounded + // range, and then rounds to the nearest representable result + // using the current rounding mode, we can rely on this to + // cheaply round. + // + // In particular, .NET doesn't support changing the rounding + // mode and defaults to "round to nearest, ties to even", thus + // by adding the original value to the IntegerBoundary we get + // an exactly represented whole integer that is precisely the + // IntegerBoundary greater in magnitude than the answer we want. + // + // We can then simply remove that offset to get the correct answer, + // noting that we also need to copy back the original sign to + // correctly handle -0.0 - return BitConverter.UInt64BitsToDouble(bits); + double temp = CopySign(IntegerBoundary, a); + return CopySign((a + temp) - temp, a); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 8d9473d3c0fc21..6be7b0544bcd33 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; @@ -41,10 +42,6 @@ public static partial class MathF private const float SCALEB_C3 = 16777216f; // 0x1p24f - private const int ILogB_NaN = 0x7fffffff; - - private const int ILogB_Zero = (-1 - 0x7fffffff); - [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Abs(float x) @@ -54,17 +51,17 @@ public static float Abs(float x) public static float BitDecrement(float x) { - int bits = BitConverter.SingleToInt32Bits(x); + uint bits = BitConverter.SingleToUInt32Bits(x); - if ((bits & 0x7F800000) >= 0x7F800000) + if (!float.IsFinite(x)) { // NaN returns NaN // -Infinity returns -Infinity - // +Infinity returns float.MaxValue - return (bits == 0x7F800000) ? float.MaxValue : x; + // +Infinity returns MaxValue + return (bits == float.PositiveInfinityBits) ? float.MaxValue : x; } - if (bits == 0x00000000) + if (bits == float.PositiveZeroBits) { // +0.0 returns -float.Epsilon return -float.Epsilon; @@ -73,33 +70,47 @@ public static float BitDecrement(float x) // Negative values need to be incremented // Positive values need to be decremented - bits += ((bits < 0) ? +1 : -1); - return BitConverter.Int32BitsToSingle(bits); + if (float.IsNegative(x)) + { + bits += 1; + } + else + { + bits -= 1; + } + return BitConverter.UInt32BitsToSingle(bits); } public static float BitIncrement(float x) { - int bits = BitConverter.SingleToInt32Bits(x); + uint bits = BitConverter.SingleToUInt32Bits(x); - if ((bits & 0x7F800000) >= 0x7F800000) + if (!float.IsFinite(x)) { // NaN returns NaN - // -Infinity returns float.MinValue + // -Infinity returns MinValue // +Infinity returns +Infinity - return (bits == unchecked((int)(0xFF800000))) ? float.MinValue : x; + return (bits == float.NegativeInfinityBits) ? float.MinValue : x; } - if (bits == unchecked((int)(0x80000000))) + if (bits == float.NegativeZeroBits) { - // -0.0 returns float.Epsilon + // -0.0 returns Epsilon return float.Epsilon; } // Negative values need to be decremented // Positive values need to be incremented - bits += ((bits < 0) ? -1 : +1); - return BitConverter.Int32BitsToSingle(bits); + if (float.IsNegative(x)) + { + bits -= 1; + } + else + { + bits += 1; + } + return BitConverter.UInt32BitsToSingle(bits); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -116,19 +127,14 @@ public static float CopySign(float x, float y) static float SoftwareFallback(float x, float y) { - const int signMask = 1 << 31; - // This method is required to work for all inputs, // including NaN, so we operate on the raw bits. - int xbits = BitConverter.SingleToInt32Bits(x); - int ybits = BitConverter.SingleToInt32Bits(y); + uint xbits = BitConverter.SingleToUInt32Bits(x); + uint ybits = BitConverter.SingleToUInt32Bits(y); // Remove the sign from x, and remove everything but the sign from y - xbits &= ~signMask; - ybits &= signMask; - - // Simply OR them to get the correct sign - return BitConverter.Int32BitsToSingle(xbits | ybits); + // Then, simply OR them to get the correct sign + return BitConverter.UInt32BitsToSingle((xbits & ~float.SignMask) | (ybits & float.SignMask)); } } @@ -185,34 +191,29 @@ public static float IEEERemainder(float x, float y) public static int ILogB(float x) { - // Implementation based on https://git.musl-libc.org/cgit/musl/tree/src/math/ilogbf.c + // This code is based on `ilogbf` from amd/aocl-libm-ose + // Copyright (C) 2008-2022 Advanced Micro Devices, Inc. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text - if (float.IsNaN(x)) + if (!float.IsNormal(x)) // x is zero, subnormal, infinity, or NaN { - return ILogB_NaN; - } - - uint i = BitConverter.SingleToUInt32Bits(x); - int e = (int)((i >> 23) & 0xFF); + if (float.IsZero(x)) + { + return int.MinValue; + } - if (e == 0) - { - i <<= 9; - if (i == 0) + if (!float.IsFinite(x)) // infinity or NaN { - return ILogB_Zero; + return int.MaxValue; } - for (e = -0x7F; (i >> 31) == 0; e--, i <<= 1) ; - return e; + Debug.Assert(float.IsSubnormal(x)); + return float.MinExponent - (BitOperations.TrailingZeroCount(x.TrailingSignificand) - float.BiasedExponentLength); } - if (e == 0xFF) - { - return i << 9 != 0 ? ILogB_Zero : int.MaxValue; - } - - return e - 0x7F; + return x.Exponent; } public static float Log(float x, float y) @@ -358,69 +359,44 @@ public static float ReciprocalSqrtEstimate(float x) public static float Round(float x) { // ************************************************************************************ - // IMPORTANT: Do not change this implementation without also updating MathF.Round(float), + // IMPORTANT: Do not change this implementation without also updating Math.Round(float), // FloatingPointUtils::round(double), and FloatingPointUtils::round(float) // ************************************************************************************ - // This is based on the 'Berkeley SoftFloat Release 3e' algorithm - - uint bits = BitConverter.SingleToUInt32Bits(x); - byte biasedExponent = float.ExtractBiasedExponentFromBits(bits); - - if (biasedExponent <= 0x7E) - { - if ((bits << 1) == 0) - { - // Exactly +/- zero should return the original value - return x; - } - - // Any value less than or equal to 0.5 will always round to exactly zero - // and any value greater than 0.5 will always round to exactly one. However, - // we need to preserve the original sign for IEEE compliance. + // This code is based on `nearbyintf` from amd/aocl-libm-ose + // Copyright (C) 2008-2022 Advanced Micro Devices, Inc. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text - float result = ((biasedExponent == 0x7E) && (float.ExtractTrailingSignificandFromBits(bits) != 0)) ? 1.0f : 0.0f; - return CopySign(result, x); - } + // This represents the boundary at which point we can only represent whole integers + const float IntegerBoundary = 8388608.0f; // 2^23 - if (biasedExponent >= 0x96) + if (Abs(x) >= IntegerBoundary) { - // Any value greater than or equal to 2^23 cannot have a fractional part, - // So it will always round to exactly itself. - + // Values above this boundary don't have a fractional + // portion and so we can simply return them as-is. return x; } - // The absolute value should be greater than or equal to 1.0 and less than 2^23 - Debug.Assert((0x7F <= biasedExponent) && (biasedExponent <= 0x95)); - - // Determine the last bit that represents the integral portion of the value - // and the bits representing the fractional portion - - uint lastBitMask = 1U << (0x96 - biasedExponent); - uint roundBitsMask = lastBitMask - 1; - - // Increment the first fractional bit, which represents the midpoint between - // two integral values in the current window. - - bits += lastBitMask >> 1; - - if ((bits & roundBitsMask) == 0) - { - // If that overflowed and the rest of the fractional bits are zero - // then we were exactly x.5 and we want to round to the even result - - bits &= ~lastBitMask; - } - else - { - // Otherwise, we just want to strip the fractional bits off, truncating - // to the current integer value. - - bits &= ~roundBitsMask; - } + // Otherwise, since floating-point takes the inputs, performs + // the computation as if to infinite precision and unbounded + // range, and then rounds to the nearest representable result + // using the current rounding mode, we can rely on this to + // cheaply round. + // + // In particular, .NET doesn't support changing the rounding + // mode and defaults to "round to nearest, ties to even", thus + // by adding the original value to the IntegerBoundary we get + // an exactly represented whole integer that is precisely the + // IntegerBoundary greater in magnitude than the answer we want. + // + // We can then simply remove that offset to get the correct answer, + // noting that we also need to copy back the original sign to + // correctly handle -0.0 - return BitConverter.UInt32BitsToSingle(bits); + float temp = CopySign(IntegerBoundary, x); + return CopySign((x + temp) - temp, x); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs index 125c76d278a8bb..5b37fbfb3f85d4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs @@ -333,7 +333,7 @@ public static TVectorDouble LogDouble(TVectorInt64.GreaterThanOrEqual(xBits, TVectorInt64.Create(double.PositiveInfinityBits))); + | Unsafe.BitCast(TVectorInt64.GreaterThanOrEqual(xBits, TVectorInt64.Create((long)double.PositiveInfinityBits))); // subnormal TVectorDouble subnormalMask = TVectorDouble.AndNot(Unsafe.BitCast(specialMask), temp); @@ -613,7 +613,7 @@ public static TVectorDouble Log2Double(TVectorInt64.GreaterThanOrEqual(xBits, TVectorInt64.Create(double.PositiveInfinityBits))); + | Unsafe.BitCast(TVectorInt64.GreaterThanOrEqual(xBits, TVectorInt64.Create((long)double.PositiveInfinityBits))); // subnormal TVectorDouble subnormalMask = TVectorDouble.AndNot(Unsafe.BitCast(specialMask), temp); @@ -762,7 +762,7 @@ public static TVectorSingle Log2Single(TVectorInt32.GreaterThanOrEqual(xBits, TVectorInt32.Create(float.PositiveInfinityBits))); + | Unsafe.BitCast(TVectorInt32.GreaterThanOrEqual(xBits, TVectorInt32.Create((int)float.PositiveInfinityBits))); // subnormal TVectorSingle subnormalMask = TVectorSingle.AndNot(Unsafe.BitCast(specialMask), temp); diff --git a/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index 44b48ba6b5bbb7..9cc7c6b56c0adf 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -84,6 +84,7 @@ public readonly struct Single internal const uint BiasedExponentMask = 0x7F80_0000; internal const int BiasedExponentShift = 23; + internal const int BiasedExponentLength = 8; internal const byte ShiftedBiasedExponentMask = (byte)(BiasedExponentMask >> BiasedExponentShift); internal const uint TrailingSignificandMask = 0x007F_FFFF; @@ -105,8 +106,17 @@ public readonly struct Single internal const int TrailingSignificandLength = 23; internal const int SignificandLength = TrailingSignificandLength + 1; - internal const int PositiveInfinityBits = 0x7F80_0000; - internal const int SmallestNormalBits = 0x0080_0000; + // Constants representing the private bit-representation for various default values + + internal const uint PositiveZeroBits = 0x0000_0000; + internal const uint NegativeZeroBits = 0x8000_0000; + + internal const uint EpsilonBits = 0x0000_0001; + + internal const uint PositiveInfinityBits = 0x7F80_0000; + internal const uint NegativeInfinityBits = 0xFF80_0000; + + internal const uint SmallestNormalBits = 0x0080_0000; internal byte BiasedExponent { @@ -153,12 +163,13 @@ internal static uint ExtractTrailingSignificandFromBits(uint bits) } /// Determines whether the specified value is finite (zero, subnormal, or normal). + /// This effectively checks the value is not NaN and not infinite. [NonVersionable] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsFinite(float f) { - int bits = BitConverter.SingleToInt32Bits(f); - return (bits & 0x7FFFFFFF) < 0x7F800000; + uint bits = BitConverter.SingleToUInt32Bits(f); + return (~bits & PositiveInfinityBits) != 0; } /// Determines whether the specified value is infinite. @@ -166,8 +177,8 @@ public static bool IsFinite(float f) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool IsInfinity(float f) { - int bits = BitConverter.SingleToInt32Bits(f); - return (bits & 0x7FFFFFFF) == 0x7F800000; + uint bits = BitConverter.SingleToUInt32Bits(f); + return (bits & ~SignMask) == PositiveInfinityBits; } /// Determines whether the specified value is NaN. @@ -183,6 +194,14 @@ public static unsafe bool IsNaN(float f) #pragma warning restore CS1718 } + [NonVersionable] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsNaNOrZero(float f) + { + uint bits = BitConverter.SingleToUInt32Bits(f); + return ((bits - 1) & ~SignMask) >= PositiveInfinityBits; + } + /// Determines whether the specified value is negative. [NonVersionable] [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -199,14 +218,14 @@ public static unsafe bool IsNegativeInfinity(float f) return f == NegativeInfinity; } - /// Determines whether the specified value is normal. + /// Determines whether the specified value is normal (finite, but not zero or subnormal). + /// This effectively checks the value is not NaN, not infinite, not subnormal, and not zero. [NonVersionable] - // This is probably not worth inlining, it has branches and should be rarely called + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool IsNormal(float f) { - int bits = BitConverter.SingleToInt32Bits(f); - bits &= 0x7FFFFFFF; - return (bits < 0x7F800000) && (bits != 0) && ((bits & 0x7F800000) != 0); + uint bits = BitConverter.SingleToUInt32Bits(f); + return ((bits & ~SignMask) - SmallestNormalBits) < (PositiveInfinityBits - SmallestNormalBits); } /// Determines whether the specified value is positive infinity. @@ -217,14 +236,21 @@ public static unsafe bool IsPositiveInfinity(float f) return f == PositiveInfinity; } - /// Determines whether the specified value is subnormal. + /// Determines whether the specified value is subnormal (finite, but not zero or normal). + /// This effectively checks the value is not NaN, not infinite, not normal, and not zero. [NonVersionable] - // This is probably not worth inlining, it has branches and should be rarely called + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool IsSubnormal(float f) { - int bits = BitConverter.SingleToInt32Bits(f); - bits &= 0x7FFFFFFF; - return (bits < 0x7F800000) && (bits != 0) && ((bits & 0x7F800000) == 0); + uint bits = BitConverter.SingleToUInt32Bits(f); + return ((bits & ~SignMask) - 1) < MaxTrailingSignificand; + } + + [NonVersionable] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsZero(float f) + { + return f == 0; } // Compares this object to another object, returning an integer that @@ -235,39 +261,38 @@ public static unsafe bool IsSubnormal(float f) // public int CompareTo(object? value) { - if (value == null) + if (value is not float other) { - return 1; + return (value is null) ? 1 : throw new ArgumentException(SR.Arg_MustBeSingle); } - - if (value is float f) - { - if (m_value < f) return -1; - if (m_value > f) return 1; - if (m_value == f) return 0; - - // At least one of the values is NaN. - if (IsNaN(m_value)) - return IsNaN(f) ? 0 : -1; - else // f is NaN. - return 1; - } - - throw new ArgumentException(SR.Arg_MustBeSingle); + return CompareTo(other); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public int CompareTo(float value) { - if (m_value < value) return -1; - if (m_value > value) return 1; - if (m_value == value) return 0; + if (m_value < value) + { + return -1; + } + + if (m_value > value) + { + return 1; + } + + if (m_value == value) + { + return 0; + } - // At least one of the values is NaN. if (IsNaN(m_value)) + { return IsNaN(value) ? 0 : -1; - else // f is NaN. - return 1; + } + + Debug.Assert(IsNaN(value)); + return 1; } /// @@ -296,17 +321,7 @@ public int CompareTo(float value) public override bool Equals([NotNullWhen(true)] object? obj) { - if (!(obj is float)) - { - return false; - } - float temp = ((float)obj).m_value; - if (temp == m_value) - { - return true; - } - - return IsNaN(temp) && IsNaN(m_value); + return (obj is float other) && Equals(other); } public bool Equals(float obj) @@ -315,23 +330,21 @@ public bool Equals(float obj) { return true; } - return IsNaN(obj) && IsNaN(m_value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { - int bits = Unsafe.As(ref Unsafe.AsRef(in m_value)); + uint bits = BitConverter.SingleToUInt32Bits(m_value); - // Optimized check for IsNan() || IsZero() - if (((bits - 1) & 0x7FFFFFFF) >= 0x7F800000) + if (IsNaNOrZero(m_value)) { // Ensure that all NaNs and both zeros have the same hash code - bits &= 0x7F800000; + bits &= PositiveInfinityBits; } - return bits; + return (int)bits; } public override string ToString() @@ -1100,7 +1113,7 @@ public static bool IsRealNumber(float value) } /// - static bool INumberBase.IsZero(float value) => (value == 0); + static bool INumberBase.IsZero(float value) => IsZero(value); /// [Intrinsic]