From 8666adc1c29ce6cff314ad17a772c9df03a6ae72 Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Mon, 8 Jun 2020 15:00:26 -0700 Subject: [PATCH 01/14] Bit shifts based Half --- .../src/Resources/Strings.resx | 3 + .../System.Private.CoreLib.Shared.projitems | 1 + .../System.Private.CoreLib/src/System/Half.cs | 637 ++++++++++++ .../src/System/TypeCode.cs | 1 + .../System.Runtime/ref/System.Runtime.cs | 49 + .../tests/System.Runtime.Tests.csproj | 1 + .../System.Runtime/tests/System/HalfTests.cs | 939 ++++++++++++++++++ 7 files changed, 1631 insertions(+) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Half.cs create mode 100644 src/libraries/System.Runtime/tests/System/HalfTests.cs diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 671183f4096f24..ef96719ad764a7 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3751,4 +3751,7 @@ Type '{0}' returned by IDynamicInterfaceCastable is not an interface. + + Object must be of type Half + diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 73eeb0b884c3d9..8de305221ba51a 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -338,6 +338,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs new file mode 100644 index 00000000000000..a0515fb7417e46 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -0,0 +1,637 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Globalization; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System +{ + // =================================================================================================== + // Portions of the code implemented below are based on the 'Berkeley SoftFloat Release 3e' algorithms. + // =================================================================================================== + + [Serializable] + [StructLayout(LayoutKind.Sequential)] + public readonly struct Half : IComparable, IFormattable, IComparable, IEquatable + { + private const NumberStyles DefaultParseStyle = NumberStyles.Float | NumberStyles.AllowThousands; + + // + // Constants for manipulating the private bit-representation + // + + private const ushort SignMask = 0x8000; + private const ushort SignShift = 15; + + private const ushort ExponentMask = 0x7C00; + private const ushort ExponentShift = 10; + + private const ushort SignificandMask = 0x03FF; + private const ushort SignificandShift = 0; + + private const ushort MinSign = 0; + private const ushort MaxSign = 1; + + private const ushort MinExponent = 0x00; + private const ushort MaxExponent = 0x1F; + + private const ushort MinSignificand = 0x0000; + private const ushort MaxSignificand = 0x03FF; + + // + // Constants representing the private bit-representation for various default values + // + + private const ushort PositiveZeroBits = 0x0000; + private const ushort NegativeZeroBits = 0x8000; + + private const ushort EpsilonBits = 0x0001; + + private const ushort PositiveInfinityBits = 0x7C00; + private const ushort NegativeInfinityBits = 0xFC00; + + private const ushort PositiveQNaNBits = 0x7E00; + private const ushort NegativeQNaNBits = 0xFE00; + + private const ushort MinValueBits = 0xFBFF; + private const ushort MaxValueBits = 0x7BFF; + + // + // Constants that should be returned if values that cannot be represented are converted + // + + private const long IllegalValueToInt64 = long.MinValue; + private const ulong IllegalValueToUInt64 = ulong.MinValue; + private const int IllegalValueToInt32 = int.MinValue; + private const uint IllegalValueToUInt32 = uint.MinValue; + + // + // Well-defined and commonly used values + // + + public static readonly Half Epsilon = new Half(EpsilonBits); // 5.9604645E-08 + + public static readonly Half PositiveInfinity = new Half(PositiveInfinityBits); // 1.0 / 0.0 + public static readonly Half NegativeInfinity = new Half(NegativeInfinityBits); // -1.0 / 0.0 + + public static readonly Half NaN = new Half(NegativeQNaNBits); // 0.0 / 0.0 + + public static readonly Half MinValue = new Half(MinValueBits); // -65504 + public static readonly Half MaxValue = new Half(MaxValueBits); // 65504 + + // We use these explicit definitions to avoid the confusion between 0.0 and -0.0. + private static readonly Half PositiveZero = new Half(PositiveZeroBits); // 0.0 + private static readonly Half NegativeZero = new Half(NegativeZeroBits); // -0.0 + + private readonly ushort m_value; // Do not rename (binary serialization) + + //internal const uint SingleSignMask = 0x8000_0000; + //internal const int SingleSignShift = 31; + //internal const uint SingleExponentMask = 0x7F80_0000; + //internal const int SingleExponentShift = 23; + //internal const uint SingleSignificandMask = 0x007F_FFFF; + + private Half(ushort value) + { + m_value = value; + } + + private Half(bool sign, ushort exp, ushort sig) + => m_value = (ushort)(((sign ? 1 : 0) << SignShift) + (exp << ExponentShift) + sig); + + private sbyte Exponent + { + get + { + return (sbyte)((m_value & ExponentMask) >> ExponentShift); + } + } + + private ushort Significand + { + get + { + return (ushort)((m_value & SignificandMask) >> SignificandShift); + } + } + + public static bool operator <(Half left, Half right) + { + if (IsNaN(left) || IsNaN(right)) + { + // IEEE defines that NaN is unordered with respect to everything, including itself. + return false; + } + + bool leftIsNegative = IsNegative(left); + + if (leftIsNegative != IsNegative(right)) + { + // When the signs of left and right differ, we know that left is less than right if it is + // the negative value. The exception to this is if both values are zero, in which case IEEE + // says they should be equal, even if the signs differ. + return leftIsNegative && !AreZero(left, right); + } + return (short)(left.m_value) < (short)(right.m_value); + } + + public static bool operator >(Half left, Half right) + { + return right < left; + } + + public static bool operator <=(Half left, Half right) + { + if (IsNaN(left) || IsNaN(right)) + { + // IEEE defines that NaN is unordered with respect to everything, including itself. + return false; + } + + bool leftIsNegative = IsNegative(left); + + if (leftIsNegative != IsNegative(right)) + { + // When the signs of left and right differ, we know that left is less than right if it is + // the negative value. The exception to this is if both values are zero, in which case IEEE + // says they should be equal, even if the signs differ. + return leftIsNegative || AreZero(left, right); + } + return (short)(left.m_value) <= (short)(right.m_value); + } + + public static bool operator >=(Half left, Half right) + { + return right <= left; + } + + public static bool operator ==(Half left, Half right) + { + if (IsNaN(left) || IsNaN(right)) + { + // IEEE defines that NaN is not equal to anything, including itself. + return false; + } + + // IEEE defines that positive and negative zero are equivalent. + return (left.m_value == right.m_value) || AreZero(left, right); + } + + public static bool operator !=(Half left, Half right) + { + return !(left == right); + } + + // Determines whether the specified value is finite (zero, subnormal, or normal). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsFinite(Half value) + { + return StripSign(value) < PositiveInfinityBits; + } + + /// Determines whether the specified value is infinite. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsInfinity(Half value) + { + return StripSign(value) == PositiveInfinityBits; + } + + /// Determines whether the specified value is NaN. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNaN(Half value) + { + return StripSign(value) > PositiveInfinityBits; + } + + /// Determines whether the specified value is negative. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNegative(Half value) + { + return (short)(value.m_value) < 0; + } + + /// Determines whether the specified value is negative infinity. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNegativeInfinity(Half value) + { + return value.m_value == NegativeInfinityBits; + } + + /// Determines whether the specified value is normal. + // This is probably not worth inlining, it has branches and should be rarely called + public static bool IsNormal(Half value) + { + int absValue = StripSign(value); + return (absValue < PositiveInfinityBits) // is finite + && (absValue != 0) // is not zero + && ((absValue & ExponentMask) != 0); // is not subnormal (has a non-zero exponent) + } + + /// Determines whether the specified value is positive infinity. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsPositiveInfinity(Half value) + { + return value.m_value == PositiveInfinityBits; + } + + /// Determines whether the specified value is subnormal. + // This is probably not worth inlining, it has branches and should be rarely called + public static bool IsSubnormal(Half value) + { + int absValue = StripSign(value); + return (absValue < PositiveInfinityBits) // is finite + && (absValue != 0) // is not zero + && ((absValue & ExponentMask) == 0); // is subnormal (has a zero exponent) + } + + public static Half Parse(string s) + { + return (Half)float.Parse(s, style: DefaultParseStyle, provider: null); + } + + public static Half Parse(string s, NumberStyles style) + { + return (Half)float.Parse(s, style); + } + + public static Half Parse(string s, IFormatProvider provider) + { + return (Half)float.Parse(s, provider); + } + + public static Half Parse(string s, NumberStyles style = DefaultParseStyle, IFormatProvider? provider = null) + { + if (s is null) + { + throw new ArgumentNullException(nameof(s)); + } + return Parse(s.AsSpan(), style, provider); + } + + public static Half Parse(ReadOnlySpan s, NumberStyles style = DefaultParseStyle, IFormatProvider? provider = null) + { + return (Half)(float.Parse(s, style, provider)); + } + + public static bool TryParse(string s, out Half result) + { + return TryParse(s, DefaultParseStyle, provider: null, out result); + } + + public static bool TryParse(ReadOnlySpan s, out Half result) + { + return TryParse(s, DefaultParseStyle, provider: null, out result); + } + + public static bool TryParse(string s, NumberStyles style, IFormatProvider? provider, out Half result) + { + return TryParse(s.AsSpan(), style, provider, out result); + } + + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Half result) + { + bool ret = false; + if (float.TryParse(s, style, provider, out float floatResult)) + { + result = (Half)floatResult; + ret = true; + } + else + { + result = default(Half); + } + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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.m_value | right.m_value) & ~SignMask) == 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsNaNOrZero(Half value) + { + return ((value.m_value - 1) & ~SignMask) >= PositiveInfinityBits; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ushort StripSign(Half value) + { + return (ushort)(value.m_value & ~SignMask); + } + + public int CompareTo(object? obj) + { + if (!(obj is Half)) + { + return (obj is null) ? 1 : throw new ArgumentException(SR.Arg_MustBeHalf); + } + return CompareTo((Half)(obj)); + } + + public int CompareTo(Half other) + { + if ((short)(m_value) < (short)(other.m_value)) + { + return -1; + } + + if ((short)(m_value) > (short)(other.m_value)) + { + return 1; + } + + if (m_value == other.m_value) + { + return 0; + } + + if (IsNaN(this)) + { + return IsNaN(other) ? 0 : -1; + } + + Debug.Assert(IsNaN(other)); + return 1; + } + + public override bool Equals(object? obj) + { + return (obj is Half) && Equals((Half)(obj)); + } + + public bool Equals(Half other) + { + if (IsNaN(this) || IsNaN(other)) + { + // IEEE defines that NaN is not equal to anything, including itself. + return false; + } + + // IEEE defines that positive and negative zero are equivalent. + return (m_value == other.m_value) || AreZero(this, other); + } + + public override int GetHashCode() + { + if (IsNaNOrZero(this)) + { + // All NaNs should have the same hash code, as should both Zeros. + return m_value & PositiveInfinityBits; + } + return m_value; + } + + public TypeCode GetTypeCode() + { + return TypeCode.Half; + } + + public override string ToString() + { + return ToString(format: null, formatProvider: null); + } + + public string ToString(string? format) + { + return ((float)this).ToString(format); + } + + public string ToString(IFormatProvider? formatProvider) + { + return ((float)this).ToString(formatProvider); + } + + public string ToString(string? format, IFormatProvider? formatProvider) + { + return ((float)this).ToString(format: null, formatProvider); + + } + + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? formatProvider = null) + { + return ((float)this).TryFormat(destination, out charsWritten, format, formatProvider); + } + + // -----------------------Start of to-half conversions------------------------- + + public static explicit operator Half(float value) + { + const int singleMaxExponent = 0xFF; + + uint floatInt = ToUInt32(value); + bool sign = (floatInt & float.SignMask) >> float.SignShift != 0; + int exp = (int)(floatInt & float.ExponentMask) >> float.ExponentShift; + uint sig = floatInt & float.SignificandMask; + + if (exp == singleMaxExponent) + { + if (sig != 0) // NaN + { + return CreateHalfNaN(sign, (ulong)sig << 41); // Shift the significand bits to the left end + } + return sign ? NegativeInfinity : PositiveInfinity; + } + + uint sigHalf = sig >> 9 | ((sig & 0x1FFU) != 0 ? 1U : 0U); // RightShiftJam + + if ((exp | (int)sigHalf) == 0) + { + return new Half(sign, 0, 0); + } + + return new Half(RoundPackToHalf(sign, (short)(exp - 0x71), (ushort)(sigHalf | 0x4000))); + } + + public static explicit operator Half(double value) + { + const int doubleMaxExponent = 0x7FF; + + ulong doubleInt = ToUInt64(value); + bool sign = (doubleInt & double.SignMask) >> double.SignShift != 0; + int exp = (int)((doubleInt & double.ExponentMask) >> double.ExponentShift); + ulong sig = doubleInt & double.SignificandMask; + + if (exp == doubleMaxExponent) + { + if (sig != 0) // NaN + { + return CreateHalfNaN(sign, sig << 12); // Shift the significand bits to the left end + } + return sign ? NegativeInfinity : PositiveInfinity; + } + + uint sigHalf = (uint)ShiftRightJam(sig, 38); + if ((exp | (int)sigHalf) == 0) + { + return new Half(sign, 0, 0); + } + return new Half(RoundPackToHalf(sign, (short)(exp - 0x3F1), (ushort)(sigHalf | 0x4000))); + } + + // -----------------------Start of from-half conversions------------------------- + public static implicit operator float(Half value) + { + bool sign = IsNegative(value); + int exp = value.Exponent; + uint sig = value.Significand; + + if (exp == MaxExponent) + { + if (sig != 0) + { + return CreateSingleNaN(sign, (ulong)sig << 54); + } + return sign ? float.NegativeInfinity : float.PositiveInfinity; + } + + if (exp == 0) + { + if (sig == 0) + { + return CreateSingle(sign ? float.SignMask : 0); // Positive / Negative zero + } + (exp, sig) = NormSubnormalF16Sig(sig); + exp -= 1; + } + + return CreateSingle(sign, (byte)(exp + 0x70), sig << 13); + } + + public static implicit operator double(Half value) + { + bool sign = IsNegative(value); + int exp = value.Exponent; + uint sig = value.Significand; + + if (exp == MaxExponent) + { + if (sig != 0) + { + return CreateDoubleNaN(sign, (ulong)sig << 54); + } + return sign ? double.NegativeInfinity : double.PositiveInfinity; + } + + if (exp == 0) + { + if (sig == 0) + { + return CreateDouble(sign ? double.SignMask : 0); // Positive / Negative zero + } + (exp, sig) = NormSubnormalF16Sig(sig); + exp -= 1; + } + + return CreateDouble(sign, (ushort)(exp + 0x3F0), (ulong)sig << 42); + } + + private static (int Exp, uint Sig) NormSubnormalF16Sig(uint sig) + { + int shiftDist = BitOperations.LeadingZeroCount(sig) - 16 - 5; // No LZCNT for 16-bit + return (1 - shiftDist, sig << shiftDist); + } + + #region Utilities + internal static uint ToUInt32(float value) + => (uint)BitConverter.SingleToInt32Bits(value); + + // Significand bits should be shifted towards to the left end before calling these methods + // Creates Quiet NaN if significand == 0 + internal static Half CreateHalfNaN(bool sign, ulong significand) + { + const uint NaNBits = ExponentMask | 0x200; // Most significant significand bit + + uint signInt = (sign ? 1U : 0U) << SignShift; + uint sigInt = (uint)(significand >> 54); + + return CreateHalf((ushort)(signInt | NaNBits | sigInt)); + } + + internal static unsafe Half CreateHalf(ushort value) + => *(Half*)&value; + + internal static ushort RoundPackToHalf(bool sign, short exp, ushort sig) + { + const int roundIncrement = 0x8; // Depends on rounding mode but it's always towards closest / ties to even + int roundBits = sig & 0xF; + + if ((uint)exp >= 0x1D) + { + if (exp < 0) + { + sig = (ushort)ShiftRightJam(sig, -exp); + exp = 0; + } + else if (exp > 0x1D || sig + roundIncrement >= 0x8000) // Overflow + { + return sign ? NegativeInfinityBits : PositiveInfinityBits; + } + } + + sig = (ushort)((sig + roundIncrement) >> 4); + sig &= (ushort)~(((roundBits ^ 8) != 0 ? 0 : 1) & 1); + + if (sig == 0) + { + exp = 0; + } + + return new Half(sign, (ushort)exp, sig).m_value; + } + + // If any bits are lost by shifting, "jam" them into the LSB. + // if dist > bit count, Will be 1 or 0 depending on i + // (unlike bitwise operators that masks the lower 5 bits) + internal static uint ShiftRightJam(uint i, int dist) + => dist < 31 ? (i >> dist) | (i << (-dist & 31) != 0 ? 1U : 0U) : (i != 0 ? 1U : 0U); + + internal static ulong ShiftRightJam(ulong l, int dist) + => dist < 63 ? (l >> dist) | (l << (-dist & 63) != 0 ? 1UL : 0UL) : (l != 0 ? 1UL : 0UL); + + internal static ulong ToUInt64(double value) + => (ulong)BitConverter.DoubleToInt64Bits(value); + + internal static float CreateSingleNaN(bool sign, ulong significand) + { + const uint NaNBits = float.ExponentMask | 0x400000; // Most significant significand bit + + uint signInt = (sign ? 1U : 0U) << float.SignShift; + uint sigInt = (uint)(significand >> 41); + + return CreateSingle(signInt | NaNBits | sigInt); + } + + internal static double CreateDoubleNaN(bool sign, ulong significand) + { + const ulong NaNBits = double.ExponentMask | 0x80000_00000000; // Most significant significand bit + + ulong signInt = (sign ? 1UL : 0UL) << double.SignShift; + ulong sigInt = significand >> 12; + + return CreateDouble(signInt | NaNBits | sigInt); + } + + internal static double CreateDouble(ulong value) + => BitConverter.Int64BitsToDouble((long)value); + + internal static float CreateSingle(uint value) + => BitConverter.Int32BitsToSingle((int)value); + + internal static float CreateSingle(bool sign, byte exp, uint sig) + => BitConverter.Int32BitsToSingle((int)(((sign ? 1U : 0U) << float.SignShift) | ((uint)exp << float.ExponentShift) | sig)); + + internal static double CreateDouble(bool sign, ushort exp, ulong sig) + => BitConverter.Int64BitsToDouble((long)(((sign ? 1UL : 0UL) << double.SignShift) | ((ulong)exp << double.ExponentShift) | sig)); + + #endregion + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/TypeCode.cs b/src/libraries/System.Private.CoreLib/src/System/TypeCode.cs index 296198656b77ce..f6a856600d8edf 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TypeCode.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TypeCode.cs @@ -43,5 +43,6 @@ public enum TypeCode Decimal = 15, // Decimal DateTime = 16, // DateTime String = 18, // Unicode character string + Half = 19, // IEEE 16-bit float } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 7fa3dac5f21062..57a68c3641544a 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -2194,6 +2194,54 @@ public partial struct Guid : System.IComparable, System.IComparable public static bool TryParseExact([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? input, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? format, out System.Guid result) { throw null; } public bool TryWriteBytes(System.Span destination) { throw null; } } + public readonly partial struct Half : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable + { + private readonly int _dummyPrimitive; + public static readonly System.Half Epsilon; + public static readonly System.Half MaxValue; + public static readonly System.Half MinValue; + public static readonly System.Half NaN; + public static readonly System.Half NegativeInfinity; + public static readonly System.Half PositiveInfinity; + public int CompareTo(System.Half other) { throw null; } + public int CompareTo(object? obj) { throw null; } + public bool Equals(System.Half other) { throw null; } + public override bool Equals(object? obj) { throw null; } + public override int GetHashCode() { throw null; } + public System.TypeCode GetTypeCode() { throw null; } + public static bool IsFinite(System.Half value) { throw null; } + public static bool IsInfinity(System.Half value) { throw null; } + public static bool IsNaN(System.Half value) { throw null; } + public static bool IsNegative(System.Half value) { throw null; } + public static bool IsNegativeInfinity(System.Half value) { throw null; } + public static bool IsNormal(System.Half value) { throw null; } + public static bool IsPositiveInfinity(System.Half value) { throw null; } + public static bool IsSubnormal(System.Half value) { throw null; } + public static bool operator ==(System.Half left, System.Half right) { throw null; } + public static explicit operator System.Half (double value) { throw null; } + public static explicit operator System.Half (float value) { throw null; } + public static bool operator >(System.Half left, System.Half right) { throw null; } + public static bool operator >=(System.Half left, System.Half right) { throw null; } + public static implicit operator double (System.Half value) { throw null; } + public static implicit operator float (System.Half value) { throw null; } + public static bool operator !=(System.Half left, System.Half right) { throw null; } + public static bool operator <(System.Half left, System.Half right) { throw null; } + public static bool operator <=(System.Half left, System.Half right) { throw null; } + public static System.Half Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } + public static System.Half Parse(string s) { throw null; } + public static System.Half Parse(string s, System.Globalization.NumberStyles style) { throw null; } + public static System.Half Parse(string s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } + public static System.Half Parse(string s, System.IFormatProvider provider) { throw null; } + public override string ToString() { throw null; } + public string ToString(System.IFormatProvider? formatProvider) { throw null; } + public string ToString(string? format) { throw null; } + public string ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } + public bool TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? formatProvider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Half result) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, out System.Half result) { throw null; } + public static bool TryParse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Half result) { throw null; } + public static bool TryParse(string s, out System.Half result) { throw null; } + } public partial struct HashCode { private int _dummyPrimitive; @@ -4306,6 +4354,7 @@ public enum TypeCode Decimal = 15, DateTime = 16, String = 18, + Half = 19, } [System.CLSCompliantAttribute(false)] public ref partial struct TypedReference diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index 69f4de93591a8a..8943d858f94dec 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -80,6 +80,7 @@ + diff --git a/src/libraries/System.Runtime/tests/System/HalfTests.cs b/src/libraries/System.Runtime/tests/System/HalfTests.cs new file mode 100644 index 00000000000000..91cbc62a03e864 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/HalfTests.cs @@ -0,0 +1,939 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.CompilerServices; +using Xunit; + +namespace System.Tests +{ + public partial class HalfTests + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe ushort HalfToUInt16Bits(Half value) + { + return *((ushort*)&value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe Half UInt16BitsToHalf(ushort value) + { + return *((Half*)&value); + } + + [Fact] + public static void Epsilon() + { + Assert.Equal(0x0001u, HalfToUInt16Bits(Half.Epsilon)); + } + + [Fact] + public static void PositiveInfinity() + { + Assert.Equal(0x7C00u, HalfToUInt16Bits(Half.PositiveInfinity)); + } + + [Fact] + public static void NegativeInfinity() + { + Assert.Equal(0xFC00u, HalfToUInt16Bits(Half.NegativeInfinity)); + } + + [Fact] + public static void NaN() + { + Assert.Equal(0xFE00u, HalfToUInt16Bits(Half.NaN)); + } + + [Fact] + public static void MinValue() + { + Assert.Equal(0xFBFFu, HalfToUInt16Bits(Half.MinValue)); + } + + [Fact] + public static void MaxValue() + { + Assert.Equal(0x7BFFu, HalfToUInt16Bits(Half.MaxValue)); + } + + [Fact] + public static void Ctor_Empty() + { + var value = new Half(); + Assert.Equal(0x0000, HalfToUInt16Bits(value)); + } + + public static IEnumerable IsFinite_TestData() + { + yield return new object[] { Half.NegativeInfinity, false }; // Negative Infinity + yield return new object[] { Half.MinValue, true }; // Min Negative Normal + yield return new object[] { UInt16BitsToHalf(0x8400), true }; // Max Negative Normal + yield return new object[] { UInt16BitsToHalf(0x83FF), true }; // Min Negative Subnormal + yield return new object[] { UInt16BitsToHalf(0x8001), true }; // Max Negative Subnormal + yield return new object[] { UInt16BitsToHalf(0x8000), true }; // Negative Zero + yield return new object[] { Half.NaN, false }; // NaN + yield return new object[] { UInt16BitsToHalf(0x0000), true }; // Positive Zero + yield return new object[] { Half.Epsilon, true }; // Min Positive Subnormal + yield return new object[] { UInt16BitsToHalf(0x03FF), true }; // Max Positive Subnormal + yield return new object[] { UInt16BitsToHalf(0x0400), true }; // Min Positive Normal + yield return new object[] { Half.MaxValue, true }; // Max Positive Normal + yield return new object[] { Half.PositiveInfinity, false }; // Positive Infinity + } + + [Theory] + [MemberData(nameof(IsFinite_TestData))] + public static void IsFinite(Half value, bool expected) + { + Assert.Equal(expected, Half.IsFinite(value)); + } + + public static IEnumerable IsInfinity_TestData() + { + yield return new object[] { Half.NegativeInfinity, true }; // Negative Infinity + yield return new object[] { Half.MinValue, false }; // Min Negative Normal + yield return new object[] { UInt16BitsToHalf(0x8400), false }; // Max Negative Normal + yield return new object[] { UInt16BitsToHalf(0x83FF), false }; // Min Negative Subnormal + yield return new object[] { UInt16BitsToHalf(0x8001), false }; // Max Negative Subnormal (Negative Epsilon) + yield return new object[] { UInt16BitsToHalf(0x8000), false }; // Negative Zero + yield return new object[] { Half.NaN, false }; // NaN + yield return new object[] { UInt16BitsToHalf(0x0000), false }; // Positive Zero + yield return new object[] { Half.Epsilon, false }; // Min Positive Subnormal (Positive Epsilon) + yield return new object[] { UInt16BitsToHalf(0x03FF), false }; // Max Positive Subnormal + yield return new object[] { UInt16BitsToHalf(0x0400), false }; // Min Positive Normal + yield return new object[] { Half.MaxValue, false }; // Max Positive Normal + yield return new object[] { Half.PositiveInfinity, true }; // Positive Infinity + } + + [Theory] + [MemberData(nameof(IsInfinity_TestData))] + public static void IsInfinity(Half value, bool expected) + { + Assert.Equal(expected, Half.IsInfinity(value)); + } + + public static IEnumerable IsNaN_TestData() + { + yield return new object[] { Half.NegativeInfinity, false }; // Negative Infinity + yield return new object[] { Half.MinValue, false }; // Min Negative Normal + yield return new object[] { UInt16BitsToHalf(0x8400), false }; // Max Negative Normal + yield return new object[] { UInt16BitsToHalf(0x83FF), false }; // Min Negative Subnormal + yield return new object[] { UInt16BitsToHalf(0x8001), false }; // Max Negative Subnormal (Negative Epsilon) + yield return new object[] { UInt16BitsToHalf(0x8000), false }; // Negative Zero + yield return new object[] { Half.NaN, true }; // NaN + yield return new object[] { UInt16BitsToHalf(0x0000), false }; // Positive Zero + yield return new object[] { Half.Epsilon, false }; // Min Positive Subnormal (Positive Epsilon) + yield return new object[] { UInt16BitsToHalf(0x03FF), false }; // Max Positive Subnormal + yield return new object[] { UInt16BitsToHalf(0x0400), false }; // Min Positive Normal + yield return new object[] { Half.MaxValue, false }; // Max Positive Normal + yield return new object[] { Half.PositiveInfinity, false }; // Positive Infinity + } + + [Theory] + [MemberData(nameof(IsNaN_TestData))] + public static void IsNaN(Half value, bool expected) + { + Assert.Equal(expected, Half.IsNaN(value)); + } + + public static IEnumerable IsNegative_TestData() + { + yield return new object[] { Half.NegativeInfinity, true }; // Negative Infinity + yield return new object[] { Half.MinValue, true }; // Min Negative Normal + yield return new object[] { UInt16BitsToHalf(0x8400), true }; // Max Negative Normal + yield return new object[] { UInt16BitsToHalf(0x83FF), true }; // Min Negative Subnormal + yield return new object[] { UInt16BitsToHalf(0x8001), true }; // Max Negative Subnormal + yield return new object[] { UInt16BitsToHalf(0x8000), true }; // Negative Zero + yield return new object[] { Half.NaN, true }; // NaN + yield return new object[] { UInt16BitsToHalf(0x0000), false }; // Positive Zero + yield return new object[] { Half.Epsilon, false }; // Min Positive Subnormal + yield return new object[] { UInt16BitsToHalf(0x03FF), false }; // Max Positive Subnormal + yield return new object[] { UInt16BitsToHalf(0x0400), false }; // Min Positive Normal + yield return new object[] { Half.MaxValue, false }; // Max Positive Normal + yield return new object[] { Half.PositiveInfinity, false }; // Positive Infinity + } + + [Theory] + [MemberData(nameof(IsNegative_TestData))] + public static void IsNegative(Half value, bool expected) + { + Assert.Equal(expected, Half.IsNegative(value)); + } + + public static IEnumerable IsNegativeInfinity_TestData() + { + yield return new object[] { Half.NegativeInfinity, true }; // Negative Infinity + yield return new object[] { Half.MinValue, false }; // Min Negative Normal + yield return new object[] { UInt16BitsToHalf(0x8400), false }; // Max Negative Normal + yield return new object[] { UInt16BitsToHalf(0x83FF), false }; // Min Negative Subnormal + yield return new object[] { UInt16BitsToHalf(0x8001), false }; // Max Negative Subnormal (Negative Epsilon) + yield return new object[] { UInt16BitsToHalf(0x8000), false }; // Negative Zero + yield return new object[] { Half.NaN, false }; // NaN + yield return new object[] { UInt16BitsToHalf(0x0000), false }; // Positive Zero + yield return new object[] { Half.Epsilon, false }; // Min Positive Subnormal (Positive Epsilon) + yield return new object[] { UInt16BitsToHalf(0x03FF), false }; // Max Positive Subnormal + yield return new object[] { UInt16BitsToHalf(0x0400), false }; // Min Positive Normal + yield return new object[] { Half.MaxValue, false }; // Max Positive Normal + yield return new object[] { Half.PositiveInfinity, false }; // Positive Infinity + } + + [Theory] + [MemberData(nameof(IsNegativeInfinity_TestData))] + public static void IsNegativeInfinity(Half value, bool expected) + { + Assert.Equal(expected, Half.IsNegativeInfinity(value)); + } + + public static IEnumerable IsNormal_TestData() + { + yield return new object[] { Half.NegativeInfinity, false }; // Negative Infinity + yield return new object[] { Half.MinValue, true }; // Min Negative Normal + yield return new object[] { UInt16BitsToHalf(0x8400), true }; // Max Negative Normal + yield return new object[] { UInt16BitsToHalf(0x83FF), false }; // Min Negative Subnormal + yield return new object[] { UInt16BitsToHalf(0x8001), false }; // Max Negative Subnormal + yield return new object[] { UInt16BitsToHalf(0x8000), false }; // Negative Zero + yield return new object[] { Half.NaN, false }; // NaN + yield return new object[] { UInt16BitsToHalf(0x0000), false }; // Positive Zero + yield return new object[] { Half.Epsilon, false }; // Min Positive Subnormal + yield return new object[] { UInt16BitsToHalf(0x03FF), false }; // Max Positive Subnormal + yield return new object[] { UInt16BitsToHalf(0x0400), true }; // Min Positive Normal + yield return new object[] { Half.MaxValue, true }; // Max Positive Normal + yield return new object[] { Half.PositiveInfinity, false }; // Positive Infinity + } + + [Theory] + [MemberData(nameof(IsNormal_TestData))] + public static void IsNormal(Half value, bool expected) + { + Assert.Equal(expected, Half.IsNormal(value)); + } + + public static IEnumerable IsPositiveInfinity_TestData() + { + yield return new object[] { Half.NegativeInfinity, false }; // Negative Infinity + yield return new object[] { Half.MinValue, false }; // Min Negative Normal + yield return new object[] { UInt16BitsToHalf(0x8400), false }; // Max Negative Normal + yield return new object[] { UInt16BitsToHalf(0x83FF), false }; // Min Negative Subnormal + yield return new object[] { UInt16BitsToHalf(0x8001), false }; // Max Negative Subnormal (Negative Epsilon) + yield return new object[] { UInt16BitsToHalf(0x8000), false }; // Negative Zero + yield return new object[] { Half.NaN, false }; // NaN + yield return new object[] { UInt16BitsToHalf(0x0000), false }; // Positive Zero + yield return new object[] { Half.Epsilon, false }; // Min Positive Subnormal (Positive Epsilon) + yield return new object[] { UInt16BitsToHalf(0x03FF), false }; // Max Positive Subnormal + yield return new object[] { UInt16BitsToHalf(0x0400), false }; // Min Positive Normal + yield return new object[] { Half.MaxValue, false }; // Max Positive Normal + yield return new object[] { Half.PositiveInfinity, true }; // Positive Infinity + } + + [Theory] + [MemberData(nameof(IsPositiveInfinity_TestData))] + public static void IsPositiveInfinity(Half value, bool expected) + { + Assert.Equal(expected, Half.IsPositiveInfinity(value)); + } + + public static IEnumerable IsSubnormal_TestData() + { + yield return new object[] { Half.NegativeInfinity, false }; // Negative Infinity + yield return new object[] { Half.MinValue, false }; // Min Negative Normal + yield return new object[] { UInt16BitsToHalf(0x8400), false }; // Max Negative Normal + yield return new object[] { UInt16BitsToHalf(0x83FF), true }; // Min Negative Subnormal + yield return new object[] { UInt16BitsToHalf(0x8001), true }; // Max Negative Subnormal + yield return new object[] { UInt16BitsToHalf(0x8000), false }; // Negative Zero + yield return new object[] { Half.NaN, false }; // NaN + yield return new object[] { UInt16BitsToHalf(0x0000), false }; // Positive Zero + yield return new object[] { Half.Epsilon, true }; // Min Positive Subnormal + yield return new object[] { UInt16BitsToHalf(0x03FF), true }; // Max Positive Subnormal + yield return new object[] { UInt16BitsToHalf(0x0400), false }; // Min Positive Normal + yield return new object[] { Half.MaxValue, false }; // Max Positive Normal + yield return new object[] { Half.PositiveInfinity, false }; // Positive Infinity + } + + [Theory] + [MemberData(nameof(IsSubnormal_TestData))] + public static void IsSubnormal(Half value, bool expected) + { + Assert.Equal(expected, Half.IsSubnormal(value)); + } + + public static IEnumerable CompareTo_ThrowsArgumentException_TestData() + { + yield return new object[] { "a" }; + yield return new object[] { 234.0 }; + } + + [Theory] + [MemberData(nameof(CompareTo_ThrowsArgumentException_TestData))] + public static void CompareTo_ThrowsArgumentException(object obj) + { + Assert.Throws(typeof(ArgumentException), () => Half.MaxValue.CompareTo(obj)); + } + + public static IEnumerable CompareTo_TestData() + { + yield return new object[] { Half.MaxValue, Half.MaxValue, 0 }; + yield return new object[] { Half.MaxValue, Half.MinValue, 1 }; + yield return new object[] { Half.Epsilon, UInt16BitsToHalf(0x8001), 1 }; + yield return new object[] { Half.MaxValue, UInt16BitsToHalf(0x0000), 1 }; + yield return new object[] { Half.MaxValue, Half.Epsilon, 1 }; + yield return new object[] { Half.MaxValue, Half.PositiveInfinity, -1 }; + yield return new object[] { Half.MinValue, Half.MaxValue, -1 }; + yield return new object[] { Half.MaxValue, Half.NaN, 1 }; + yield return new object[] { Half.NaN, Half.NaN, 0 }; + yield return new object[] { Half.NaN, UInt16BitsToHalf(0x0000), -1 }; + yield return new object[] { Half.MaxValue, null, 1 }; + } + + [Theory] + [MemberData(nameof(CompareTo_TestData))] + public static void CompareTo(Half value, object obj, int expected) + { + if (obj is Half other) + { + Assert.Equal(expected, Math.Sign(value.CompareTo(other))); + + if (Half.IsNaN(value) || Half.IsNaN(other)) + { + Assert.False(value >= other); + Assert.False(value > other); + Assert.False(value <= other); + Assert.False(value < other); + } + else + { + if (expected >= 0) + { + Assert.True(value >= other); + Assert.False(value < other); + } + if (expected > 0) + { + Assert.True(value > other); + Assert.False(value <= other); + } + if (expected <= 0) + { + Assert.True(value <= other); + Assert.False(value > other); + } + if (expected < 0) + { + Assert.True(value < other); + Assert.False(value >= other); + } + } + } + + Assert.Equal(expected, Math.Sign(value.CompareTo(obj))); + } + + public static IEnumerable Equals_TestData() + { + yield return new object[] { Half.MaxValue, Half.MaxValue, true }; + yield return new object[] { Half.MaxValue, Half.MinValue, false }; + yield return new object[] { Half.MaxValue, UInt16BitsToHalf(0x0000), false }; + yield return new object[] { Half.NaN, Half.NaN, false }; + yield return new object[] { Half.MaxValue, 789.0f, false }; + yield return new object[] { Half.MaxValue, "789", false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public static void Equals(Half value, object obj, bool expected) + { + Assert.Equal(expected, value.Equals(obj)); + } + + public static IEnumerable ExplicitConversion_ToSingle_TestData() + { + (Half Original, float Expected)[] data = // Fraction is shifted left by 42, Exponent is -15 then +127 = +112 + { + (UInt16BitsToHalf(0b0_01111_0000000000), 1f), // 1 + (UInt16BitsToHalf(0b1_01111_0000000000), -1f), // -1 + (Half.MaxValue, 65504f), // 65504 + (Half.MinValue, -65504f), // -65504 + (UInt16BitsToHalf(0b0_01011_1001100110), 0.0999755859375f), // 0.1ish + (UInt16BitsToHalf(0b1_01011_1001100110), -0.0999755859375f), // -0.1ish + (UInt16BitsToHalf(0b0_10100_0101000000), 42f), // 42 + (UInt16BitsToHalf(0b1_10100_0101000000), -42f), // -42 + (Half.PositiveInfinity, float.PositiveInfinity), // PosInfinity + (Half.NegativeInfinity, float.NegativeInfinity), // NegInfinity + (UInt16BitsToHalf(0b0_11111_1000000000), BitConverter.Int32BitsToSingle(0x7FC00000)), // Positive Quiet NaN + (Half.NaN, float.NaN), // Negative Quiet NaN + (UInt16BitsToHalf(0b0_11111_1010101010), BitConverter.Int32BitsToSingle(0x7FD54000)), // Positive Signalling NaN - Should preserve payload + (UInt16BitsToHalf(0b1_11111_1010101010), BitConverter.Int32BitsToSingle(unchecked((int)0xFFD54000))), // Negative Signalling NaN - Should preserve payload + (Half.Epsilon, 1/16777216f), // PosEpsilon = 0.000000059605... + (-Half.Epsilon, -1/16777216f), // NegEpsilon = 0.000000059605... + (UInt16BitsToHalf(0), 0), // 0 + (UInt16BitsToHalf(0b1_00000_0000000000), -0f), // -0 + (UInt16BitsToHalf(0b0_10000_1001001000), 3.140625f), // 3.140625 + (UInt16BitsToHalf(0b1_10000_1001001000), -3.140625f), // -3.140625 + (UInt16BitsToHalf(0b0_10000_0101110000), 2.71875f), // 2.71875 + (UInt16BitsToHalf(0b1_10000_0101110000), -2.71875f), // -2.71875 + (UInt16BitsToHalf(0b0_01111_1000000000), 1.5f), // 1.5 + (UInt16BitsToHalf(0b1_01111_1000000000), -1.5f), // -1.5 + (UInt16BitsToHalf(0b0_01111_1000000001), 1.5009765625f), // 1.5009765625 + (UInt16BitsToHalf(0b1_01111_1000000001), -1.5009765625f), // -1.5009765625 + }; + + foreach ((Half original, float expected) in data) + { + yield return new object[] { original, expected }; + } + } + + [MemberData(nameof(ExplicitConversion_ToSingle_TestData))] + [Theory] + public static void ExplicitConversion_ToSingle(Half value, float expected) // Check the underlying bits for verifying NaNs + { + float f = (float)value; + Assert.Equal(BitConverter.SingleToInt32Bits(expected), BitConverter.SingleToInt32Bits(f)); + } + + public static IEnumerable ExplicitConversion_ToDouble_TestData() + { + (Half Original, double Expected)[] data = // Fraction is shifted left by 42, Exponent is -15 then +127 = +112 + { + (UInt16BitsToHalf(0b0_01111_0000000000), 1d), // 1 + (UInt16BitsToHalf(0b1_01111_0000000000), -1d), // -1 + (Half.MaxValue, 65504d), // 65504 + (Half.MinValue, -65504d), // -65504 + (UInt16BitsToHalf(0b0_01011_1001100110), 0.0999755859375d), // 0.1ish + (UInt16BitsToHalf(0b1_01011_1001100110), -0.0999755859375d), // -0.1ish + (UInt16BitsToHalf(0b0_10100_0101000000), 42d), // 42 + (UInt16BitsToHalf(0b1_10100_0101000000), -42d), // -42 + (Half.PositiveInfinity, double.PositiveInfinity), // PosInfinity + (Half.NegativeInfinity, double.NegativeInfinity), // NegInfinity + (UInt16BitsToHalf(0b0_11111_1000000000), BitConverter.Int64BitsToDouble(0x7FF80000_00000000)), // Positive Quiet NaN + (Half.NaN, double.NaN), // Negative Quiet NaN + (UInt16BitsToHalf(0b0_11111_1010101010), BitConverter.Int64BitsToDouble(0x7FFAA800_00000000)), // Positive Signalling NaN - Should preserve payload + (UInt16BitsToHalf(0b1_11111_1010101010), BitConverter.Int64BitsToDouble(unchecked((long)0xFFFAA800_00000000))), // Negative Signalling NaN - Should preserve payload + (Half.Epsilon, 1/16777216d), // PosEpsilon = 0.000000059605... + (-Half.Epsilon, -1/16777216d), // NegEpsilon = 0.000000059605... + (UInt16BitsToHalf(0), 0d), // 0 + (UInt16BitsToHalf(0b1_00000_0000000000), -0d), // -0 + (UInt16BitsToHalf(0b0_10000_1001001000), 3.140625d), // 3.140625 + (UInt16BitsToHalf(0b1_10000_1001001000), -3.140625d), // -3.140625 + (UInt16BitsToHalf(0b0_10000_0101110000), 2.71875d), // 2.71875 + (UInt16BitsToHalf(0b1_10000_0101110000), -2.71875d), // -2.71875 + (UInt16BitsToHalf(0b0_01111_1000000000), 1.5d), // 1.5 + (UInt16BitsToHalf(0b1_01111_1000000000), -1.5d), // -1.5 + (UInt16BitsToHalf(0b0_01111_1000000001), 1.5009765625d), // 1.5009765625 + (UInt16BitsToHalf(0b1_01111_1000000001), -1.5009765625d) // -1.5009765625 + }; + + foreach ((Half original, double expected) in data) + { + yield return new object[] { original, expected }; + } + } + + [MemberData(nameof(ExplicitConversion_ToDouble_TestData))] + [Theory] + public static void ExplicitConversion_ToDouble(Half value, double expected) // Check the underlying bits for verifying NaNs + { + double d = (double)value; + Assert.Equal(BitConverter.DoubleToInt64Bits(expected), BitConverter.DoubleToInt64Bits(d)); + } + + // ---------- Start of To-half conversion tests ---------- + public static IEnumerable ExplicitConversion_FromSingle_TestData() + { + (float, Half)[] data = + { + (MathF.PI, UInt16BitsToHalf(0b0_10000_1001001000)), // 3.140625 + (MathF.E, UInt16BitsToHalf(0b0_10000_0101110000)), // 2.71875 + (-MathF.PI, UInt16BitsToHalf(0b1_10000_1001001000)), // -3.140625 + (-MathF.E, UInt16BitsToHalf(0b1_10000_0101110000)), // -2.71875 + (float.MaxValue, Half.PositiveInfinity), // Overflow + (float.MinValue, Half.NegativeInfinity), // Overflow + (float.PositiveInfinity, Half.PositiveInfinity), // Overflow + (float.NegativeInfinity, Half.NegativeInfinity), // Overflow + (float.NaN, Half.NaN), // Quiet Negative NaN + (BitConverter.Int32BitsToSingle(0x7FC00000), UInt16BitsToHalf(0b0_11111_1000000000)), // Quiet Positive NaN + (BitConverter.Int32BitsToSingle(unchecked((int)0xFFD55555)), + UInt16BitsToHalf(0b1_11111_1010101010)), // Signalling Negative NaN + (BitConverter.Int32BitsToSingle(0x7FD55555), UInt16BitsToHalf(0b0_11111_1010101010)), // Signalling Positive NaN + (float.Epsilon, UInt16BitsToHalf(0)), // Underflow + (-float.Epsilon, UInt16BitsToHalf(0b1_00000_0000000000)), // Underflow + (1f, UInt16BitsToHalf(0b0_01111_0000000000)), // 1 + (-1f, UInt16BitsToHalf(0b1_01111_0000000000)), // -1 + (0f, UInt16BitsToHalf(0)), // 0 + (-0f, UInt16BitsToHalf(0b1_00000_0000000000)), // -0 + (42f, UInt16BitsToHalf(0b0_10100_0101000000)), // 42 + (-42f, UInt16BitsToHalf(0b1_10100_0101000000)), // -42 + (0.1f, UInt16BitsToHalf(0b0_01011_1001100110)), // 0.0999755859375 + (-0.1f, UInt16BitsToHalf(0b1_01011_1001100110)), // -0.0999755859375 + (1.5f, UInt16BitsToHalf(0b0_01111_1000000000)), // 1.5 + (-1.5f, UInt16BitsToHalf(0b1_01111_1000000000)), // -1.5 + (1.5009765625f, UInt16BitsToHalf(0b0_01111_1000000001)), // 1.5009765625 + (-1.5009765625f, UInt16BitsToHalf(0b1_01111_1000000001)), // -1.5009765625 + }; + + foreach ((float original, Half expected) in data) + { + yield return new object[] { original, expected }; + } + } + + [MemberData(nameof(ExplicitConversion_FromSingle_TestData))] + [Theory] + public static void ExplicitConversion_FromSingle(float f, Half expected) // Check the underlying bits for verifying NaNs + { + Half h = (Half)f; + Assert.Equal(HalfToUInt16Bits(expected), HalfToUInt16Bits(h)); + } + + public static IEnumerable ExplicitConversion_FromDouble_TestData() + { + (double, Half)[] data = + { + (Math.PI, UInt16BitsToHalf(0b0_10000_1001001000)), // 3.140625 + (Math.E, UInt16BitsToHalf(0b0_10000_0101110000)), // 2.71875 + (-Math.PI, UInt16BitsToHalf(0b1_10000_1001001000)), // -3.140625 + (-Math.E, UInt16BitsToHalf(0b1_10000_0101110000)), // -2.71875 + (double.MaxValue, Half.PositiveInfinity), // Overflow + (double.MinValue, Half.NegativeInfinity), // Overflow + (double.PositiveInfinity, Half.PositiveInfinity), // Overflow + (double.NegativeInfinity, Half.NegativeInfinity), // Overflow + (double.NaN, Half.NaN), // Quiet Negative NaN + (BitConverter.Int64BitsToDouble(0x7FF80000_00000000), + UInt16BitsToHalf(0b0_11111_1000000000)), // Quiet Positive NaN + (BitConverter.Int64BitsToDouble(unchecked((long)0xFFFAAAAA_AAAAAAAA)), + UInt16BitsToHalf(0b1_11111_1010101010)), // Signalling Negative NaN + (BitConverter.Int64BitsToDouble(0x7FFAAAAA_AAAAAAAA), + UInt16BitsToHalf(0b0_11111_1010101010)), // Signalling Positive NaN + (double.Epsilon, UInt16BitsToHalf(0)), // Underflow + (-double.Epsilon, UInt16BitsToHalf(0b1_00000_0000000000)), // Underflow + (1d, UInt16BitsToHalf(0b0_01111_0000000000)), // 1 + (-1d, UInt16BitsToHalf(0b1_01111_0000000000)), // -1 + (0d, UInt16BitsToHalf(0)), // 0 + (-0d, UInt16BitsToHalf(0b1_00000_0000000000)), // -0 + (42d, UInt16BitsToHalf(0b0_10100_0101000000)), // 42 + (-42d, UInt16BitsToHalf(0b1_10100_0101000000)), // -42 + (0.1d, UInt16BitsToHalf(0b0_01011_1001100110)), // 0.0999755859375 + (-0.1d, UInt16BitsToHalf(0b1_01011_1001100110)), // -0.0999755859375 + (1.5d, UInt16BitsToHalf(0b0_01111_1000000000)), // 1.5 + (-1.5d, UInt16BitsToHalf(0b1_01111_1000000000)), // -1.5 + (1.5009765625d, UInt16BitsToHalf(0b0_01111_1000000001)), // 1.5009765625 + (-1.5009765625d, UInt16BitsToHalf(0b1_01111_1000000001)), // -1.5009765625 + }; + + foreach ((double original, Half expected) in data) + { + yield return new object[] { original, expected }; + } + } + + [MemberData(nameof(ExplicitConversion_FromDouble_TestData))] + [Theory] + public static void ExplicitConversion_FromDouble(double d, Half expected) // Check the underlying bits for verifying NaNs + { + Half h = (Half)d; + Assert.Equal(HalfToUInt16Bits(expected), HalfToUInt16Bits(h)); + } + + public static IEnumerable Parse_Valid_TestData() + { + NumberStyles defaultStyle = NumberStyles.Float | NumberStyles.AllowThousands; + + NumberFormatInfo emptyFormat = NumberFormatInfo.CurrentInfo; + + var dollarSignCommaSeparatorFormat = new NumberFormatInfo() + { + CurrencySymbol = "$", + CurrencyGroupSeparator = "," + }; + + var decimalSeparatorFormat = new NumberFormatInfo() + { + NumberDecimalSeparator = "." + }; + + NumberFormatInfo invariantFormat = NumberFormatInfo.InvariantInfo; + + yield return new object[] { "-123", defaultStyle, null, -123.0f }; + yield return new object[] { "0", defaultStyle, null, 0.0f }; + yield return new object[] { "123", defaultStyle, null, 123.0f }; + yield return new object[] { " 123 ", defaultStyle, null, 123.0f }; + yield return new object[] { (567.89f).ToString(), defaultStyle, null, 567.89f }; + yield return new object[] { (-567.89f).ToString(), defaultStyle, null, -567.89f }; + yield return new object[] { "1E23", defaultStyle, null, 1E23f }; + + yield return new object[] { (123.1f).ToString(), NumberStyles.AllowDecimalPoint, null, 123.1f }; + yield return new object[] { (1000.0f).ToString("N0"), NumberStyles.AllowThousands, null, 1000.0f }; + + yield return new object[] { "123", NumberStyles.Any, emptyFormat, 123.0f }; + yield return new object[] { (123.567f).ToString(), NumberStyles.Any, emptyFormat, 123.567f }; + yield return new object[] { "123", NumberStyles.Float, emptyFormat, 123.0f }; + yield return new object[] { "$1,000", NumberStyles.Currency, dollarSignCommaSeparatorFormat, 1000.0f }; + yield return new object[] { "$1000", NumberStyles.Currency, dollarSignCommaSeparatorFormat, 1000.0f }; + yield return new object[] { "123.123", NumberStyles.Float, decimalSeparatorFormat, 123.123f }; + yield return new object[] { "(123)", NumberStyles.AllowParentheses, decimalSeparatorFormat, -123.0f }; + + yield return new object[] { "NaN", NumberStyles.Any, invariantFormat, float.NaN }; + yield return new object[] { "Infinity", NumberStyles.Any, invariantFormat, float.PositiveInfinity }; + yield return new object[] { "-Infinity", NumberStyles.Any, invariantFormat, float.NegativeInfinity }; + } + + [Theory] + [MemberData(nameof(Parse_Valid_TestData))] + public static void Parse(string value, NumberStyles style, IFormatProvider provider, float expectedFloat) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Half result; + Half expected = (Half)expectedFloat; + if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Half.TryParse(value, out result)); + Assert.True(expected.Equals(result)); + + Assert.Equal(expected, Half.Parse(value)); + } + + Assert.True(expected.Equals(Half.Parse(value, provider: provider))); + } + + // Use Parse(string, NumberStyles, IFormatProvider) + Assert.True(Half.TryParse(value, style, provider, out result)); + Assert.True(expected.Equals(result) || (Half.IsNaN(expected) && Half.IsNaN(result))); + + Assert.True(expected.Equals(Half.Parse(value, style, provider)) || (Half.IsNaN(expected) && Half.IsNaN(result))); + + if (isDefaultProvider) + { + // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider) + Assert.True(Half.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.True(expected.Equals(result)); + + Assert.True(expected.Equals(Half.Parse(value, style))); + Assert.True(expected.Equals(Half.Parse(value, style, NumberFormatInfo.CurrentInfo))); + } + } + + public static IEnumerable Parse_Invalid_TestData() + { + NumberStyles defaultStyle = NumberStyles.Float; + + var dollarSignDecimalSeparatorFormat = new NumberFormatInfo(); + dollarSignDecimalSeparatorFormat.CurrencySymbol = "$"; + dollarSignDecimalSeparatorFormat.NumberDecimalSeparator = "."; + + yield return new object[] { null, defaultStyle, null, typeof(ArgumentNullException) }; + yield return new object[] { "", defaultStyle, null, typeof(FormatException) }; + yield return new object[] { " ", defaultStyle, null, typeof(FormatException) }; + yield return new object[] { "Garbage", defaultStyle, null, typeof(FormatException) }; + + yield return new object[] { "ab", defaultStyle, null, typeof(FormatException) }; // Hex value + yield return new object[] { "(123)", defaultStyle, null, typeof(FormatException) }; // Parentheses + yield return new object[] { (100.0f).ToString("C0"), defaultStyle, null, typeof(FormatException) }; // Currency + + yield return new object[] { (123.456f).ToString(), NumberStyles.Integer, null, typeof(FormatException) }; // Decimal + yield return new object[] { " " + (123.456f).ToString(), NumberStyles.None, null, typeof(FormatException) }; // Leading space + yield return new object[] { (123.456f).ToString() + " ", NumberStyles.None, null, typeof(FormatException) }; // Leading space + yield return new object[] { "1E23", NumberStyles.None, null, typeof(FormatException) }; // Exponent + + yield return new object[] { "ab", NumberStyles.None, null, typeof(FormatException) }; // Negative hex value + yield return new object[] { " 123 ", NumberStyles.None, null, typeof(FormatException) }; // Trailing and leading whitespace + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Half result; + if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None && (style & NumberStyles.AllowLeadingWhite) == (style & NumberStyles.AllowTrailingWhite)) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.False(Half.TryParse(value, out result)); + Assert.Equal(default(Half), result); + + Assert.Throws(exceptionType, () => Half.Parse(value)); + } + + Assert.Throws(exceptionType, () => Half.Parse(value, provider: provider)); + } + + // Use Parse(string, NumberStyles, IFormatProvider) + Assert.False(Half.TryParse(value, style, provider, out result)); + Assert.Equal(default(Half), result); + + Assert.Throws(exceptionType, () => Half.Parse(value, style, provider)); + + if (isDefaultProvider) + { + // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider) + Assert.False(Half.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.Equal(default(Half), result); + + Assert.Throws(exceptionType, () => Half.Parse(value, style)); + Assert.Throws(exceptionType, () => Half.Parse(value, style, NumberFormatInfo.CurrentInfo)); + } + } + + public static IEnumerable ToString_TestData() + { + yield return new object[] { -4568.0f, "G", null, "-4568" }; + yield return new object[] { 0.0f, "G", null, "0" }; + yield return new object[] { 4568.0f, "G", null, "4568" }; + + yield return new object[] { float.NaN, "G", null, "NaN" }; + + //yield return new object[] { 2468.0f, "N", null, "2,468.00" }; + + // Changing the negative pattern doesn't do anything without also passing in a format string + var customNegativePattern = new NumberFormatInfo() { NumberNegativePattern = 0 }; + yield return new object[] { -6312.0f, "G", customNegativePattern, "-6312" }; + + var customNegativeSignDecimalGroupSeparator = new NumberFormatInfo() + { + NegativeSign = "#", + NumberDecimalSeparator = "~", + NumberGroupSeparator = "*" + }; + //yield return new object[] { -2468.0f, "N", customNegativeSignDecimalGroupSeparator, "#2*468~00" }; + //yield return new object[] { 2468.0f, "N", customNegativeSignDecimalGroupSeparator, "2*468~00" }; + + var customNegativeSignGroupSeparatorNegativePattern = new NumberFormatInfo() + { + NegativeSign = "xx", // Set to trash to make sure it doesn't show up + NumberGroupSeparator = "*", + NumberNegativePattern = 0 + }; + //yield return new object[] { -2468.0f, "N", customNegativeSignGroupSeparatorNegativePattern, "(2*468.00)" }; + + NumberFormatInfo invariantFormat = NumberFormatInfo.InvariantInfo; + yield return new object[] { float.NaN, "G", invariantFormat, "NaN" }; + yield return new object[] { float.PositiveInfinity, "G", invariantFormat, "Infinity" }; + yield return new object[] { float.NegativeInfinity, "G", invariantFormat, "-Infinity" }; + } + + public static IEnumerable ToString_TestData_NotNetFramework() + { + foreach (var testData in ToString_TestData()) + { + yield return testData; + } + + yield return new object[] { Half.MinValue, "G", null, "-65504" }; + yield return new object[] { Half.MaxValue, "G", null, "65504" }; + + //yield return new object[] { Half.Epsilon, "G", null, "5.9604645E-08" }; + + NumberFormatInfo invariantFormat = NumberFormatInfo.InvariantInfo; + //yield return new object[] { Half.Epsilon, "G", invariantFormat, "5.9604645E-08" }; + } + + [Fact] + public static void Test_ToString_NotNetFramework() + { + //using (new ThreadCultureChange(CultureInfo.InvariantCulture)) + { + foreach (object[] testdata in ToString_TestData_NotNetFramework()) + { + ToStringTest(testdata[0] is float floatData ? (Half)floatData : (Half)testdata[0], (string)testdata[1], (IFormatProvider)testdata[2], (string)testdata[3]); + //ToStringTest((Half)(float)testdata[0], (string)testdata[1], (IFormatProvider)testdata[2], (string)testdata[3]); + } + } + } + + private static void ToStringTest(Half f, string format, IFormatProvider provider, string expected) + { + bool isDefaultProvider = provider == null; + if (string.IsNullOrEmpty(format) || format.ToUpperInvariant() == "G") + { + if (isDefaultProvider) + { + Assert.Equal(expected, f.ToString()); + Assert.Equal(expected, f.ToString((IFormatProvider)null)); + } + Assert.Equal(expected, f.ToString(provider)); + } + if (isDefaultProvider) + { + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant())); // If format is upper case, then exponents are printed in upper case + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant())); // If format is lower case, then exponents are printed in lower case + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant(), null)); + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant(), null)); + } + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant(), provider)); + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant(), provider)); + } + + public static IEnumerable Parse_ValidWithOffsetCount_TestData() + { + foreach (object[] inputs in Parse_Valid_TestData()) + { + yield return new object[] { inputs[0], 0, ((string)inputs[0]).Length, inputs[1], inputs[2], inputs[3] }; + } + + const NumberStyles DefaultStyle = NumberStyles.Float | NumberStyles.AllowThousands; + + yield return new object[] { "-123", 1, 3, DefaultStyle, null, (float)123 }; + yield return new object[] { "-123", 0, 3, DefaultStyle, null, (float)-12 }; + yield return new object[] { "1E23", 0, 3, DefaultStyle, null, (float)1E2 }; + yield return new object[] { "123", 0, 2, NumberStyles.Float, new NumberFormatInfo(), (float)12 }; + yield return new object[] { "$1,000", 1, 3, NumberStyles.Currency, new NumberFormatInfo() { CurrencySymbol = "$", CurrencyGroupSeparator = "," }, (float)10 }; + yield return new object[] { "(123)", 1, 3, NumberStyles.AllowParentheses, new NumberFormatInfo() { NumberDecimalSeparator = "." }, (float)123 }; + yield return new object[] { "-Infinity", 1, 8, NumberStyles.Any, NumberFormatInfo.InvariantInfo, float.PositiveInfinity }; + } + + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, float expectedFloat) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Half result; + Half expected = (Half)expectedFloat; + if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Half.TryParse(value.AsSpan(offset, count), out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Half.Parse(value.AsSpan(offset, count))); + } + + Assert.Equal(expected, Half.Parse(value.AsSpan(offset, count), provider: provider)); + } + + Assert.True(expected.Equals(Half.Parse(value.AsSpan(offset, count), style, provider)) || (Half.IsNaN(expected) && Half.IsNaN(Half.Parse(value.AsSpan(offset, count), style, provider)))); + + Assert.True(Half.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.True(expected.Equals(result) || (Half.IsNaN(expected) && Half.IsNaN(result))); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + Assert.Throws(exceptionType, () => float.Parse(value.AsSpan(), style, provider)); + + Assert.False(float.TryParse(value.AsSpan(), style, provider, out float result)); + Assert.Equal(0, result); + } + } + + [Fact] + public static void TryFormat() + { + //using (new ThreadCultureChange(CultureInfo.InvariantCulture)) + { + foreach (object[] testdata in ToString_TestData()) + { + float localI = (float)testdata[0]; + string localFormat = (string)testdata[1]; + IFormatProvider localProvider = (IFormatProvider)testdata[2]; + string localExpected = (string)testdata[3]; + + try + { + char[] actual; + int charsWritten; + + // Just right + actual = new char[localExpected.Length]; + Assert.True(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat, localProvider)); + Assert.Equal(localExpected.Length, charsWritten); + Assert.Equal(localExpected, new string(actual)); + + // Longer than needed + actual = new char[localExpected.Length + 1]; + Assert.True(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat, localProvider)); + Assert.Equal(localExpected.Length, charsWritten); + Assert.Equal(localExpected, new string(actual, 0, charsWritten)); + + // Too short + if (localExpected.Length > 0) + { + actual = new char[localExpected.Length - 1]; + Assert.False(localI.TryFormat(actual.AsSpan(), out charsWritten, localFormat, localProvider)); + Assert.Equal(0, charsWritten); + } + } + catch (Exception exc) + { + throw new Exception($"Failed on `{localI}`, `{localFormat}`, `{localProvider}`, `{localExpected}`. {exc}"); + } + } + } + } + + public static IEnumerable ToStringRoundtrip_TestData() + { + yield return new object[] { Half.NegativeInfinity }; + yield return new object[] { Half.MinValue }; + yield return new object[] { -MathF.PI }; + yield return new object[] { -MathF.E }; + yield return new object[] { -Half.Epsilon }; + yield return new object[] { -0.845512408f }; + yield return new object[] { -0.0f }; + yield return new object[] { Half.NaN }; + yield return new object[] { 0.0f }; + yield return new object[] { 0.845512408f }; + yield return new object[] { Half.Epsilon }; + yield return new object[] { MathF.E }; + yield return new object[] { MathF.PI }; + yield return new object[] { Half.MaxValue }; + yield return new object[] { Half.PositiveInfinity }; + } + + [Theory] + [MemberData(nameof(ToStringRoundtrip_TestData))] + public static void ToStringRoundtrip(object o_value) + { + float value = o_value is float floatValue ? floatValue : (float)(Half)o_value; + Half result = Half.Parse(value.ToString()); + Assert.Equal(HalfToUInt16Bits((Half)value), HalfToUInt16Bits(result)); + } + + [Theory] + [MemberData(nameof(ToStringRoundtrip_TestData))] + public static void ToStringRoundtrip_R(object o_value) + { + float value = o_value is float floatValue ? floatValue : (float)(Half)o_value; + Half result = Half.Parse(value.ToString("R")); + Assert.Equal(HalfToUInt16Bits((Half)value), HalfToUInt16Bits(result)); + } + + public static IEnumerable RoundTripFloat_CornerCases() + { + // Magnitude smaller than 2^-24 maps to 0 + yield return new object[] { (Half)(5.2e-20f), 0 }; + yield return new object[] { (Half)(-5.2e-20f), 0 }; + // Magnitude smaller than 2^(map to subnormals + yield return new object[] { (Half)(1.52e-5f), 1.52e-5f }; + yield return new object[] { (Half)(-1.52e-5f), -1.52e-5f }; + // Normal numbers + yield return new object[] { (Half)(55.77f), 55.75f }; + yield return new object[] { (Half)(-55.77f), -55.75f }; + // Magnitude smaller than 2^(map to infinity + yield return new object[] { (Half)(1.7e38f), float.PositiveInfinity }; + yield return new object[] { (Half)(-1.7e38f), float.NegativeInfinity }; + // Infinity and NaN map to infinity and Nan + yield return new object[] { Half.PositiveInfinity, float.PositiveInfinity }; + yield return new object[] { Half.NegativeInfinity, float.NegativeInfinity }; + yield return new object[] { Half.NaN, float.NaN }; + } + + [Theory] + [MemberData(nameof(RoundTripFloat_CornerCases))] + public static void ToSingle(Half half, float verify) + { + float f = (float)half; + Assert.Equal(f, verify, precision: 1); + } + } +} From b0e4b700739c8914b2483e83f90d8bac39292ff1 Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Mon, 8 Jun 2020 21:04:02 -0700 Subject: [PATCH 02/14] sq --- .../System.Private.CoreLib/src/System/Half.cs | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index a0515fb7417e46..6ce7d2683f854d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -2,6 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +/*============================================================ +** +** +** +** Purpose: An IEEE 768 compliant float16 type +** +** +===========================================================*/ + using System.Diagnostics; using System.Globalization; using System.Numerics; @@ -16,7 +25,7 @@ namespace System [Serializable] [StructLayout(LayoutKind.Sequential)] - public readonly struct Half : IComparable, IFormattable, IComparable, IEquatable + public readonly struct Half : IComparable, IFormattable, IComparable, IEquatable, ISpanFormattable { private const NumberStyles DefaultParseStyle = NumberStyles.Float | NumberStyles.AllowThousands; @@ -60,14 +69,6 @@ namespace System private const ushort MinValueBits = 0xFBFF; private const ushort MaxValueBits = 0x7BFF; - // - // Constants that should be returned if values that cannot be represented are converted - // - - private const long IllegalValueToInt64 = long.MinValue; - private const ulong IllegalValueToUInt64 = ulong.MinValue; - private const int IllegalValueToInt32 = int.MinValue; - private const uint IllegalValueToUInt32 = uint.MinValue; // // Well-defined and commonly used values @@ -89,12 +90,6 @@ namespace System private readonly ushort m_value; // Do not rename (binary serialization) - //internal const uint SingleSignMask = 0x8000_0000; - //internal const int SingleSignShift = 31; - //internal const uint SingleExponentMask = 0x7F80_0000; - //internal const int SingleExponentShift = 23; - //internal const uint SingleSignificandMask = 0x007F_FFFF; - private Half(ushort value) { m_value = value; @@ -541,6 +536,7 @@ private static (int Exp, uint Sig) NormSubnormalF16Sig(uint sig) } #region Utilities + internal static uint ToUInt32(float value) => (uint)BitConverter.SingleToInt32Bits(value); From 1e5502a86022a60a2c1b8f8c601c63f7bfd2c375 Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Tue, 9 Jun 2020 12:52:36 -0700 Subject: [PATCH 03/14] Documentation and address comments --- .../src/Resources/Strings.resx | 2 +- .../System.Private.CoreLib/src/System/Half.cs | 133 ++++++++++++++---- .../src/System/TypeCode.cs | 1 - .../System.Runtime/ref/System.Runtime.cs | 2 - .../System.Runtime/tests/System/HalfTests.cs | 2 - 5 files changed, 104 insertions(+), 36 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index ef96719ad764a7..52cce0d6db9f02 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3752,6 +3752,6 @@ Type '{0}' returned by IDynamicInterfaceCastable is not an interface. - Object must be of type Half + Object must be of type Half. diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index 6ce7d2683f854d..50bb232ab81472 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -2,15 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -/*============================================================ -** -** -** -** Purpose: An IEEE 768 compliant float16 type -** -** -===========================================================*/ - using System.Diagnostics; using System.Globalization; using System.Numerics; @@ -19,19 +10,18 @@ namespace System { - // =================================================================================================== // Portions of the code implemented below are based on the 'Berkeley SoftFloat Release 3e' algorithms. - // =================================================================================================== + /// + /// An IEEE 754 compliant float16 type. + /// [Serializable] [StructLayout(LayoutKind.Sequential)] public readonly struct Half : IComparable, IFormattable, IComparable, IEquatable, ISpanFormattable { private const NumberStyles DefaultParseStyle = NumberStyles.Float | NumberStyles.AllowThousands; - // // Constants for manipulating the private bit-representation - // private const ushort SignMask = 0x8000; private const ushort SignShift = 15; @@ -51,9 +41,7 @@ namespace System private const ushort MinSignificand = 0x0000; private const ushort MaxSignificand = 0x03FF; - // // Constants representing the private bit-representation for various default values - // private const ushort PositiveZeroBits = 0x0000; private const ushort NegativeZeroBits = 0x8000; @@ -166,22 +154,15 @@ private ushort Significand public static bool operator ==(Half left, Half right) { - if (IsNaN(left) || IsNaN(right)) - { - // IEEE defines that NaN is not equal to anything, including itself. - return false; - } - - // IEEE defines that positive and negative zero are equivalent. - return (left.m_value == right.m_value) || AreZero(left, right); + return left.Equals(right); } public static bool operator !=(Half left, Half right) { - return !(left == right); + return !(left.Equals(right)); } - // Determines whether the specified value is finite (zero, subnormal, or normal). + /// Determines whether the specified value is finite (zero, subnormal, or normal). [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsFinite(Half value) { @@ -243,21 +224,45 @@ public static bool IsSubnormal(Half value) && ((absValue & ExponentMask) == 0); // is subnormal (has a zero exponent) } + /// + /// Parses a from a in the default parse style. + /// + /// The input to be parsed. + /// The equivalent value representing the input string. If the input exceeds Half's range, a or is returned. public static Half Parse(string s) { return (Half)float.Parse(s, style: DefaultParseStyle, provider: null); } + /// + /// Parses a from a in the given . + /// + /// The input to be parsed. + /// The used to parse the input. + /// The equivalent value representing the input string. If the input exceeds Half's range, a or is returned. public static Half Parse(string s, NumberStyles style) { return (Half)float.Parse(s, style); } + /// + /// Parses a from a and . + /// + /// The input to be parsed. + /// A format provider. + /// The equivalent value representing the input string. If the input exceeds Half's range, a or is returned. public static Half Parse(string s, IFormatProvider provider) { return (Half)float.Parse(s, provider); } + /// + /// Parses a from a with the given and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string. If the input exceeds Half's range, a or is returned. public static Half Parse(string s, NumberStyles style = DefaultParseStyle, IFormatProvider? provider = null) { if (s is null) @@ -267,26 +272,61 @@ public static Half Parse(string s, NumberStyles style = DefaultParseStyle, IForm return Parse(s.AsSpan(), style, provider); } + /// + /// Parses a from a and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string. If the input exceeds Half's range, a or is returned. public static Half Parse(ReadOnlySpan s, NumberStyles style = DefaultParseStyle, IFormatProvider? provider = null) { return (Half)(float.Parse(s, style, provider)); } + /// + /// Tries to parses a from a in the default parse style. + /// + /// The input to be parsed. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Half's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. public static bool TryParse(string s, out Half result) { return TryParse(s, DefaultParseStyle, provider: null, out result); } + /// + /// Tries to parses a from a in the default parse style. + /// + /// The input to be parsed. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Half's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. public static bool TryParse(ReadOnlySpan s, out Half result) { return TryParse(s, DefaultParseStyle, provider: null, out result); } + /// + /// Tries to parse a from a with the given and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Half's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. public static bool TryParse(string s, NumberStyles style, IFormatProvider? provider, out Half result) { return TryParse(s.AsSpan(), style, provider, out result); } + /// + /// Tries to parse a from a with the given and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Half's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Half result) { bool ret = false; @@ -323,6 +363,11 @@ private static ushort StripSign(Half value) return (ushort)(value.m_value & ~SignMask); } + /// + /// Compares this object to another object, returning an integer that indicates the relationship. + /// + /// A value less than zero if this is less than , zero if this is equal to , or a value greater than zero if this is greater than . + /// Thrown when is not of type . public int CompareTo(object? obj) { if (!(obj is Half)) @@ -332,6 +377,10 @@ public int CompareTo(object? obj) return CompareTo((Half)(obj)); } + /// + /// Compares this object to another object, returning an integer that indicates the relationship. + /// + /// A value less than zero if this is less than , zero if this is equal to , or a value greater than zero if this is greater than . public int CompareTo(Half other) { if ((short)(m_value) < (short)(other.m_value)) @@ -358,11 +407,17 @@ public int CompareTo(Half other) return 1; } + /// + /// Returns a value that indicates whether this instance is equal to a specified . + /// public override bool Equals(object? obj) { return (obj is Half) && Equals((Half)(obj)); } + /// + /// Returns a value that indicates whether this instance is equal to a specified value. + /// public bool Equals(Half other) { if (IsNaN(this) || IsNaN(other)) @@ -375,6 +430,9 @@ public bool Equals(Half other) return (m_value == other.m_value) || AreZero(this, other); } + /// + /// Serves as the default hash function. + /// public override int GetHashCode() { if (IsNaNOrZero(this)) @@ -385,32 +443,47 @@ public override int GetHashCode() return m_value; } - public TypeCode GetTypeCode() - { - return TypeCode.Half; - } - + /// + /// Returns a string representation of the current value. + /// public override string ToString() { return ToString(format: null, formatProvider: null); } + /// + /// Returns a string representation of the current value using the specified . + /// public string ToString(string? format) { return ((float)this).ToString(format); } + /// + /// Returns a string representation of the current value with the specified . + /// public string ToString(IFormatProvider? formatProvider) { return ((float)this).ToString(formatProvider); } + /// + /// Returns a string representation of the current value using the specified and . + /// public string ToString(string? format, IFormatProvider? formatProvider) { return ((float)this).ToString(format: null, formatProvider); } + /// + /// Tries to format the value of the current Half instance into the provided span of characters. + /// + /// When this method returns, this instance's value formatted as a span of characters. + /// When this method returns, the number of characters that were written in . + /// A span containing the characters that represent a standard or custom format string that defines the acceptable format for . + /// An optional object that supplies culture-specific formatting information for . + /// public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? formatProvider = null) { return ((float)this).TryFormat(destination, out charsWritten, format, formatProvider); diff --git a/src/libraries/System.Private.CoreLib/src/System/TypeCode.cs b/src/libraries/System.Private.CoreLib/src/System/TypeCode.cs index f6a856600d8edf..296198656b77ce 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TypeCode.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TypeCode.cs @@ -43,6 +43,5 @@ public enum TypeCode Decimal = 15, // Decimal DateTime = 16, // DateTime String = 18, // Unicode character string - Half = 19, // IEEE 16-bit float } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 57a68c3641544a..b853ec504c8243 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -2208,7 +2208,6 @@ public partial struct Guid : System.IComparable, System.IComparable public bool Equals(System.Half other) { throw null; } public override bool Equals(object? obj) { throw null; } public override int GetHashCode() { throw null; } - public System.TypeCode GetTypeCode() { throw null; } public static bool IsFinite(System.Half value) { throw null; } public static bool IsInfinity(System.Half value) { throw null; } public static bool IsNaN(System.Half value) { throw null; } @@ -4354,7 +4353,6 @@ public enum TypeCode Decimal = 15, DateTime = 16, String = 18, - Half = 19, } [System.CLSCompliantAttribute(false)] public ref partial struct TypedReference diff --git a/src/libraries/System.Runtime/tests/System/HalfTests.cs b/src/libraries/System.Runtime/tests/System/HalfTests.cs index 91cbc62a03e864..6202391539e7ff 100644 --- a/src/libraries/System.Runtime/tests/System/HalfTests.cs +++ b/src/libraries/System.Runtime/tests/System/HalfTests.cs @@ -11,13 +11,11 @@ namespace System.Tests { public partial class HalfTests { - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static unsafe ushort HalfToUInt16Bits(Half value) { return *((ushort*)&value); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static unsafe Half UInt16BitsToHalf(ushort value) { return *((Half*)&value); From a9870115010001ee2dd8a1f154000dd1b6fa0f2c Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Wed, 10 Jun 2020 19:23:00 -0700 Subject: [PATCH 04/14] Address feedback --- .../System.Private.CoreLib/src/System/Half.cs | 101 +++++++------- .../src/System/Number.DiyFp.cs | 1 + .../src/System/Number.Dragon4.cs | 30 +++++ .../src/System/Number.Formatting.cs | 125 ++++++++++++++++++ .../src/System/Number.Grisu3.cs | 34 +++++ .../src/System/Number.NumberBuffer.cs | 1 + .../Number.NumberToFloatingPointBits.cs | 8 ++ .../src/System/Number.Parsing.cs | 92 +++++++++++++ 8 files changed, 342 insertions(+), 50 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index 50bb232ab81472..d9129a30a0028c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Globalization; using System.Numerics; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace System @@ -15,7 +14,6 @@ namespace System /// /// An IEEE 754 compliant float16 type. /// - [Serializable] [StructLayout(LayoutKind.Sequential)] public readonly struct Half : IComparable, IFormattable, IComparable, IEquatable, ISpanFormattable { @@ -58,27 +56,33 @@ namespace System private const ushort MaxValueBits = 0x7BFF; - // // Well-defined and commonly used values - // - public static readonly Half Epsilon = new Half(EpsilonBits); // 5.9604645E-08 + private static readonly Half s_epsilon = new Half(EpsilonBits); // 5.9604645E-08 + public static Half Epsilon => s_epsilon; - public static readonly Half PositiveInfinity = new Half(PositiveInfinityBits); // 1.0 / 0.0 - public static readonly Half NegativeInfinity = new Half(NegativeInfinityBits); // -1.0 / 0.0 + private static readonly Half s_positiveInfinity = new Half(PositiveInfinityBits); // 1.0 / 0.0 + public static Half PositiveInfinity => s_positiveInfinity; - public static readonly Half NaN = new Half(NegativeQNaNBits); // 0.0 / 0.0 + private static readonly Half s_negativeInfinity = new Half(NegativeInfinityBits); // -1.0 / 0.0 + public static Half NegativeInfinity => s_negativeInfinity; - public static readonly Half MinValue = new Half(MinValueBits); // -65504 - public static readonly Half MaxValue = new Half(MaxValueBits); // 65504 + private static readonly Half s_naN = new Half(NegativeQNaNBits); // 0.0 / 0.0 + public static Half NaN => s_naN; + + private static readonly Half _MinValue = new Half(MinValueBits); // -65504 + public static Half MinValue => _MinValue; + + private static readonly Half s_maxValue = new Half(MaxValueBits); // 65504 + public static Half MaxValue => s_maxValue; // We use these explicit definitions to avoid the confusion between 0.0 and -0.0. private static readonly Half PositiveZero = new Half(PositiveZeroBits); // 0.0 private static readonly Half NegativeZero = new Half(NegativeZeroBits); // -0.0 - private readonly ushort m_value; // Do not rename (binary serialization) + private readonly ushort m_value; - private Half(ushort value) + internal Half(ushort value) { m_value = value; } @@ -163,35 +167,30 @@ private ushort Significand } /// Determines whether the specified value is finite (zero, subnormal, or normal). - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsFinite(Half value) { return StripSign(value) < PositiveInfinityBits; } /// Determines whether the specified value is infinite. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsInfinity(Half value) { return StripSign(value) == PositiveInfinityBits; } /// Determines whether the specified value is NaN. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNaN(Half value) { return StripSign(value) > PositiveInfinityBits; } /// Determines whether the specified value is negative. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNegative(Half value) { return (short)(value.m_value) < 0; } /// Determines whether the specified value is negative infinity. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNegativeInfinity(Half value) { return value.m_value == NegativeInfinityBits; @@ -208,7 +207,6 @@ public static bool IsNormal(Half value) } /// Determines whether the specified value is positive infinity. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsPositiveInfinity(Half value) { return value.m_value == PositiveInfinityBits; @@ -342,7 +340,6 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro return ret; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool AreZero(Half left, Half right) { // IEEE defines that positive and negative zero are equal, this gives us a quick equality check @@ -351,13 +348,11 @@ private static bool AreZero(Half left, Half right) return (ushort)((left.m_value | right.m_value) & ~SignMask) == 0; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsNaNOrZero(Half value) { return ((value.m_value - 1) & ~SignMask) >= PositiveInfinityBits; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort StripSign(Half value) { return (ushort)(value.m_value & ~SignMask); @@ -383,17 +378,17 @@ public int CompareTo(object? obj) /// A value less than zero if this is less than , zero if this is equal to , or a value greater than zero if this is greater than . public int CompareTo(Half other) { - if ((short)(m_value) < (short)(other.m_value)) + if (this < other) { return -1; } - if ((short)(m_value) > (short)(other.m_value)) + if (this > other) { return 1; } - if (m_value == other.m_value) + if (this == other) { return 0; } @@ -412,7 +407,7 @@ public int CompareTo(Half other) /// public override bool Equals(object? obj) { - return (obj is Half) && Equals((Half)(obj)); + return (obj is Half other) && Equals(other); } /// @@ -448,7 +443,7 @@ public override int GetHashCode() /// public override string ToString() { - return ToString(format: null, formatProvider: null); + return Number.FormatHalf(this, null, NumberFormatInfo.CurrentInfo); } /// @@ -456,23 +451,23 @@ public override string ToString() /// public string ToString(string? format) { - return ((float)this).ToString(format); + return Number.FormatHalf(this, format, NumberFormatInfo.CurrentInfo); } /// - /// Returns a string representation of the current value with the specified . + /// Returns a string representation of the current value with the specified . /// - public string ToString(IFormatProvider? formatProvider) + public string ToString(IFormatProvider? provider) { - return ((float)this).ToString(formatProvider); + return Number.FormatHalf(this, null, NumberFormatInfo.GetInstance(provider)); } /// - /// Returns a string representation of the current value using the specified and . + /// Returns a string representation of the current value using the specified and . /// - public string ToString(string? format, IFormatProvider? formatProvider) + public string ToString(string? format, IFormatProvider? provider) { - return ((float)this).ToString(format: null, formatProvider); + return Number.FormatHalf(this, format, NumberFormatInfo.GetInstance(provider)); } @@ -482,11 +477,11 @@ public string ToString(string? format, IFormatProvider? formatProvider) /// When this method returns, this instance's value formatted as a span of characters. /// When this method returns, the number of characters that were written in . /// A span containing the characters that represent a standard or custom format string that defines the acceptable format for . - /// An optional object that supplies culture-specific formatting information for . + /// An optional object that supplies culture-specific formatting information for . /// - public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? formatProvider = null) + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) { - return ((float)this).TryFormat(destination, out charsWritten, format, formatProvider); + return Number.TryFormatHalf(this, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); } // -----------------------Start of to-half conversions------------------------- @@ -602,20 +597,26 @@ public static implicit operator double(Half value) return CreateDouble(sign, (ushort)(exp + 0x3F0), (ulong)sig << 42); } + // IEEE 754 specifies NaNs to be propagated + internal static Half Negate(Half value) + { + return IsNaN(value) ? value : new Half((ushort)(value.m_value ^ SignMask)); + } + private static (int Exp, uint Sig) NormSubnormalF16Sig(uint sig) { - int shiftDist = BitOperations.LeadingZeroCount(sig) - 16 - 5; // No LZCNT for 16-bit + int shiftDist = BitOperations.LeadingZeroCount(sig) - 16 - 5; return (1 - shiftDist, sig << shiftDist); } #region Utilities - internal static uint ToUInt32(float value) + private static uint ToUInt32(float value) => (uint)BitConverter.SingleToInt32Bits(value); // Significand bits should be shifted towards to the left end before calling these methods // Creates Quiet NaN if significand == 0 - internal static Half CreateHalfNaN(bool sign, ulong significand) + private static Half CreateHalfNaN(bool sign, ulong significand) { const uint NaNBits = ExponentMask | 0x200; // Most significant significand bit @@ -625,10 +626,10 @@ internal static Half CreateHalfNaN(bool sign, ulong significand) return CreateHalf((ushort)(signInt | NaNBits | sigInt)); } - internal static unsafe Half CreateHalf(ushort value) + private static unsafe Half CreateHalf(ushort value) => *(Half*)&value; - internal static ushort RoundPackToHalf(bool sign, short exp, ushort sig) + private static ushort RoundPackToHalf(bool sign, short exp, ushort sig) { const int roundIncrement = 0x8; // Depends on rounding mode but it's always towards closest / ties to even int roundBits = sig & 0xF; @@ -660,16 +661,16 @@ internal static ushort RoundPackToHalf(bool sign, short exp, ushort sig) // If any bits are lost by shifting, "jam" them into the LSB. // if dist > bit count, Will be 1 or 0 depending on i // (unlike bitwise operators that masks the lower 5 bits) - internal static uint ShiftRightJam(uint i, int dist) + private static uint ShiftRightJam(uint i, int dist) => dist < 31 ? (i >> dist) | (i << (-dist & 31) != 0 ? 1U : 0U) : (i != 0 ? 1U : 0U); - internal static ulong ShiftRightJam(ulong l, int dist) + private static ulong ShiftRightJam(ulong l, int dist) => dist < 63 ? (l >> dist) | (l << (-dist & 63) != 0 ? 1UL : 0UL) : (l != 0 ? 1UL : 0UL); - internal static ulong ToUInt64(double value) + private static ulong ToUInt64(double value) => (ulong)BitConverter.DoubleToInt64Bits(value); - internal static float CreateSingleNaN(bool sign, ulong significand) + private static float CreateSingleNaN(bool sign, ulong significand) { const uint NaNBits = float.ExponentMask | 0x400000; // Most significant significand bit @@ -679,7 +680,7 @@ internal static float CreateSingleNaN(bool sign, ulong significand) return CreateSingle(signInt | NaNBits | sigInt); } - internal static double CreateDoubleNaN(bool sign, ulong significand) + private static double CreateDoubleNaN(bool sign, ulong significand) { const ulong NaNBits = double.ExponentMask | 0x80000_00000000; // Most significant significand bit @@ -689,16 +690,16 @@ internal static double CreateDoubleNaN(bool sign, ulong significand) return CreateDouble(signInt | NaNBits | sigInt); } - internal static double CreateDouble(ulong value) + private static double CreateDouble(ulong value) => BitConverter.Int64BitsToDouble((long)value); - internal static float CreateSingle(uint value) + private static float CreateSingle(uint value) => BitConverter.Int32BitsToSingle((int)value); - internal static float CreateSingle(bool sign, byte exp, uint sig) + private static float CreateSingle(bool sign, byte exp, uint sig) => BitConverter.Int32BitsToSingle((int)(((sign ? 1U : 0U) << float.SignShift) | ((uint)exp << float.ExponentShift) | sig)); - internal static double CreateDouble(bool sign, ushort exp, ulong sig) + private static double CreateDouble(bool sign, ushort exp, ulong sig) => BitConverter.Int64BitsToDouble((long)(((sign ? 1UL : 0UL) << double.SignShift) | ((ulong)exp << double.ExponentShift) | sig)); #endregion diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs index 141690dd6325bf..90359cea2e97a2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs @@ -20,6 +20,7 @@ internal readonly ref struct DiyFp { public const int DoubleImplicitBitIndex = 52; public const int SingleImplicitBitIndex = 23; + public const int HalfImplicitBitIndex = 10; public const int SignificandSize = 64; diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs index 3e03b8b4972cd7..1799a7415438d9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs @@ -41,6 +41,36 @@ public static void Dragon4Double(double value, int cutoffNumber, bool isSignific number.DigitsCount = length; } + public static unsafe void Dragon4Half(Half value, int cutoffNumber, bool isSignificantDigits, ref NumberBuffer number) + { + Half v = Half.IsNegative(value) ? Half.Negate(value) : value; + + Debug.Assert(v > 0); + Debug.Assert(Half.IsFinite(v)); + + ushort mantissa = ExtractFractionAndBiasedExponent(value, out int exponent); + + uint mantissaHighBitIdx; + bool hasUnequalMargins = false; + + if ((mantissa >> DiyFp.HalfImplicitBitIndex) != 0) + { + mantissaHighBitIdx = DiyFp.HalfImplicitBitIndex; + hasUnequalMargins = (mantissa == (1U << DiyFp.HalfImplicitBitIndex)); + } + else + { + Debug.Assert(mantissa != 0); + mantissaHighBitIdx = (uint)BitOperations.Log2(mantissa); + } + + int length = (int)(Dragon4(mantissa, exponent, mantissaHighBitIdx, hasUnequalMargins, cutoffNumber, isSignificantDigits, number.Digits, out int decimalExponent)); + + number.Scale = decimalExponent + 1; + number.Digits[length] = (byte)('\0'); + number.DigitsCount = length; + } + public static unsafe void Dragon4Single(float value, int cutoffNumber, bool isSignificantDigits, ref NumberBuffer number) { float v = float.IsNegative(value) ? -value : value; diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index 6f2f7b15d7c2d7..de7724e81ecad3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -247,6 +247,7 @@ internal static partial class Number // SinglePrecision and DoublePrecision represent the maximum number of digits required // to guarantee that any given Single or Double can roundtrip. Some numbers may require // less, but none will require more. + private const int HalfPrecision = 5; private const int SinglePrecision = 9; private const int DoublePrecision = 17; @@ -256,6 +257,7 @@ internal static partial class Number // In order to support more digits, we would need to update ParseFormatSpecifier to pre-parse // the format and determine exactly how many digits are being requested and whether they // represent "significant digits" or "digits after the decimal point". + private const int HalfPrecisionCustomFormat = 5; private const int SinglePrecisionCustomFormat = 7; private const int DoublePrecisionCustomFormat = 15; @@ -582,6 +584,13 @@ private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int preci return null; } + public static string FormatHalf(Half value, string? format, NumberFormatInfo info) + { + Span stackBuffer = stackalloc char[CharStackBufferSize]; + var sb = new ValueStringBuilder(stackBuffer); + return FormatHalf(ref sb, value, format, info) ?? sb.ToString(); + } + public static string FormatSingle(float value, string? format, NumberFormatInfo info) { var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); @@ -597,6 +606,85 @@ public static bool TryFormatSingle(float value, ReadOnlySpan format, Numbe sb.TryCopyTo(destination, out charsWritten); } + public static bool TryFormatHalf(Half value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) + { + Span stackBuffer = stackalloc char[CharStackBufferSize]; + var sb = new ValueStringBuilder(stackBuffer); + string? s = FormatHalf(ref sb, value, format, info); + return s != null ? + TryCopyTo(s, destination, out charsWritten) : + sb.TryCopyTo(destination, out charsWritten); + } + + /// Formats the specified value according to the specified format and info. + /// + /// Non-null if an existing string can be returned, in which case the builder will be unmodified. + /// Null if no existing string was returned, in which case the formatted output is in the builder. + /// + private static unsafe string? FormatHalf(ref ValueStringBuilder sb, Half value, ReadOnlySpan format, NumberFormatInfo info) + { + if (!Half.IsFinite(value)) + { + if (Half.IsNaN(value)) + { + return info.NaNSymbol; + } + + return Half.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; + } + + char fmt = ParseFormatSpecifier(format, out int precision); + byte* pDigits = stackalloc byte[HalfNumberBufferLength]; + + if (fmt == '\0') + { + precision = HalfPrecisionCustomFormat; + } + + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, HalfNumberBufferLength); + number.IsNegative = Half.IsNegative(value); + + // We need to track the original precision requested since some formats + // accept values like 0 and others may require additional fixups. + int nMaxDigits = GetFloatingPointMaxDigitsAndPrecision(fmt, ref precision, info, out bool isSignificantDigits); + + if ((value != 0.0) && (!isSignificantDigits || !Grisu3.TryRunHalf(value, precision, ref number))) + { + Dragon4Half(value, precision, isSignificantDigits, ref number); + } + + number.CheckConsistency(); + + // When the number is known to be roundtrippable (either because we requested it be, or + // because we know we have enough digits to satisfy roundtrippability), we should validate + // that the number actually roundtrips back to the original result. + + Debug.Assert(((precision != -1) && (precision < HalfPrecision)) || (HalfToUInt16Bits(value) == HalfToUInt16Bits(NumberToHalf(ref number)))); + + if (fmt != 0) + { + if (precision == -1) + { + Debug.Assert((fmt == 'G') || (fmt == 'g') || (fmt == 'R') || (fmt == 'r')); + + // For the roundtrip and general format specifiers, when returning the shortest roundtrippable + // string, we need to update the maximum number of digits to be the greater of number.DigitsCount + // or SinglePrecision. This ensures that we continue returning "pretty" strings for values with + // less digits. One example this fixes is "-60", which would otherwise be formatted as "-6E+01" + // since DigitsCount would be 1 and the formatter would almost immediately switch to scientific notation. + + nMaxDigits = Math.Max(number.DigitsCount, SinglePrecision); + } + NumberToString(ref sb, ref number, fmt, nMaxDigits, info); + } + else + { + Debug.Assert(precision == HalfPrecisionCustomFormat); + NumberToStringFormat(ref sb, ref number, format, info); + } + return null; + } + /// Formats the specified value according to the specified format and info. /// /// Non-null if an existing string can be returned, in which case the builder will be unmodified. @@ -2563,6 +2651,43 @@ private static ulong ExtractFractionAndBiasedExponent(double value, out int expo return fraction; } + private static unsafe ushort HalfToUInt16Bits(Half value) + { + return *((ushort*)&value); + } + + private static ushort ExtractFractionAndBiasedExponent(Half value, out int exponent) + { + ushort bits = HalfToUInt16Bits(value); + ushort fraction = (ushort)(bits & 0x3FF); + exponent = ((int)(bits >> 10) & 0xFF); + + if (exponent != 0) + { + // For normalized value, according to https://en.wikipedia.org/wiki/Single-precision_floating-point_format + // value = 1.fraction * 2^(exp - 15) + // = (1 + mantissa / 2^10) * 2^(exp - 15) + // = (2^10 + mantissa) * 2^(exp - 15 - 10) + // + // So f = (2^10 + mantissa), e = exp - 25; + + fraction |= (ushort)(1U << 10); + exponent -= 25; + } + else + { + // For denormalized value, according to https://en.wikipedia.org/wiki/Single-precision_floating-point_format + // value = 0.fraction * 2^(1 - 15) + // = (mantissa / 2^10) * 2^(-14) + // = mantissa * 2^(-14 - 10) + // = mantissa * 2^(-24) + // So f = mantissa, e = -24 + exponent = -24; + } + + return fraction; + } + private static uint ExtractFractionAndBiasedExponent(float value, out int exponent) { uint bits = (uint)(BitConverter.SingleToInt32Bits(value)); diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs index 528e64e0aa7387..84d39a5ee415d7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs @@ -356,6 +356,40 @@ public static bool TryRunDouble(double value, int requestedDigits, ref NumberBuf return result; } + public static bool TryRunHalf(Half value, int requestedDigits, ref NumberBuffer number) + { + Half v = Half.IsNegative(value) ? Half.Negate(value) : value; + + Debug.Assert(v > 0); + Debug.Assert(Half.IsFinite(v)); + + int length; + int decimalExponent; + bool result; + + if (requestedDigits == -1) + { + DiyFp w = DiyFp.CreateAndGetBoundaries(v, out DiyFp boundaryMinus, out DiyFp boundaryPlus).Normalize(); + result = TryRunShortest(in boundaryMinus, in w, in boundaryPlus, number.Digits, out length, out decimalExponent); + } + else + { + DiyFp w = new DiyFp(v).Normalize(); + result = TryRunCounted(in w, requestedDigits, number.Digits, out length, out decimalExponent); + } + + if (result) + { + Debug.Assert((requestedDigits == -1) || (length == requestedDigits)); + + number.Scale = length + decimalExponent; + number.Digits[length] = (byte)('\0'); + number.DigitsCount = length; + } + + return result; + } + public static bool TryRunSingle(float value, int requestedDigits, ref NumberBuffer number) { float v = float.IsNegative(value) ? -value : value; diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs b/src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs index 7218af24076fff..994ddf2c3ce3c0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs @@ -16,6 +16,7 @@ internal static partial class Number internal const int Int32NumberBufferLength = 10 + 1; // 10 for the longest input: 2,147,483,647 internal const int Int64NumberBufferLength = 19 + 1; // 19 for the longest input: 9,223,372,036,854,775,807 internal const int SingleNumberBufferLength = 112 + 1 + 1; // 112 for the longest input + 1 for rounding: 1.40129846E-45 + internal const int HalfNumberBufferLength = 21; internal const int UInt32NumberBufferLength = 10 + 1; // 10 for the longest input: 4,294,967,295 internal const int UInt64NumberBufferLength = 20 + 1; // 20 for the longest input: 18,446,744,073,709,551,615 diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs index 59a47c67455e6a..078292e827c5fc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs @@ -26,6 +26,14 @@ public readonly struct FloatingPointInfo infinityBits: 0x7F800000 ); + public static readonly FloatingPointInfo Half = new FloatingPointInfo( + denormalMantissaBits: 10, + exponentBits: 5, + maxBinaryExponent: 15, + exponentBias: 15, + infinityBits: 0x7C00 + ); + public ulong ZeroBits { get; } public ulong InfinityBits { get; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs index 8c4cd1c9adff5e..aae3a5b3d9b3e2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -42,6 +42,9 @@ internal static partial class Number private const int SingleMaxExponent = 39; private const int SingleMinExponent = -45; + private const int HalfMaxExponent = 5; + private const int HalfMinExponent = -8; + /// Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. internal static ReadOnlySpan CharToHexLookup => new byte[] { @@ -1789,6 +1792,73 @@ internal static unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyle return true; } + internal static unsafe bool TryParseHalf(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out Half result) + { + byte* pDigits = stackalloc byte[HalfNumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, HalfNumberBufferLength); + + if (!TryStringToNumber(value, styles, ref number, info)) + { + ReadOnlySpan valueTrim = value.Trim(); + + // This code would be simpler if we only had the concept of `InfinitySymbol`, but + // we don't so we'll check the existing cases first and then handle `PositiveSign` + + // `PositiveInfinitySymbol` and `PositiveSign/NegativeSign` + `NaNSymbol` last. + // + // Additionally, since some cultures ("wo") actually define `PositiveInfinitySymbol` + // to include `PositiveSign`, we need to check whether `PositiveInfinitySymbol` fits + // that case so that we don't start parsing things like `++infini`. + + if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) + { + result = Half.PositiveInfinity; + } + else if (valueTrim.EqualsOrdinalIgnoreCase(info.NegativeInfinitySymbol)) + { + result = Half.NegativeInfinity; + } + else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) + { + result = Half.NaN; + } + else if (valueTrim.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase)) + { + valueTrim = valueTrim.Slice(info.PositiveSign.Length); + + if (!info.PositiveInfinitySymbol.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase) && valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) + { + result = Half.PositiveInfinity; + } + else if (!info.NaNSymbol.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase) && valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) + { + result = Half.NaN; + } + else + { + result = (Half)0; + return false; + } + } + else if (valueTrim.StartsWith(info.NegativeSign, StringComparison.OrdinalIgnoreCase) && + !info.NaNSymbol.StartsWith(info.NegativeSign, StringComparison.OrdinalIgnoreCase) && + valueTrim.Slice(info.NegativeSign.Length).EqualsOrdinalIgnoreCase(info.NaNSymbol)) + { + result = Half.NaN; + } + else + { + result = (Half)0; + return false; // We really failed + } + } + else + { + result = NumberToHalf(ref number); + } + + return true; + } + internal static unsafe bool TryParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out float result) { byte* pDigits = stackalloc byte[SingleNumberBufferLength]; @@ -1999,6 +2069,28 @@ internal static double NumberToDouble(ref NumberBuffer number) return number.IsNegative ? -result : result; } + internal static Half NumberToHalf(ref NumberBuffer number) + { + number.CheckConsistency(); + Half result; + + if ((number.DigitsCount == 0) || (number.Scale < HalfMinExponent)) + { + result = (Half)0; + } + else if (number.Scale > HalfMaxExponent) + { + result = Half.PositiveInfinity; + } + else + { + ushort bits = (ushort)(NumberToFloatingPointBits(ref number, in FloatingPointInfo.Half)); + result = new Half(bits); + } + + return number.IsNegative ? Half.Negate(result) : result; + } + internal static float NumberToSingle(ref NumberBuffer number) { number.CheckConsistency(); From 250cc0181620641eb4c4f08682a71017d8266d6f Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Wed, 10 Jun 2020 19:35:44 -0700 Subject: [PATCH 05/14] sq --- .../System.Runtime/ref/System.Runtime.cs | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index b853ec504c8243..9e9576b445d9f1 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -2194,53 +2194,53 @@ public partial struct Guid : System.IComparable, System.IComparable public static bool TryParseExact([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? input, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? format, out System.Guid result) { throw null; } public bool TryWriteBytes(System.Span destination) { throw null; } } - public readonly partial struct Half : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable - { - private readonly int _dummyPrimitive; - public static readonly System.Half Epsilon; - public static readonly System.Half MaxValue; - public static readonly System.Half MinValue; - public static readonly System.Half NaN; - public static readonly System.Half NegativeInfinity; - public static readonly System.Half PositiveInfinity; - public int CompareTo(System.Half other) { throw null; } - public int CompareTo(object? obj) { throw null; } - public bool Equals(System.Half other) { throw null; } - public override bool Equals(object? obj) { throw null; } - public override int GetHashCode() { throw null; } - public static bool IsFinite(System.Half value) { throw null; } - public static bool IsInfinity(System.Half value) { throw null; } - public static bool IsNaN(System.Half value) { throw null; } - public static bool IsNegative(System.Half value) { throw null; } - public static bool IsNegativeInfinity(System.Half value) { throw null; } - public static bool IsNormal(System.Half value) { throw null; } - public static bool IsPositiveInfinity(System.Half value) { throw null; } - public static bool IsSubnormal(System.Half value) { throw null; } - public static bool operator ==(System.Half left, System.Half right) { throw null; } - public static explicit operator System.Half (double value) { throw null; } - public static explicit operator System.Half (float value) { throw null; } - public static bool operator >(System.Half left, System.Half right) { throw null; } - public static bool operator >=(System.Half left, System.Half right) { throw null; } - public static implicit operator double (System.Half value) { throw null; } - public static implicit operator float (System.Half value) { throw null; } - public static bool operator !=(System.Half left, System.Half right) { throw null; } - public static bool operator <(System.Half left, System.Half right) { throw null; } - public static bool operator <=(System.Half left, System.Half right) { throw null; } - public static System.Half Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } - public static System.Half Parse(string s) { throw null; } - public static System.Half Parse(string s, System.Globalization.NumberStyles style) { throw null; } - public static System.Half Parse(string s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } - public static System.Half Parse(string s, System.IFormatProvider provider) { throw null; } - public override string ToString() { throw null; } - public string ToString(System.IFormatProvider? formatProvider) { throw null; } - public string ToString(string? format) { throw null; } - public string ToString(string? format, System.IFormatProvider? formatProvider) { throw null; } - public bool TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? formatProvider = null) { throw null; } - public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Half result) { throw null; } - public static bool TryParse(System.ReadOnlySpan s, out System.Half result) { throw null; } - public static bool TryParse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Half result) { throw null; } - public static bool TryParse(string s, out System.Half result) { throw null; } - } +    public readonly partial struct Half : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable +    { +        private readonly int _dummyPrimitive; +        public static System.Half Epsilon { get { throw null; } } +        public static System.Half MaxValue { get { throw null; } } +        public static System.Half MinValue { get { throw null; } } +        public static System.Half NaN { get { throw null; } } +        public static System.Half NegativeInfinity { get { throw null; } } +        public static System.Half PositiveInfinity { get { throw null; } } +        public int CompareTo(System.Half other) { throw null; } +        public int CompareTo(object? obj) { throw null; } +        public bool Equals(System.Half other) { throw null; } +        public override bool Equals(object? obj) { throw null; } +        public override int GetHashCode() { throw null; } +        public static bool IsFinite(System.Half value) { throw null; } +        public static bool IsInfinity(System.Half value) { throw null; } +        public static bool IsNaN(System.Half value) { throw null; } +        public static bool IsNegative(System.Half value) { throw null; } +        public static bool IsNegativeInfinity(System.Half value) { throw null; } +        public static bool IsNormal(System.Half value) { throw null; } +        public static bool IsPositiveInfinity(System.Half value) { throw null; } +        public static bool IsSubnormal(System.Half value) { throw null; } +        public static bool operator ==(System.Half left, System.Half right) { throw null; } +        public static explicit operator System.Half (double value) { throw null; } +        public static explicit operator System.Half (float value) { throw null; } +        public static bool operator >(System.Half left, System.Half right) { throw null; } +        public static bool operator >=(System.Half left, System.Half right) { throw null; } +        public static implicit operator double (System.Half value) { throw null; } +        public static implicit operator float (System.Half value) { throw null; } +        public static bool operator !=(System.Half left, System.Half right) { throw null; } +        public static bool operator <(System.Half left, System.Half right) { throw null; } +        public static bool operator <=(System.Half left, System.Half right) { throw null; } +        public static System.Half Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } +        public static System.Half Parse(string s) { throw null; } +        public static System.Half Parse(string s, System.Globalization.NumberStyles style) { throw null; } +        public static System.Half Parse(string s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } +        public static System.Half Parse(string s, System.IFormatProvider provider) { throw null; } +        public override string ToString() { throw null; } +        public string ToString(System.IFormatProvider? provider) { throw null; } +        public string ToString(string? format) { throw null; } +        public string ToString(string? format, System.IFormatProvider? provider) { throw null; } +        public bool TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } +        public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Half result) { throw null; } +        public static bool TryParse(System.ReadOnlySpan s, out System.Half result) { throw null; } +        public static bool TryParse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Half result) { throw null; } +        public static bool TryParse(string s, out System.Half result) { throw null; } +    } public partial struct HashCode { private int _dummyPrimitive; From a3d03cf8a334dcdd65e48d45e02e8a89befab30a Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Thu, 11 Jun 2020 15:18:38 -0700 Subject: [PATCH 06/14] Address feedback --- .../System.Private.CoreLib/src/System/Half.cs | 60 +++++++++---------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index d9129a30a0028c..71f43cdbf17a45 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -58,43 +58,37 @@ namespace System // Well-defined and commonly used values - private static readonly Half s_epsilon = new Half(EpsilonBits); // 5.9604645E-08 - public static Half Epsilon => s_epsilon; + public static Half Epsilon => new Half(EpsilonBits); // 5.9604645E-08 - private static readonly Half s_positiveInfinity = new Half(PositiveInfinityBits); // 1.0 / 0.0 - public static Half PositiveInfinity => s_positiveInfinity; + public static Half PositiveInfinity => new Half(PositiveInfinityBits); // 1.0 / 0.0; - private static readonly Half s_negativeInfinity = new Half(NegativeInfinityBits); // -1.0 / 0.0 - public static Half NegativeInfinity => s_negativeInfinity; + public static Half NegativeInfinity => new Half(NegativeInfinityBits); // -1.0 / 0.0 - private static readonly Half s_naN = new Half(NegativeQNaNBits); // 0.0 / 0.0 - public static Half NaN => s_naN; + public static Half NaN => new Half(NegativeQNaNBits); // 0.0 / 0.0 - private static readonly Half _MinValue = new Half(MinValueBits); // -65504 - public static Half MinValue => _MinValue; + public static Half MinValue => new Half(MinValueBits); // -65504 - private static readonly Half s_maxValue = new Half(MaxValueBits); // 65504 - public static Half MaxValue => s_maxValue; + public static Half MaxValue => new Half(MaxValueBits); // 65504 // We use these explicit definitions to avoid the confusion between 0.0 and -0.0. private static readonly Half PositiveZero = new Half(PositiveZeroBits); // 0.0 private static readonly Half NegativeZero = new Half(NegativeZeroBits); // -0.0 - private readonly ushort m_value; + private readonly ushort _value; internal Half(ushort value) { - m_value = value; + _value = value; } private Half(bool sign, ushort exp, ushort sig) - => m_value = (ushort)(((sign ? 1 : 0) << SignShift) + (exp << ExponentShift) + sig); + => _value = (ushort)(((sign ? 1 : 0) << SignShift) + (exp << ExponentShift) + sig); private sbyte Exponent { get { - return (sbyte)((m_value & ExponentMask) >> ExponentShift); + return (sbyte)((_value & ExponentMask) >> ExponentShift); } } @@ -102,7 +96,7 @@ private ushort Significand { get { - return (ushort)((m_value & SignificandMask) >> SignificandShift); + return (ushort)((_value & SignificandMask) >> SignificandShift); } } @@ -123,7 +117,7 @@ private ushort Significand // says they should be equal, even if the signs differ. return leftIsNegative && !AreZero(left, right); } - return (short)(left.m_value) < (short)(right.m_value); + return (short)(left._value) < (short)(right._value); } public static bool operator >(Half left, Half right) @@ -148,7 +142,7 @@ private ushort Significand // says they should be equal, even if the signs differ. return leftIsNegative || AreZero(left, right); } - return (short)(left.m_value) <= (short)(right.m_value); + return (short)(left._value) <= (short)(right._value); } public static bool operator >=(Half left, Half right) @@ -187,20 +181,20 @@ public static bool IsNaN(Half value) /// Determines whether the specified value is negative. public static bool IsNegative(Half value) { - return (short)(value.m_value) < 0; + return (short)(value._value) < 0; } /// Determines whether the specified value is negative infinity. public static bool IsNegativeInfinity(Half value) { - return value.m_value == NegativeInfinityBits; + 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 public static bool IsNormal(Half value) { - int absValue = StripSign(value); + uint absValue = StripSign(value); return (absValue < PositiveInfinityBits) // is finite && (absValue != 0) // is not zero && ((absValue & ExponentMask) != 0); // is not subnormal (has a non-zero exponent) @@ -209,14 +203,14 @@ public static bool IsNormal(Half value) /// Determines whether the specified value is positive infinity. public static bool IsPositiveInfinity(Half value) { - return value.m_value == PositiveInfinityBits; + 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 public static bool IsSubnormal(Half value) { - int absValue = StripSign(value); + uint absValue = StripSign(value); return (absValue < PositiveInfinityBits) // is finite && (absValue != 0) // is not zero && ((absValue & ExponentMask) == 0); // is subnormal (has a zero exponent) @@ -345,17 +339,17 @@ 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.m_value | right.m_value) & ~SignMask) == 0; + return (ushort)((left._value | right._value) & ~SignMask) == 0; } private static bool IsNaNOrZero(Half value) { - return ((value.m_value - 1) & ~SignMask) >= PositiveInfinityBits; + return ((value._value - 1) & ~SignMask) >= PositiveInfinityBits; } - private static ushort StripSign(Half value) + private static uint StripSign(Half value) { - return (ushort)(value.m_value & ~SignMask); + return (ushort)(value._value & ~SignMask); } /// @@ -422,7 +416,7 @@ public bool Equals(Half other) } // IEEE defines that positive and negative zero are equivalent. - return (m_value == other.m_value) || AreZero(this, other); + return (_value == other._value) || AreZero(this, other); } /// @@ -433,9 +427,9 @@ public override int GetHashCode() if (IsNaNOrZero(this)) { // All NaNs should have the same hash code, as should both Zeros. - return m_value & PositiveInfinityBits; + return _value & PositiveInfinityBits; } - return m_value; + return _value; } /// @@ -600,7 +594,7 @@ public static implicit operator double(Half value) // IEEE 754 specifies NaNs to be propagated internal static Half Negate(Half value) { - return IsNaN(value) ? value : new Half((ushort)(value.m_value ^ SignMask)); + return IsNaN(value) ? value : new Half((ushort)(value._value ^ SignMask)); } private static (int Exp, uint Sig) NormSubnormalF16Sig(uint sig) @@ -655,7 +649,7 @@ private static ushort RoundPackToHalf(bool sign, short exp, ushort sig) exp = 0; } - return new Half(sign, (ushort)exp, sig).m_value; + return new Half(sign, (ushort)exp, sig)._value; } // If any bits are lost by shifting, "jam" them into the LSB. From 999587ba8390fbf9ba92f60e00d99d4630f4ffac Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Fri, 12 Jun 2020 10:46:15 -0700 Subject: [PATCH 07/14] Nits --- .../System.Private.CoreLib/src/System/Half.cs | 17 ++++++++--------- .../src/System/Number.Formatting.cs | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index 71f43cdbf17a45..80440183f8c8be 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -462,7 +462,6 @@ public string ToString(IFormatProvider? provider) public string ToString(string? format, IFormatProvider? provider) { return Number.FormatHalf(this, format, NumberFormatInfo.GetInstance(provider)); - } /// @@ -482,14 +481,14 @@ public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan public static explicit operator Half(float value) { - const int singleMaxExponent = 0xFF; + const int SingleMaxExponent = 0xFF; uint floatInt = ToUInt32(value); bool sign = (floatInt & float.SignMask) >> float.SignShift != 0; int exp = (int)(floatInt & float.ExponentMask) >> float.ExponentShift; uint sig = floatInt & float.SignificandMask; - if (exp == singleMaxExponent) + if (exp == SingleMaxExponent) { if (sig != 0) // NaN { @@ -510,14 +509,14 @@ public static explicit operator Half(float value) public static explicit operator Half(double value) { - const int doubleMaxExponent = 0x7FF; + const int DoubleMaxExponent = 0x7FF; ulong doubleInt = ToUInt64(value); bool sign = (doubleInt & double.SignMask) >> double.SignShift != 0; int exp = (int)((doubleInt & double.ExponentMask) >> double.ExponentShift); ulong sig = doubleInt & double.SignificandMask; - if (exp == doubleMaxExponent) + if (exp == DoubleMaxExponent) { if (sig != 0) // NaN { @@ -606,7 +605,7 @@ private static (int Exp, uint Sig) NormSubnormalF16Sig(uint sig) #region Utilities private static uint ToUInt32(float value) - => (uint)BitConverter.SingleToInt32Bits(value); + => (uint)BitConverter.SingleToInt32Bits(value); // Significand bits should be shifted towards to the left end before calling these methods // Creates Quiet NaN if significand == 0 @@ -625,7 +624,7 @@ private static unsafe Half CreateHalf(ushort value) private static ushort RoundPackToHalf(bool sign, short exp, ushort sig) { - const int roundIncrement = 0x8; // Depends on rounding mode but it's always towards closest / ties to even + const int RoundIncrement = 0x8; // Depends on rounding mode but it's always towards closest / ties to even int roundBits = sig & 0xF; if ((uint)exp >= 0x1D) @@ -635,13 +634,13 @@ private static ushort RoundPackToHalf(bool sign, short exp, ushort sig) sig = (ushort)ShiftRightJam(sig, -exp); exp = 0; } - else if (exp > 0x1D || sig + roundIncrement >= 0x8000) // Overflow + else if (exp > 0x1D || sig + RoundIncrement >= 0x8000) // Overflow { return sign ? NegativeInfinityBits : PositiveInfinityBits; } } - sig = (ushort)((sig + roundIncrement) >> 4); + sig = (ushort)((sig + RoundIncrement) >> 4); sig &= (ushort)~(((roundBits ^ 8) != 0 ? 0 : 1) & 1); if (sig == 0) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index de7724e81ecad3..6ad8fb8520c036 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -587,7 +587,7 @@ private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int preci public static string FormatHalf(Half value, string? format, NumberFormatInfo info) { Span stackBuffer = stackalloc char[CharStackBufferSize]; - var sb = new ValueStringBuilder(stackBuffer); + var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); return FormatHalf(ref sb, value, format, info) ?? sb.ToString(); } From 6855b32517bb8dbf8f2016056692879c85635e03 Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Fri, 12 Jun 2020 11:00:21 -0700 Subject: [PATCH 08/14] sq --- .../System.Private.CoreLib/src/System/Number.Formatting.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index 6ad8fb8520c036..7db6b1300bb35b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -586,7 +586,6 @@ private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int preci public static string FormatHalf(Half value, string? format, NumberFormatInfo info) { - Span stackBuffer = stackalloc char[CharStackBufferSize]; var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); return FormatHalf(ref sb, value, format, info) ?? sb.ToString(); } @@ -608,8 +607,7 @@ public static bool TryFormatSingle(float value, ReadOnlySpan format, Numbe public static bool TryFormatHalf(Half value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) { - Span stackBuffer = stackalloc char[CharStackBufferSize]; - var sb = new ValueStringBuilder(stackBuffer); + var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); string? s = FormatHalf(ref sb, value, format, info); return s != null ? TryCopyTo(s, destination, out charsWritten) : From e9cf6a62ae25aa38f302a8be75b7f74706da6a8f Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Mon, 22 Jun 2020 13:54:20 -0700 Subject: [PATCH 09/14] Address Tanner's comments --- .../src/System/BitConverter.cs | 6 ++ .../System.Private.CoreLib/src/System/Half.cs | 52 +++++----- .../src/System/Number.Formatting.cs | 96 +++++++++---------- .../src/System/Number.NumberBuffer.cs | 2 +- .../src/System/Number.Parsing.cs | 12 ++- 5 files changed, 87 insertions(+), 81 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs b/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs index bf604f41a97d60..478eb6fa87d8db 100644 --- a/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs @@ -499,5 +499,11 @@ public static unsafe float Int32BitsToSingle(int value) return *((float*)&value); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe short HalfToInt16Bits(Half value) + { + return *((short*)&value); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index 80440183f8c8be..dcccbba5b4252a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -223,7 +223,8 @@ public static bool IsSubnormal(Half value) /// The equivalent value representing the input string. If the input exceeds Half's range, a or is returned. public static Half Parse(string s) { - return (Half)float.Parse(s, style: DefaultParseStyle, provider: null); + if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + return Number.ParseHalf(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo); } /// @@ -234,7 +235,9 @@ public static Half Parse(string s) /// The equivalent value representing the input string. If the input exceeds Half's range, a or is returned. public static Half Parse(string s, NumberStyles style) { - return (Half)float.Parse(s, style); + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + return Number.ParseHalf(s, style, NumberFormatInfo.CurrentInfo); } /// @@ -243,9 +246,10 @@ public static Half Parse(string s, NumberStyles style) /// The input to be parsed. /// A format provider. /// The equivalent value representing the input string. If the input exceeds Half's range, a or is returned. - public static Half Parse(string s, IFormatProvider provider) + public static Half Parse(string s, IFormatProvider? provider) { - return (Half)float.Parse(s, provider); + if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + return Number.ParseHalf(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.GetInstance(provider)); } /// @@ -257,11 +261,9 @@ public static Half Parse(string s, IFormatProvider provider) /// The equivalent value representing the input string. If the input exceeds Half's range, a or is returned. public static Half Parse(string s, NumberStyles style = DefaultParseStyle, IFormatProvider? provider = null) { - if (s is null) - { - throw new ArgumentNullException(nameof(s)); - } - return Parse(s.AsSpan(), style, provider); + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + return Number.ParseHalf(s, style, NumberFormatInfo.GetInstance(provider)); } /// @@ -273,7 +275,8 @@ public static Half Parse(string s, NumberStyles style = DefaultParseStyle, IForm /// The equivalent value representing the input string. If the input exceeds Half's range, a or is returned. public static Half Parse(ReadOnlySpan s, NumberStyles style = DefaultParseStyle, IFormatProvider? provider = null) { - return (Half)(float.Parse(s, style, provider)); + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.ParseHalf(s, style, NumberFormatInfo.GetInstance(provider)); } /// @@ -321,17 +324,8 @@ public static bool TryParse(string s, NumberStyles style, IFormatProvider? provi /// if the parse was successful, otherwise. public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Half result) { - bool ret = false; - if (float.TryParse(s, style, provider, out float floatResult)) - { - result = (Half)floatResult; - ret = true; - } - else - { - result = default(Half); - } - return ret; + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseHalf(s, style, NumberFormatInfo.GetInstance(provider), out result); } private static bool AreZero(Half left, Half right) @@ -483,7 +477,7 @@ public static explicit operator Half(float value) { const int SingleMaxExponent = 0xFF; - uint floatInt = ToUInt32(value); + uint floatInt = SingleToUInt32Bits(value); bool sign = (floatInt & float.SignMask) >> float.SignShift != 0; int exp = (int)(floatInt & float.ExponentMask) >> float.ExponentShift; uint sig = floatInt & float.SignificandMask; @@ -511,7 +505,7 @@ public static explicit operator Half(double value) { const int DoubleMaxExponent = 0x7FF; - ulong doubleInt = ToUInt64(value); + ulong doubleInt = DoubleToUInt64Bits(value); bool sign = (doubleInt & double.SignMask) >> double.SignShift != 0; int exp = (int)((doubleInt & double.ExponentMask) >> double.ExponentShift); ulong sig = doubleInt & double.SignificandMask; @@ -534,7 +528,7 @@ public static explicit operator Half(double value) } // -----------------------Start of from-half conversions------------------------- - public static implicit operator float(Half value) + public static explicit operator float(Half value) { bool sign = IsNegative(value); int exp = value.Exponent; @@ -562,7 +556,7 @@ public static implicit operator float(Half value) return CreateSingle(sign, (byte)(exp + 0x70), sig << 13); } - public static implicit operator double(Half value) + public static explicit operator double(Half value) { bool sign = IsNegative(value); int exp = value.Exponent; @@ -604,7 +598,7 @@ private static (int Exp, uint Sig) NormSubnormalF16Sig(uint sig) #region Utilities - private static uint ToUInt32(float value) + private static uint SingleToUInt32Bits(float value) => (uint)BitConverter.SingleToInt32Bits(value); // Significand bits should be shifted towards to the left end before calling these methods @@ -616,10 +610,10 @@ private static Half CreateHalfNaN(bool sign, ulong significand) uint signInt = (sign ? 1U : 0U) << SignShift; uint sigInt = (uint)(significand >> 54); - return CreateHalf((ushort)(signInt | NaNBits | sigInt)); + return UInt16BitsToHalf((ushort)(signInt | NaNBits | sigInt)); } - private static unsafe Half CreateHalf(ushort value) + private static unsafe Half UInt16BitsToHalf(ushort value) => *(Half*)&value; private static ushort RoundPackToHalf(bool sign, short exp, ushort sig) @@ -660,7 +654,7 @@ private static uint ShiftRightJam(uint i, int dist) private static ulong ShiftRightJam(ulong l, int dist) => dist < 63 ? (l >> dist) | (l << (-dist & 63) != 0 ? 1UL : 0UL) : (l != 0 ? 1UL : 0UL); - private static ulong ToUInt64(double value) + private static ulong DoubleToUInt64Bits(double value) => (ulong)BitConverter.DoubleToInt64Bits(value); private static float CreateSingleNaN(bool sign, ulong significand) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index 7db6b1300bb35b..802a5083bd4068 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -584,12 +584,6 @@ private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int preci return null; } - public static string FormatHalf(Half value, string? format, NumberFormatInfo info) - { - var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); - return FormatHalf(ref sb, value, format, info) ?? sb.ToString(); - } - public static string FormatSingle(float value, string? format, NumberFormatInfo info) { var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); @@ -605,50 +599,43 @@ public static bool TryFormatSingle(float value, ReadOnlySpan format, Numbe sb.TryCopyTo(destination, out charsWritten); } - public static bool TryFormatHalf(Half value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) - { - var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); - string? s = FormatHalf(ref sb, value, format, info); - return s != null ? - TryCopyTo(s, destination, out charsWritten) : - sb.TryCopyTo(destination, out charsWritten); - } - /// Formats the specified value according to the specified format and info. /// /// Non-null if an existing string can be returned, in which case the builder will be unmodified. /// Null if no existing string was returned, in which case the formatted output is in the builder. /// - private static unsafe string? FormatHalf(ref ValueStringBuilder sb, Half value, ReadOnlySpan format, NumberFormatInfo info) + private static unsafe string? FormatSingle(ref ValueStringBuilder sb, float value, ReadOnlySpan format, NumberFormatInfo info) { - if (!Half.IsFinite(value)) + if (!float.IsFinite(value)) { - if (Half.IsNaN(value)) + if (float.IsNaN(value)) { return info.NaNSymbol; } - return Half.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; + return float.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; } char fmt = ParseFormatSpecifier(format, out int precision); - byte* pDigits = stackalloc byte[HalfNumberBufferLength]; + byte* pDigits = stackalloc byte[SingleNumberBufferLength]; if (fmt == '\0') { - precision = HalfPrecisionCustomFormat; + // For back-compat we currently specially treat the precision for custom + // format specifiers. The constant has more details as to why. + precision = SinglePrecisionCustomFormat; } - NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, HalfNumberBufferLength); - number.IsNegative = Half.IsNegative(value); + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, SingleNumberBufferLength); + number.IsNegative = float.IsNegative(value); // We need to track the original precision requested since some formats // accept values like 0 and others may require additional fixups. int nMaxDigits = GetFloatingPointMaxDigitsAndPrecision(fmt, ref precision, info, out bool isSignificantDigits); - if ((value != 0.0) && (!isSignificantDigits || !Grisu3.TryRunHalf(value, precision, ref number))) + if ((value != 0.0f) && (!isSignificantDigits || !Grisu3.TryRunSingle(value, precision, ref number))) { - Dragon4Half(value, precision, isSignificantDigits, ref number); + Dragon4Single(value, precision, isSignificantDigits, ref number); } number.CheckConsistency(); @@ -657,7 +644,7 @@ public static bool TryFormatHalf(Half value, ReadOnlySpan format, NumberFo // because we know we have enough digits to satisfy roundtrippability), we should validate // that the number actually roundtrips back to the original result. - Debug.Assert(((precision != -1) && (precision < HalfPrecision)) || (HalfToUInt16Bits(value) == HalfToUInt16Bits(NumberToHalf(ref number)))); + Debug.Assert(((precision != -1) && (precision < SinglePrecision)) || (BitConverter.SingleToInt32Bits(value) == BitConverter.SingleToInt32Bits(NumberToSingle(ref number)))); if (fmt != 0) { @@ -677,49 +664,53 @@ public static bool TryFormatHalf(Half value, ReadOnlySpan format, NumberFo } else { - Debug.Assert(precision == HalfPrecisionCustomFormat); + Debug.Assert(precision == SinglePrecisionCustomFormat); NumberToStringFormat(ref sb, ref number, format, info); } return null; } + public static string FormatHalf(Half value, string? format, NumberFormatInfo info) + { + var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); + return FormatHalf(ref sb, value, format, info) ?? sb.ToString(); + } + /// Formats the specified value according to the specified format and info. /// /// Non-null if an existing string can be returned, in which case the builder will be unmodified. /// Null if no existing string was returned, in which case the formatted output is in the builder. /// - private static unsafe string? FormatSingle(ref ValueStringBuilder sb, float value, ReadOnlySpan format, NumberFormatInfo info) + private static unsafe string? FormatHalf(ref ValueStringBuilder sb, Half value, ReadOnlySpan format, NumberFormatInfo info) { - if (!float.IsFinite(value)) + if (!Half.IsFinite(value)) { - if (float.IsNaN(value)) + if (Half.IsNaN(value)) { return info.NaNSymbol; } - return float.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; + return Half.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol; } char fmt = ParseFormatSpecifier(format, out int precision); - byte* pDigits = stackalloc byte[SingleNumberBufferLength]; + byte* pDigits = stackalloc byte[HalfNumberBufferLength]; if (fmt == '\0') { - // For back-compat we currently specially treat the precision for custom - // format specifiers. The constant has more details as to why. - precision = SinglePrecisionCustomFormat; + precision = HalfPrecisionCustomFormat; } - NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, SingleNumberBufferLength); - number.IsNegative = float.IsNegative(value); + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, HalfNumberBufferLength); + number.IsNegative = Half.IsNegative(value); // We need to track the original precision requested since some formats // accept values like 0 and others may require additional fixups. int nMaxDigits = GetFloatingPointMaxDigitsAndPrecision(fmt, ref precision, info, out bool isSignificantDigits); - if ((value != 0.0f) && (!isSignificantDigits || !Grisu3.TryRunSingle(value, precision, ref number))) + if (((double)value != 0.0) && (!isSignificantDigits || !Grisu3.TryRunHalf(value, precision, ref number))) { - Dragon4Single(value, precision, isSignificantDigits, ref number); + Dragon4Half(value, precision, isSignificantDigits, ref number); } number.CheckConsistency(); @@ -728,7 +719,7 @@ public static bool TryFormatHalf(Half value, ReadOnlySpan format, NumberFo // because we know we have enough digits to satisfy roundtrippability), we should validate // that the number actually roundtrips back to the original result. - Debug.Assert(((precision != -1) && (precision < SinglePrecision)) || (BitConverter.SingleToInt32Bits(value) == BitConverter.SingleToInt32Bits(NumberToSingle(ref number)))); + Debug.Assert(((precision != -1) && (precision < HalfPrecision)) || (BitConverter.HalfToInt16Bits(value) == BitConverter.HalfToInt16Bits(NumberToHalf(ref number)))); if (fmt != 0) { @@ -742,18 +733,28 @@ public static bool TryFormatHalf(Half value, ReadOnlySpan format, NumberFo // less digits. One example this fixes is "-60", which would otherwise be formatted as "-6E+01" // since DigitsCount would be 1 and the formatter would almost immediately switch to scientific notation. - nMaxDigits = Math.Max(number.DigitsCount, SinglePrecision); + nMaxDigits = Math.Max(number.DigitsCount, HalfPrecision); } NumberToString(ref sb, ref number, fmt, nMaxDigits, info); } else { - Debug.Assert(precision == SinglePrecisionCustomFormat); + Debug.Assert(precision == HalfPrecisionCustomFormat); NumberToStringFormat(ref sb, ref number, format, info); } return null; } + public static bool TryFormatHalf(Half value, ReadOnlySpan format, NumberFormatInfo info, Span destination, out int charsWritten) + { + var sb = new ValueStringBuilder(stackalloc char[CharStackBufferSize]); + string? s = FormatHalf(ref sb, value, format, info); + return s != null ? + TryCopyTo(s, destination, out charsWritten) : + sb.TryCopyTo(destination, out charsWritten); + } + + private static bool TryCopyTo(string source, Span destination, out int charsWritten) { Debug.Assert(source != null); @@ -2649,20 +2650,15 @@ private static ulong ExtractFractionAndBiasedExponent(double value, out int expo return fraction; } - private static unsafe ushort HalfToUInt16Bits(Half value) - { - return *((ushort*)&value); - } - private static ushort ExtractFractionAndBiasedExponent(Half value, out int exponent) { - ushort bits = HalfToUInt16Bits(value); + ushort bits = (ushort)BitConverter.HalfToInt16Bits(value); ushort fraction = (ushort)(bits & 0x3FF); exponent = ((int)(bits >> 10) & 0xFF); if (exponent != 0) { - // For normalized value, according to https://en.wikipedia.org/wiki/Single-precision_floating-point_format + // For normalized value, according to https://en.wikipedia.org/wiki/Half-precision_floating-point_format // value = 1.fraction * 2^(exp - 15) // = (1 + mantissa / 2^10) * 2^(exp - 15) // = (2^10 + mantissa) * 2^(exp - 15 - 10) @@ -2674,7 +2670,7 @@ private static ushort ExtractFractionAndBiasedExponent(Half value, out int expon } else { - // For denormalized value, according to https://en.wikipedia.org/wiki/Single-precision_floating-point_format + // For denormalized value, according to https://en.wikipedia.org/wiki/Half-precision_floating-point_format // value = 0.fraction * 2^(1 - 15) // = (mantissa / 2^10) * 2^(-14) // = mantissa * 2^(-14 - 10) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs b/src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs index 994ddf2c3ce3c0..b258efc74aa3cf 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs @@ -16,7 +16,7 @@ internal static partial class Number internal const int Int32NumberBufferLength = 10 + 1; // 10 for the longest input: 2,147,483,647 internal const int Int64NumberBufferLength = 19 + 1; // 19 for the longest input: 9,223,372,036,854,775,807 internal const int SingleNumberBufferLength = 112 + 1 + 1; // 112 for the longest input + 1 for rounding: 1.40129846E-45 - internal const int HalfNumberBufferLength = 21; + internal const int HalfNumberBufferLength = 21; // 19 for the longest input + 1 for rounding (+1 for the null terminator) internal const int UInt32NumberBufferLength = 10 + 1; // 10 for the longest input: 4,294,967,295 internal const int UInt64NumberBufferLength = 20 + 1; // 20 for the longest input: 18,446,744,073,709,551,615 diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs index aae3a5b3d9b3e2..fb2949089846bd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -1710,6 +1710,16 @@ internal static float ParseSingle(ReadOnlySpan value, NumberStyles styles, return result; } + internal static Half ParseHalf(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) + { + if (!TryParseHalf(value, styles, info, out Half result)) + { + ThrowOverflowOrFormatException(ParsingStatus.Failed); + } + + return result; + } + internal static unsafe ParsingStatus TryParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out decimal result) { byte* pDigits = stackalloc byte[DecimalNumberBufferLength]; @@ -2076,7 +2086,7 @@ internal static Half NumberToHalf(ref NumberBuffer number) if ((number.DigitsCount == 0) || (number.Scale < HalfMinExponent)) { - result = (Half)0; + result = default; } else if (number.Scale > HalfMaxExponent) { From 6cc1c6c1f11fa44a5b834394f2e821046197d575 Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Mon, 22 Jun 2020 17:17:04 -0700 Subject: [PATCH 10/14] Clean up the unit tests --- .../src/System/Number.DiyFp.cs | 21 ++++++++++++++++ .../src/System/Number.Dragon4.cs | 2 +- .../src/System/Number.Grisu3.cs | 2 +- .../Number.NumberToFloatingPointBits.cs | 14 ++++++++--- .../System.Runtime/ref/System.Runtime.cs | 4 ++-- .../System.Runtime/tests/System/HalfTests.cs | 24 ++++++++----------- 6 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs index 90359cea2e97a2..2b2e34ec9065aa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DiyFp.cs @@ -55,6 +55,20 @@ public static DiyFp CreateAndGetBoundaries(float value, out DiyFp mMinus, out Di return result; } + // Computes the two boundaries of value. + // + // The bigger boundary (mPlus) is normalized. + // The lower boundary has the same exponent as mPlus. + // + // Precondition: + // The value encoded by value must be greater than 0. + public static DiyFp CreateAndGetBoundaries(Half value, out DiyFp mMinus, out DiyFp mPlus) + { + var result = new DiyFp(value); + result.GetBoundaries(HalfImplicitBitIndex, out mMinus, out mPlus); + return result; + } + public DiyFp(double value) { Debug.Assert(double.IsFinite(value)); @@ -69,6 +83,13 @@ public DiyFp(float value) f = ExtractFractionAndBiasedExponent(value, out e); } + public DiyFp(Half value) + { + Debug.Assert(Half.IsFinite(value)); + Debug.Assert((float)value > 0.0f); + f = ExtractFractionAndBiasedExponent(value, out e); + } + public DiyFp(ulong f, int e) { this.f = f; diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs index 1799a7415438d9..d753124b3de0ad 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs @@ -45,7 +45,7 @@ public static unsafe void Dragon4Half(Half value, int cutoffNumber, bool isSigni { Half v = Half.IsNegative(value) ? Half.Negate(value) : value; - Debug.Assert(v > 0); + Debug.Assert((double)v > 0.0); Debug.Assert(Half.IsFinite(v)); ushort mantissa = ExtractFractionAndBiasedExponent(value, out int exponent); diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs index 84d39a5ee415d7..064d9fec187bfa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Grisu3.cs @@ -360,7 +360,7 @@ public static bool TryRunHalf(Half value, int requestedDigits, ref NumberBuffer { Half v = Half.IsNegative(value) ? Half.Negate(value) : value; - Debug.Assert(v > 0); + Debug.Assert((double)v > 0); Debug.Assert(Half.IsFinite(v)); int length; diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs index 078292e827c5fc..5563441a0a5249 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs @@ -373,7 +373,7 @@ private static ulong NumberToFloatingPointBits(ref NumberBuffer number, in Float byte* src = number.GetDigitsPointer(); - if ((info.DenormalMantissaBits == 23) && (totalDigits <= 7) && (fastExponent <= 10)) + if ((info.DenormalMantissaBits <= 23) && (totalDigits <= 7) && (fastExponent <= 10)) { // It is only valid to do this optimization for single-precision floating-point // values since we can lose some of the mantissa bits and would return the @@ -391,6 +391,10 @@ private static ulong NumberToFloatingPointBits(ref NumberBuffer number, in Float result *= scale; } + if (info.DenormalMantissaBits == 10) + { + return (ushort)(BitConverter.HalfToInt16Bits((Half)result)); + } return (uint)(BitConverter.SingleToInt32Bits(result)); } @@ -412,11 +416,15 @@ private static ulong NumberToFloatingPointBits(ref NumberBuffer number, in Float { return (ulong)(BitConverter.DoubleToInt64Bits(result)); } - else + else if (info.DenormalMantissaBits == 23) { - Debug.Assert(info.DenormalMantissaBits == 23); return (uint)(BitConverter.SingleToInt32Bits((float)(result))); } + else + { + Debug.Assert(info.DenormalMantissaBits == 10); + return (uint)(BitConverter.HalfToInt16Bits((Half)(result))); + } } return NumberToFloatingPointBitsSlow(ref number, in info, positiveExponent, integerDigitsPresent, fractionalDigitsPresent); diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 9e9576b445d9f1..b40ad7d9068e18 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -2221,8 +2221,8 @@ public partial struct Guid : System.IComparable, System.IComparable         public static explicit operator System.Half (float value) { throw null; }         public static bool operator >(System.Half left, System.Half right) { throw null; }         public static bool operator >=(System.Half left, System.Half right) { throw null; } -        public static implicit operator double (System.Half value) { throw null; } -        public static implicit operator float (System.Half value) { throw null; } +        public static explicit operator double (System.Half value) { throw null; } +        public static explicit operator float (System.Half value) { throw null; }         public static bool operator !=(System.Half left, System.Half right) { throw null; }         public static bool operator <(System.Half left, System.Half right) { throw null; }         public static bool operator <=(System.Half left, System.Half right) { throw null; } diff --git a/src/libraries/System.Runtime/tests/System/HalfTests.cs b/src/libraries/System.Runtime/tests/System/HalfTests.cs index 6202391539e7ff..272567750fbf00 100644 --- a/src/libraries/System.Runtime/tests/System/HalfTests.cs +++ b/src/libraries/System.Runtime/tests/System/HalfTests.cs @@ -266,7 +266,7 @@ public static IEnumerable CompareTo_ThrowsArgumentException_TestData() [MemberData(nameof(CompareTo_ThrowsArgumentException_TestData))] public static void CompareTo_ThrowsArgumentException(object obj) { - Assert.Throws(typeof(ArgumentException), () => Half.MaxValue.CompareTo(obj)); + Assert.Throws(() => Half.MaxValue.CompareTo(obj)); } public static IEnumerable CompareTo_TestData() @@ -339,7 +339,7 @@ public static IEnumerable Equals_TestData() [Theory] [MemberData(nameof(Equals_TestData))] - public static void Equals(Half value, object obj, bool expected) + public static void EqualsTest(Half value, object obj, bool expected) { Assert.Equal(expected, value.Equals(obj)); } @@ -363,7 +363,6 @@ public static IEnumerable ExplicitConversion_ToSingle_TestData() (UInt16BitsToHalf(0b0_11111_1010101010), BitConverter.Int32BitsToSingle(0x7FD54000)), // Positive Signalling NaN - Should preserve payload (UInt16BitsToHalf(0b1_11111_1010101010), BitConverter.Int32BitsToSingle(unchecked((int)0xFFD54000))), // Negative Signalling NaN - Should preserve payload (Half.Epsilon, 1/16777216f), // PosEpsilon = 0.000000059605... - (-Half.Epsilon, -1/16777216f), // NegEpsilon = 0.000000059605... (UInt16BitsToHalf(0), 0), // 0 (UInt16BitsToHalf(0b1_00000_0000000000), -0f), // -0 (UInt16BitsToHalf(0b0_10000_1001001000), 3.140625f), // 3.140625 @@ -409,7 +408,6 @@ public static IEnumerable ExplicitConversion_ToDouble_TestData() (UInt16BitsToHalf(0b0_11111_1010101010), BitConverter.Int64BitsToDouble(0x7FFAA800_00000000)), // Positive Signalling NaN - Should preserve payload (UInt16BitsToHalf(0b1_11111_1010101010), BitConverter.Int64BitsToDouble(unchecked((long)0xFFFAA800_00000000))), // Negative Signalling NaN - Should preserve payload (Half.Epsilon, 1/16777216d), // PosEpsilon = 0.000000059605... - (-Half.Epsilon, -1/16777216d), // NegEpsilon = 0.000000059605... (UInt16BitsToHalf(0), 0d), // 0 (UInt16BitsToHalf(0b1_00000_0000000000), -0d), // -0 (UInt16BitsToHalf(0b0_10000_1001001000), 3.140625d), // 3.140625 @@ -685,7 +683,7 @@ public static IEnumerable ToString_TestData() yield return new object[] { float.NaN, "G", null, "NaN" }; - //yield return new object[] { 2468.0f, "N", null, "2,468.00" }; + yield return new object[] { 2468.0f, "N", null, "2,468.00" }; // Changing the negative pattern doesn't do anything without also passing in a format string var customNegativePattern = new NumberFormatInfo() { NumberNegativePattern = 0 }; @@ -697,8 +695,8 @@ public static IEnumerable ToString_TestData() NumberDecimalSeparator = "~", NumberGroupSeparator = "*" }; - //yield return new object[] { -2468.0f, "N", customNegativeSignDecimalGroupSeparator, "#2*468~00" }; - //yield return new object[] { 2468.0f, "N", customNegativeSignDecimalGroupSeparator, "2*468~00" }; + yield return new object[] { -2468.0f, "N", customNegativeSignDecimalGroupSeparator, "#2*468~00" }; + yield return new object[] { 2468.0f, "N", customNegativeSignDecimalGroupSeparator, "2*468~00" }; var customNegativeSignGroupSeparatorNegativePattern = new NumberFormatInfo() { @@ -706,7 +704,7 @@ public static IEnumerable ToString_TestData() NumberGroupSeparator = "*", NumberNegativePattern = 0 }; - //yield return new object[] { -2468.0f, "N", customNegativeSignGroupSeparatorNegativePattern, "(2*468.00)" }; + yield return new object[] { -2468.0f, "N", customNegativeSignGroupSeparatorNegativePattern, "(2*468.00)" }; NumberFormatInfo invariantFormat = NumberFormatInfo.InvariantInfo; yield return new object[] { float.NaN, "G", invariantFormat, "NaN" }; @@ -724,21 +722,20 @@ public static IEnumerable ToString_TestData_NotNetFramework() yield return new object[] { Half.MinValue, "G", null, "-65504" }; yield return new object[] { Half.MaxValue, "G", null, "65504" }; - //yield return new object[] { Half.Epsilon, "G", null, "5.9604645E-08" }; + yield return new object[] { Half.Epsilon, "G", null, "5.9604645E-08" }; NumberFormatInfo invariantFormat = NumberFormatInfo.InvariantInfo; - //yield return new object[] { Half.Epsilon, "G", invariantFormat, "5.9604645E-08" }; + yield return new object[] { Half.Epsilon, "G", invariantFormat, "5.9604645E-08" }; } [Fact] public static void Test_ToString_NotNetFramework() { - //using (new ThreadCultureChange(CultureInfo.InvariantCulture)) + using (new ThreadCultureChange(CultureInfo.InvariantCulture)) { foreach (object[] testdata in ToString_TestData_NotNetFramework()) { ToStringTest(testdata[0] is float floatData ? (Half)floatData : (Half)testdata[0], (string)testdata[1], (IFormatProvider)testdata[2], (string)testdata[3]); - //ToStringTest((Half)(float)testdata[0], (string)testdata[1], (IFormatProvider)testdata[2], (string)testdata[3]); } } } @@ -827,7 +824,7 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP [Fact] public static void TryFormat() { - //using (new ThreadCultureChange(CultureInfo.InvariantCulture)) + using (new ThreadCultureChange(CultureInfo.InvariantCulture)) { foreach (object[] testdata in ToString_TestData()) { @@ -875,7 +872,6 @@ public static IEnumerable ToStringRoundtrip_TestData() yield return new object[] { Half.MinValue }; yield return new object[] { -MathF.PI }; yield return new object[] { -MathF.E }; - yield return new object[] { -Half.Epsilon }; yield return new object[] { -0.845512408f }; yield return new object[] { -0.0f }; yield return new object[] { Half.NaN }; From da5638f62559faa6003ea2c46df10870c87c00a5 Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Tue, 23 Jun 2020 11:28:59 -0700 Subject: [PATCH 11/14] Address nits and bug fix --- .../src/System/BitConverter.cs | 4 +++ .../System.Private.CoreLib/src/System/Half.cs | 30 +++++-------------- .../src/System/Number.Formatting.cs | 6 ++-- .../System.Runtime/tests/System/HalfTests.cs | 22 +++++++------- 4 files changed, 25 insertions(+), 37 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs b/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs index 478eb6fa87d8db..f05633bc4c6e07 100644 --- a/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs @@ -505,5 +505,9 @@ internal static unsafe short HalfToInt16Bits(Half value) { return *((short*)&value); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe Half Int16BitsToHalf(short value) + => *(Half*)&value; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index dcccbba5b4252a..c8c741000a7705 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -55,7 +55,6 @@ namespace System private const ushort MinValueBits = 0xFBFF; private const ushort MaxValueBits = 0x7BFF; - // Well-defined and commonly used values public static Half Epsilon => new Half(EpsilonBits); // 5.9604645E-08 @@ -477,7 +476,7 @@ public static explicit operator Half(float value) { const int SingleMaxExponent = 0xFF; - uint floatInt = SingleToUInt32Bits(value); + uint floatInt = (uint)BitConverter.SingleToInt32Bits(value); bool sign = (floatInt & float.SignMask) >> float.SignShift != 0; int exp = (int)(floatInt & float.ExponentMask) >> float.ExponentShift; uint sig = floatInt & float.SignificandMask; @@ -505,7 +504,7 @@ public static explicit operator Half(double value) { const int DoubleMaxExponent = 0x7FF; - ulong doubleInt = DoubleToUInt64Bits(value); + ulong doubleInt = (ulong)BitConverter.DoubleToInt64Bits(value); bool sign = (doubleInt & double.SignMask) >> double.SignShift != 0; int exp = (int)((doubleInt & double.ExponentMask) >> double.ExponentShift); ulong sig = doubleInt & double.SignificandMask; @@ -547,7 +546,7 @@ public static explicit operator float(Half value) { if (sig == 0) { - return CreateSingle(sign ? float.SignMask : 0); // Positive / Negative zero + return BitConverter.Int32BitsToSingle((int)(sign ? float.SignMask : 0)); // Positive / Negative zero } (exp, sig) = NormSubnormalF16Sig(sig); exp -= 1; @@ -575,7 +574,7 @@ public static explicit operator double(Half value) { if (sig == 0) { - return CreateDouble(sign ? double.SignMask : 0); // Positive / Negative zero + return BitConverter.Int64BitsToDouble((long)(sign ? double.SignMask : 0)); // Positive / Negative zero } (exp, sig) = NormSubnormalF16Sig(sig); exp -= 1; @@ -598,9 +597,6 @@ private static (int Exp, uint Sig) NormSubnormalF16Sig(uint sig) #region Utilities - private static uint SingleToUInt32Bits(float value) - => (uint)BitConverter.SingleToInt32Bits(value); - // Significand bits should be shifted towards to the left end before calling these methods // Creates Quiet NaN if significand == 0 private static Half CreateHalfNaN(bool sign, ulong significand) @@ -610,12 +606,9 @@ private static Half CreateHalfNaN(bool sign, ulong significand) uint signInt = (sign ? 1U : 0U) << SignShift; uint sigInt = (uint)(significand >> 54); - return UInt16BitsToHalf((ushort)(signInt | NaNBits | sigInt)); + return BitConverter.Int16BitsToHalf((short)(signInt | NaNBits | sigInt)); } - private static unsafe Half UInt16BitsToHalf(ushort value) - => *(Half*)&value; - private static ushort RoundPackToHalf(bool sign, short exp, ushort sig) { const int RoundIncrement = 0x8; // Depends on rounding mode but it's always towards closest / ties to even @@ -654,9 +647,6 @@ private static uint ShiftRightJam(uint i, int dist) private static ulong ShiftRightJam(ulong l, int dist) => dist < 63 ? (l >> dist) | (l << (-dist & 63) != 0 ? 1UL : 0UL) : (l != 0 ? 1UL : 0UL); - private static ulong DoubleToUInt64Bits(double value) - => (ulong)BitConverter.DoubleToInt64Bits(value); - private static float CreateSingleNaN(bool sign, ulong significand) { const uint NaNBits = float.ExponentMask | 0x400000; // Most significant significand bit @@ -664,7 +654,7 @@ private static float CreateSingleNaN(bool sign, ulong significand) uint signInt = (sign ? 1U : 0U) << float.SignShift; uint sigInt = (uint)(significand >> 41); - return CreateSingle(signInt | NaNBits | sigInt); + return BitConverter.Int32BitsToSingle((int)(signInt | NaNBits | sigInt)); } private static double CreateDoubleNaN(bool sign, ulong significand) @@ -674,15 +664,9 @@ private static double CreateDoubleNaN(bool sign, ulong significand) ulong signInt = (sign ? 1UL : 0UL) << double.SignShift; ulong sigInt = significand >> 12; - return CreateDouble(signInt | NaNBits | sigInt); + return BitConverter.Int64BitsToDouble((long)(signInt | NaNBits | sigInt)); } - private static double CreateDouble(ulong value) - => BitConverter.Int64BitsToDouble((long)value); - - private static float CreateSingle(uint value) - => BitConverter.Int32BitsToSingle((int)value); - private static float CreateSingle(bool sign, byte exp, uint sig) => BitConverter.Int32BitsToSingle((int)(((sign ? 1U : 0U) << float.SignShift) | ((uint)exp << float.ExponentShift) | sig)); diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index 802a5083bd4068..4b61b427d64b6b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -633,7 +633,7 @@ public static bool TryFormatSingle(float value, ReadOnlySpan format, Numbe // accept values like 0 and others may require additional fixups. int nMaxDigits = GetFloatingPointMaxDigitsAndPrecision(fmt, ref precision, info, out bool isSignificantDigits); - if ((value != 0.0f) && (!isSignificantDigits || !Grisu3.TryRunSingle(value, precision, ref number))) + if ((value != default) && (!isSignificantDigits || !Grisu3.TryRunSingle(value, precision, ref number))) { Dragon4Single(value, precision, isSignificantDigits, ref number); } @@ -708,7 +708,7 @@ public static string FormatHalf(Half value, string? format, NumberFormatInfo inf // accept values like 0 and others may require additional fixups. int nMaxDigits = GetFloatingPointMaxDigitsAndPrecision(fmt, ref precision, info, out bool isSignificantDigits); - if (((double)value != 0.0) && (!isSignificantDigits || !Grisu3.TryRunHalf(value, precision, ref number))) + if ((value != default) && (!isSignificantDigits || !Grisu3.TryRunHalf(value, precision, ref number))) { Dragon4Half(value, precision, isSignificantDigits, ref number); } @@ -2654,7 +2654,7 @@ private static ushort ExtractFractionAndBiasedExponent(Half value, out int expon { ushort bits = (ushort)BitConverter.HalfToInt16Bits(value); ushort fraction = (ushort)(bits & 0x3FF); - exponent = ((int)(bits >> 10) & 0xFF); + exponent = ((int)(bits >> 10) & 0x1F); if (exponent != 0) { diff --git a/src/libraries/System.Runtime/tests/System/HalfTests.cs b/src/libraries/System.Runtime/tests/System/HalfTests.cs index 272567750fbf00..0a57cc2f1b49b8 100644 --- a/src/libraries/System.Runtime/tests/System/HalfTests.cs +++ b/src/libraries/System.Runtime/tests/System/HalfTests.cs @@ -350,8 +350,8 @@ public static IEnumerable ExplicitConversion_ToSingle_TestData() { (UInt16BitsToHalf(0b0_01111_0000000000), 1f), // 1 (UInt16BitsToHalf(0b1_01111_0000000000), -1f), // -1 - (Half.MaxValue, 65504f), // 65504 - (Half.MinValue, -65504f), // -65504 + (Half.MaxValue, 65504f), // 65500 + (Half.MinValue, -65504f), // -65500 (UInt16BitsToHalf(0b0_01011_1001100110), 0.0999755859375f), // 0.1ish (UInt16BitsToHalf(0b1_01011_1001100110), -0.0999755859375f), // -0.1ish (UInt16BitsToHalf(0b0_10100_0101000000), 42f), // 42 @@ -395,8 +395,8 @@ public static IEnumerable ExplicitConversion_ToDouble_TestData() { (UInt16BitsToHalf(0b0_01111_0000000000), 1d), // 1 (UInt16BitsToHalf(0b1_01111_0000000000), -1d), // -1 - (Half.MaxValue, 65504d), // 65504 - (Half.MinValue, -65504d), // -65504 + (Half.MaxValue, 65504d), // 65500 + (Half.MinValue, -65504d), // -65500 (UInt16BitsToHalf(0b0_01011_1001100110), 0.0999755859375d), // 0.1ish (UInt16BitsToHalf(0b1_01011_1001100110), -0.0999755859375d), // -0.1ish (UInt16BitsToHalf(0b0_10100_0101000000), 42d), // 42 @@ -677,9 +677,9 @@ public static void Parse_Invalid(string value, NumberStyles style, IFormatProvid public static IEnumerable ToString_TestData() { - yield return new object[] { -4568.0f, "G", null, "-4568" }; + yield return new object[] { -4570.0f, "G", null, "-4570" }; yield return new object[] { 0.0f, "G", null, "0" }; - yield return new object[] { 4568.0f, "G", null, "4568" }; + yield return new object[] { 4570.0f, "G", null, "4570" }; yield return new object[] { float.NaN, "G", null, "NaN" }; @@ -687,7 +687,7 @@ public static IEnumerable ToString_TestData() // Changing the negative pattern doesn't do anything without also passing in a format string var customNegativePattern = new NumberFormatInfo() { NumberNegativePattern = 0 }; - yield return new object[] { -6312.0f, "G", customNegativePattern, "-6312" }; + yield return new object[] { -6310.0f, "G", customNegativePattern, "-6310" }; var customNegativeSignDecimalGroupSeparator = new NumberFormatInfo() { @@ -719,13 +719,13 @@ public static IEnumerable ToString_TestData_NotNetFramework() yield return testData; } - yield return new object[] { Half.MinValue, "G", null, "-65504" }; - yield return new object[] { Half.MaxValue, "G", null, "65504" }; + yield return new object[] { Half.MinValue, "G", null, "-65500" }; + yield return new object[] { Half.MaxValue, "G", null, "65500" }; - yield return new object[] { Half.Epsilon, "G", null, "5.9604645E-08" }; + yield return new object[] { Half.Epsilon, "G", null, "6E-08" }; NumberFormatInfo invariantFormat = NumberFormatInfo.InvariantInfo; - yield return new object[] { Half.Epsilon, "G", invariantFormat, "5.9604645E-08" }; + yield return new object[] { Half.Epsilon, "G", invariantFormat, "6E-08" }; } [Fact] From 8e3c9779de7b0272bd9590e4b19c7ad16454e526 Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Tue, 23 Jun 2020 11:36:22 -0700 Subject: [PATCH 12/14] sq --- .../System.Private.CoreLib/src/System/BitConverter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs b/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs index f05633bc4c6e07..50f1e0e8fec10c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs @@ -508,6 +508,8 @@ internal static unsafe short HalfToInt16Bits(Half value) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static unsafe Half Int16BitsToHalf(short value) - => *(Half*)&value; + { + return *(Half*)&value; + } } } From 6b85b56fa5abb80ff0115503dcf16bee67fe178e Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Wed, 24 Jun 2020 11:45:44 -0700 Subject: [PATCH 13/14] Nullability attrib and bug fix --- .../System.Private.CoreLib/src/System/Half.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index c8c741000a7705..6af26d03404f34 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; using System.Runtime.InteropServices; @@ -284,8 +285,13 @@ public static Half Parse(ReadOnlySpan s, NumberStyles style = DefaultParse /// The input to be parsed. /// The equivalent value representing the input string if the parse was successful. If the input exceeds Half's range, a or is returned. If the parse was unsuccessful, a default value is returned. /// if the parse was successful, otherwise. - public static bool TryParse(string s, out Half result) + public static bool TryParse([NotNullWhen(true)] string? s, out Half result) { + if (s == null) + { + result = default; + return false; + } return TryParse(s, DefaultParseStyle, provider: null, out result); } @@ -308,8 +314,16 @@ public static bool TryParse(ReadOnlySpan s, out Half result) /// A format provider. /// The equivalent value representing the input string if the parse was successful. If the input exceeds Half's range, a or is returned. If the parse was unsuccessful, a default value is returned. /// if the parse was successful, otherwise. - public static bool TryParse(string s, NumberStyles style, IFormatProvider? provider, out Half result) + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Half result) { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + + if (s == null) + { + result = 0; + return false; + } + return TryParse(s.AsSpan(), style, provider, out result); } From 3cbae9224fc1de97dd531544dba2d85d4906e8cf Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Wed, 24 Jun 2020 15:25:00 -0700 Subject: [PATCH 14/14] sq --- src/libraries/System.Private.CoreLib/src/System/Half.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index 6af26d03404f34..1fce4d1b6870ff 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -320,7 +320,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I if (s == null) { - result = 0; + result = default; return false; }