diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
index 671183f4096f24..52cce0d6db9f02 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/BitConverter.cs b/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs
index bf604f41a97d60..50f1e0e8fec10c 100644
--- a/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs
@@ -499,5 +499,17 @@ public static unsafe float Int32BitsToSingle(int value)
return *((float*)&value);
}
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static unsafe short HalfToInt16Bits(Half value)
+ {
+ return *((short*)&value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static unsafe Half Int16BitsToHalf(short value)
+ {
+ return *(Half*)&value;
+ }
}
}
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..1fce4d1b6870ff
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs
@@ -0,0 +1,692 @@
+// 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.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Numerics;
+using System.Runtime.InteropServices;
+
+namespace System
+{
+ // Portions of the code implemented below are based on the 'Berkeley SoftFloat Release 3e' algorithms.
+
+ ///
+ /// An IEEE 754 compliant float16 type.
+ ///
+ [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;
+
+ 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;
+
+ // Well-defined and commonly used values
+
+ public static Half Epsilon => new Half(EpsilonBits); // 5.9604645E-08
+
+ public static Half PositiveInfinity => new Half(PositiveInfinityBits); // 1.0 / 0.0;
+
+ public static Half NegativeInfinity => new Half(NegativeInfinityBits); // -1.0 / 0.0
+
+ public static Half NaN => new Half(NegativeQNaNBits); // 0.0 / 0.0
+
+ public static Half MinValue => new Half(MinValueBits); // -65504
+
+ 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 _value;
+
+ internal Half(ushort value)
+ {
+ _value = value;
+ }
+
+ private Half(bool sign, ushort exp, ushort sig)
+ => _value = (ushort)(((sign ? 1 : 0) << SignShift) + (exp << ExponentShift) + sig);
+
+ private sbyte Exponent
+ {
+ get
+ {
+ return (sbyte)((_value & ExponentMask) >> ExponentShift);
+ }
+ }
+
+ private ushort Significand
+ {
+ get
+ {
+ return (ushort)((_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._value) < (short)(right._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._value) <= (short)(right._value);
+ }
+
+ public static bool operator >=(Half left, Half right)
+ {
+ return right <= left;
+ }
+
+ public static bool operator ==(Half left, Half right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Half left, Half right)
+ {
+ return !(left.Equals(right));
+ }
+
+ /// Determines whether the specified value is finite (zero, subnormal, or normal).
+ public static bool IsFinite(Half value)
+ {
+ return StripSign(value) < PositiveInfinityBits;
+ }
+
+ /// Determines whether the specified value is infinite.
+ public static bool IsInfinity(Half value)
+ {
+ return StripSign(value) == PositiveInfinityBits;
+ }
+
+ /// Determines whether the specified value is NaN.
+ public static bool IsNaN(Half value)
+ {
+ return StripSign(value) > PositiveInfinityBits;
+ }
+
+ /// Determines whether the specified value is negative.
+ public static bool IsNegative(Half value)
+ {
+ return (short)(value._value) < 0;
+ }
+
+ /// Determines whether the specified value is negative infinity.
+ public static bool IsNegativeInfinity(Half value)
+ {
+ return value._value == NegativeInfinityBits;
+ }
+
+ /// Determines whether the specified value is normal.
+ // This is probably not worth inlining, it has branches and should be rarely called
+ public static bool IsNormal(Half 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)
+ }
+
+ /// Determines whether the specified value is positive infinity.
+ public static bool IsPositiveInfinity(Half value)
+ {
+ return value._value == PositiveInfinityBits;
+ }
+
+ /// Determines whether the specified value is subnormal.
+ // This is probably not worth inlining, it has branches and should be rarely called
+ public static bool IsSubnormal(Half value)
+ {
+ uint absValue = StripSign(value);
+ return (absValue < PositiveInfinityBits) // is finite
+ && (absValue != 0) // is not zero
+ && ((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)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseHalf(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo);
+ }
+
+ ///
+ /// 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)
+ {
+ NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseHalf(s, style, NumberFormatInfo.CurrentInfo);
+ }
+
+ ///
+ /// 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)
+ {
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseHalf(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.GetInstance(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)
+ {
+ NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
+ if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ return Number.ParseHalf(s, style, NumberFormatInfo.GetInstance(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)
+ {
+ NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
+ return Number.ParseHalf(s, style, NumberFormatInfo.GetInstance(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([NotNullWhen(true)] string? s, out Half result)
+ {
+ if (s == null)
+ {
+ result = default;
+ return false;
+ }
+ 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([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Half result)
+ {
+ NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
+
+ if (s == null)
+ {
+ result = default;
+ return false;
+ }
+
+ 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)
+ {
+ NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
+ return Number.TryParseHalf(s, style, NumberFormatInfo.GetInstance(provider), out result);
+ }
+
+ private static bool AreZero(Half left, Half right)
+ {
+ // IEEE defines that positive and negative zero are equal, this gives us a quick equality check
+ // for two values by or'ing the private bits together and stripping the sign. They are both zero,
+ // and therefore equivalent, if the resulting value is still zero.
+ return (ushort)((left._value | right._value) & ~SignMask) == 0;
+ }
+
+ private static bool IsNaNOrZero(Half value)
+ {
+ return ((value._value - 1) & ~SignMask) >= PositiveInfinityBits;
+ }
+
+ private static uint StripSign(Half value)
+ {
+ return (ushort)(value._value & ~SignMask);
+ }
+
+ ///
+ /// 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))
+ {
+ return (obj is null) ? 1 : throw new ArgumentException(SR.Arg_MustBeHalf);
+ }
+ 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 (this < other)
+ {
+ return -1;
+ }
+
+ if (this > other)
+ {
+ return 1;
+ }
+
+ if (this == other)
+ {
+ return 0;
+ }
+
+ if (IsNaN(this))
+ {
+ return IsNaN(other) ? 0 : -1;
+ }
+
+ Debug.Assert(IsNaN(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 other) && Equals(other);
+ }
+
+ ///
+ /// Returns a value that indicates whether this instance is equal to a specified value.
+ ///
+ 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 (_value == other._value) || AreZero(this, other);
+ }
+
+ ///
+ /// Serves as the default hash function.
+ ///
+ public override int GetHashCode()
+ {
+ if (IsNaNOrZero(this))
+ {
+ // All NaNs should have the same hash code, as should both Zeros.
+ return _value & PositiveInfinityBits;
+ }
+ return _value;
+ }
+
+ ///
+ /// Returns a string representation of the current value.
+ ///
+ public override string ToString()
+ {
+ return Number.FormatHalf(this, null, NumberFormatInfo.CurrentInfo);
+ }
+
+ ///
+ /// Returns a string representation of the current value using the specified .
+ ///
+ public string ToString(string? format)
+ {
+ return Number.FormatHalf(this, format, NumberFormatInfo.CurrentInfo);
+ }
+
+ ///
+ /// Returns a string representation of the current value with the specified .
+ ///
+ public string ToString(IFormatProvider? provider)
+ {
+ return Number.FormatHalf(this, null, NumberFormatInfo.GetInstance(provider));
+ }
+
+ ///
+ /// Returns a string representation of the current value using the specified and .
+ ///
+ public string ToString(string? format, IFormatProvider? provider)
+ {
+ return Number.FormatHalf(this, format, NumberFormatInfo.GetInstance(provider));
+ }
+
+ ///
+ /// 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? provider = null)
+ {
+ return Number.TryFormatHalf(this, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten);
+ }
+
+ // -----------------------Start of to-half conversions-------------------------
+
+ public static explicit operator Half(float value)
+ {
+ const int SingleMaxExponent = 0xFF;
+
+ 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;
+
+ 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 = (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;
+
+ 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 explicit 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 BitConverter.Int32BitsToSingle((int)(sign ? float.SignMask : 0)); // Positive / Negative zero
+ }
+ (exp, sig) = NormSubnormalF16Sig(sig);
+ exp -= 1;
+ }
+
+ return CreateSingle(sign, (byte)(exp + 0x70), sig << 13);
+ }
+
+ public static explicit 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 BitConverter.Int64BitsToDouble((long)(sign ? double.SignMask : 0)); // Positive / Negative zero
+ }
+ (exp, sig) = NormSubnormalF16Sig(sig);
+ exp -= 1;
+ }
+
+ 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._value ^ SignMask));
+ }
+
+ private static (int Exp, uint Sig) NormSubnormalF16Sig(uint sig)
+ {
+ int shiftDist = BitOperations.LeadingZeroCount(sig) - 16 - 5;
+ return (1 - shiftDist, sig << shiftDist);
+ }
+
+ #region Utilities
+
+ // 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)
+ {
+ const uint NaNBits = ExponentMask | 0x200; // Most significant significand bit
+
+ uint signInt = (sign ? 1U : 0U) << SignShift;
+ uint sigInt = (uint)(significand >> 54);
+
+ return BitConverter.Int16BitsToHalf((short)(signInt | NaNBits | sigInt));
+ }
+
+ 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;
+
+ 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)._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)
+ private static uint ShiftRightJam(uint i, int dist)
+ => dist < 31 ? (i >> dist) | (i << (-dist & 31) != 0 ? 1U : 0U) : (i != 0 ? 1U : 0U);
+
+ private static ulong ShiftRightJam(ulong l, int dist)
+ => dist < 63 ? (l >> dist) | (l << (-dist & 63) != 0 ? 1UL : 0UL) : (l != 0 ? 1UL : 0UL);
+
+ private 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 BitConverter.Int32BitsToSingle((int)(signInt | NaNBits | sigInt));
+ }
+
+ private 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 BitConverter.Int64BitsToDouble((long)(signInt | NaNBits | sigInt));
+ }
+
+ private static float CreateSingle(bool sign, byte exp, uint sig)
+ => BitConverter.Int32BitsToSingle((int)(((sign ? 1U : 0U) << float.SignShift) | ((uint)exp << float.ExponentShift) | 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..2b2e34ec9065aa 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;
@@ -54,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));
@@ -68,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 3e03b8b4972cd7..d753124b3de0ad 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((double)v > 0.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..4b61b427d64b6b 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;
@@ -631,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);
}
@@ -668,6 +670,91 @@ public static bool TryFormatSingle(float value, ReadOnlySpan format, Numbe
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? 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 != default) && (!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)) || (BitConverter.HalfToInt16Bits(value) == BitConverter.HalfToInt16Bits(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, HalfPrecision);
+ }
+ NumberToString(ref sb, ref number, fmt, nMaxDigits, info);
+ }
+ else
+ {
+ 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);
@@ -2563,6 +2650,38 @@ private static ulong ExtractFractionAndBiasedExponent(double value, out int expo
return fraction;
}
+ private static ushort ExtractFractionAndBiasedExponent(Half value, out int exponent)
+ {
+ ushort bits = (ushort)BitConverter.HalfToInt16Bits(value);
+ ushort fraction = (ushort)(bits & 0x3FF);
+ exponent = ((int)(bits >> 10) & 0x1F);
+
+ if (exponent != 0)
+ {
+ // 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)
+ //
+ // 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/Half-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..064d9fec187bfa 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((double)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..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,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; // 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.NumberToFloatingPointBits.cs b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs
index 59a47c67455e6a..5563441a0a5249 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; }
@@ -365,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
@@ -383,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));
}
@@ -404,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.Private.CoreLib/src/System/Number.Parsing.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs
index 8c4cd1c9adff5e..fb2949089846bd 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[]
{
@@ -1707,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];
@@ -1789,6 +1802,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 +2079,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 = default;
+ }
+ 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();
diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs
index 7fa3dac5f21062..b40ad7d9068e18 100644
--- a/src/libraries/System.Runtime/ref/System.Runtime.cs
+++ b/src/libraries/System.Runtime/ref/System.Runtime.cs
@@ -2194,6 +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 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 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; }
+ 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;
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..0a57cc2f1b49b8
--- /dev/null
+++ b/src/libraries/System.Runtime/tests/System/HalfTests.cs
@@ -0,0 +1,933 @@
+// 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
+ {
+ private static unsafe ushort HalfToUInt16Bits(Half value)
+ {
+ return *((ushort*)&value);
+ }
+
+ 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