Table based Half implementation (#2929)

* Half: not complete yet

* Implementation and unit tests

* Unit tests and operators

* Rename to TableBasedHalf

* Clean up Half implementation and unit tests

* Port missing unit tests from Single
This commit is contained in:
Prashanth Govindarajan 2020-06-09 17:36:28 -07:00 коммит произвёл GitHub
Родитель 00144d77d5
Коммит 6b0545e49b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 5641 добавлений и 774 удалений

Просмотреть файл

@ -0,0 +1,237 @@
// 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.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace System.Numerics.Experimental
{
// ===================================================================================================
// Portions of the code implemented below are based on the 'Berkeley SoftFloat Release 3e' algorithms.
// ===================================================================================================
public readonly partial struct Half : IComparable, IFormattable, IComparable<Half>, IEquatable<Half>
{
// -----------------------Start of to-half conversions-------------------------
public static implicit operator Half(int value)
{
bool sign = value < 0;
Half h = (uint)(sign ? -value : value); // Math.Abs but doesn't throw exception, because we cast it to uint anyway
return sign ? new Half((ushort)(h.m_value | SignMask)) : h;
}
public static implicit operator Half(uint value)
{
int shiftDist = BitOperations.LeadingZeroCount(value) - 21;
if (shiftDist >= 0)
{
return value != 0 ?
new Half(false, (ushort)(0x18 - shiftDist), (ushort)(value << shiftDist)) :
default;
}
shiftDist += 4;
uint sig = shiftDist < 0 ? Ieee754Helpers.ShiftRightJam(value, -shiftDist) : value << shiftDist;
return new Half(RoundPackToHalf(false, (short)(0x1C - shiftDist), (ushort)sig));
}
public static implicit operator Half(long value)
{
bool sign = value < 0;
Half h = (ulong)(sign ? -value : value); // Math.Abs but doesn't throw exception, because we cast it to ulong anyway
return sign ? new Half((ushort)(h.m_value | SignMask)) : h;
}
public static implicit operator Half(ulong value)
{
int shiftDist = BitOperations.LeadingZeroCount(value) - 53;
if (shiftDist >= 0)
{
return value != 0 ?
new Half(false, (ushort)(0x18 - shiftDist), (ushort)(value << shiftDist)) :
default;
}
shiftDist += 4;
ushort sig = (ushort)(shiftDist < 0 ? Ieee754Helpers.ShiftRightJam(value, -shiftDist) : value << shiftDist);
return new Half(RoundPackToHalf(false, (short)(0x1C - shiftDist), sig));
}
public static implicit operator Half(short value)
{
return (int)value;
}
public static implicit operator Half(ushort value)
{
return (uint)value;
}
public static implicit operator Half(byte value)
{
return (uint)value;
}
public static implicit operator Half(sbyte value)
{
return (int)value;
}
// -----------------------Start of from-half conversions-------------------------
public static explicit operator int(Half value)
{
bool sign = IsNegative(value);
int exp = value.Exponent;
uint sig = value.Significand;
int shiftDist = exp - 0x0F;
if (shiftDist < 0) // Value < 1
{
return 0;
}
if (exp == MaxExponent)
{
return IllegalValueToInt32;
}
int alignedSig = (int)(sig | 0x0400) << shiftDist;
alignedSig >>= 10;
return sign ? -alignedSig : alignedSig;
}
public static explicit operator uint(Half value) // 0 for every case
{
bool sign = IsNegative(value);
int exp = value.Exponent;
uint sig = value.Significand;
int shiftDist = exp - 0x0F;
if (shiftDist < 0) // Value < 1
{
return 0;
}
if (exp == MaxExponent)
{
return IllegalValueToUInt32;
}
uint alignedSig = (sig | 0x0400) << shiftDist;
alignedSig >>= 10;
return (uint)(sign ? -(int)alignedSig : (int)alignedSig);
}
public static explicit operator long(Half value)
{
bool sign = IsNegative(value);
int exp = value.Exponent;
uint sig = value.Significand;
int shiftDist = exp - 0x0F;
if (shiftDist < 0) // value < 1
{
return 0;
}
if (exp == MaxExponent)
{
return IllegalValueToInt64;
}
int alignedSig = (int) (sig | 0x0400) << shiftDist;
alignedSig >>= 10;
return sign ? -alignedSig : alignedSig;
}
public static explicit operator ulong(Half value) // 0 for PosInfinity/NaN, long.MinValue for NegInfinity
{
bool sign = IsNegative(value);
int exp = value.Exponent;
uint sig = value.Significand;
int shiftDist = exp - 0x0F;
if (shiftDist < 0) // value < 1
{
return 0;
}
if (exp == MaxExponent)
{
return IllegalValueToUInt64;
}
uint alignedSig = (sig | 0x0400) << shiftDist;
alignedSig >>= 10;
return (ulong)(sign ? -alignedSig : alignedSig);
}
public static explicit operator short(Half value)
{
return (short)(int)value;
}
public static explicit operator ushort(Half value)
{
return (ushort)(short)(int)value;
}
public static explicit operator byte(Half value)
{
return (byte)(sbyte)(int)value;
}
public static explicit operator sbyte(Half value)
{
return (sbyte)(int)value;
}
// IEEE 754 specifies NaNs to be propagated
public static Half operator -(Half value)
{
return IsNaN(value) ? value : new Half((ushort)(value.m_value ^ SignMask));
}
public static Half operator +(Half value)
{
return value;
}
#region Utilities
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)Ieee754Helpers.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;
}
#endregion
}
}

Просмотреть файл

@ -15,7 +15,7 @@ namespace System.Numerics.Experimental
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public readonly struct Half : IComparable, IFormattable, IComparable<Half>, IEquatable<Half>
public readonly partial struct Half : IComparable, IFormattable, IComparable<Half>, IEquatable<Half>
{
private const NumberStyles DefaultParseStyle = NumberStyles.Float | NumberStyles.AllowThousands;
@ -72,7 +72,7 @@ namespace System.Numerics.Experimental
// Well-defined and commonly used values
//
public static readonly Half Epsilon = new Half(EpsilonBits); // 5.9605E-08
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
@ -241,38 +241,63 @@ namespace System.Numerics.Experimental
&& ((absValue & ExponentMask) == 0); // is subnormal (has a zero exponent)
}
public static Half Parse(string s, NumberStyles style = DefaultParseStyle, IFormatProvider formatProvider = null)
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, formatProvider);
return Parse(s.AsSpan(), style, provider);
}
public static Half Parse(ReadOnlySpan<char> s, NumberStyles style = DefaultParseStyle, IFormatProvider formatProvider = null)
public static Half Parse(ReadOnlySpan<char> s, NumberStyles style = DefaultParseStyle, IFormatProvider provider = null)
{
throw new NotImplementedException();
return (Half)(float.Parse(s, style, provider));
}
public static bool TryParse(string s, out Half result)
{
return TryParse(s, DefaultParseStyle, formatProvider: null, out result);
return TryParse(s, DefaultParseStyle, provider: null, out result);
}
public static bool TryParse(ReadOnlySpan<char> s, out Half result)
{
return TryParse(s, DefaultParseStyle, formatProvider: null, out result);
return TryParse(s, DefaultParseStyle, provider: null, out result);
}
public static bool TryParse(string s, NumberStyles style, IFormatProvider formatProvider, out Half result)
public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out Half result)
{
return TryParse(s.AsSpan(), style, formatProvider, out result);
return TryParse(s.AsSpan(), style, provider, out result);
}
public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider formatProvider, out Half result)
public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider provider, out Half result)
{
throw new NotImplementedException();
bool ret = false;
if (float.TryParse(s, style, provider, out float floatResult))
{
result = (Half)floatResult;
ret = true;
}
else
{
result = new Half();
}
return ret;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -338,7 +363,14 @@ namespace System.Numerics.Experimental
public bool Equals(Half other)
{
return (this == other) || (IsNaN(this) && IsNaN(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()
@ -353,90 +385,32 @@ namespace System.Numerics.Experimental
public override string ToString()
{
return $"0x{m_value:X4}";
// return ToString(format: null, formatProvider: null);
// TODO: Implement this
return ToString(format: null, formatProvider: null);
}
public string ToString(string format = null, IFormatProvider formatProvider = null)
public string ToString(string format)
{
return $"0x{m_value:X4}";
// throw new NotImplementedException();
// TODO: Implement this
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<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider formatProvider)
{
throw new NotImplementedException();
return ((float)this).TryFormat(destination, out charsWritten, format, formatProvider);
}
// -----------------------Start of to-half conversions-------------------------
public static implicit operator Half(int value)
{
bool sign = value < 0;
Half h = (uint)(sign ? -value : value); // Math.Abs but doesn't throw exception, because we cast it to uint anyway
return sign ? new Half((ushort)(h.m_value | SignMask)) : h;
}
public static implicit operator Half(uint value)
{
int shiftDist = BitOperations.LeadingZeroCount(value) - 21;
if (shiftDist >= 0)
{
return value != 0 ?
new Half(false, (ushort)(0x18 - shiftDist), (ushort)(value << shiftDist)) :
default;
}
shiftDist += 4;
uint sig = shiftDist < 0 ? Ieee754Helpers.ShiftRightJam(value, -shiftDist) : value << shiftDist;
return new Half(RoundPackToHalf(false, (short)(0x1C - shiftDist), (ushort)sig));
}
public static implicit operator Half(long value)
{
bool sign = value < 0;
Half h = (ulong)(sign ? -value : value); // Math.Abs but doesn't throw exception, because we cast it to ulong anyway
return sign ? new Half((ushort)(h.m_value | SignMask)) : h;
}
public static implicit operator Half(ulong value)
{
int shiftDist = BitOperations.LeadingZeroCount(value) - 53;
if (shiftDist >= 0)
{
return value != 0 ?
new Half(false, (ushort)(0x18 - shiftDist), (ushort)(value << shiftDist)) :
default;
}
shiftDist += 4;
ushort sig = (ushort)(shiftDist < 0 ? Ieee754Helpers.ShiftRightJam(value, -shiftDist) : value << shiftDist);
return new Half(RoundPackToHalf(false, (short)(0x1C - shiftDist), sig));
}
public static implicit operator Half(short value)
{
return (int)value;
}
public static implicit operator Half(ushort value)
{
return (uint)value;
}
public static implicit operator Half(byte value)
{
return (uint)value;
}
public static implicit operator Half(sbyte value)
{
return (int)value;
}
public static explicit operator Half(float value)
{
const int singleMaxExponent = 0xFF;
@ -492,115 +466,6 @@ namespace System.Numerics.Experimental
}
// -----------------------Start of from-half conversions-------------------------
public static explicit operator int(Half value)
{
bool sign = IsNegative(value);
int exp = value.Exponent;
uint sig = value.Significand;
int shiftDist = exp - 0x0F;
if (shiftDist < 0) // Value < 1
{
return 0;
}
if (exp == MaxExponent)
{
return IllegalValueToInt32;
}
int alignedSig = (int)(sig | 0x0400) << shiftDist;
alignedSig >>= 10;
return sign ? -alignedSig : alignedSig;
}
public static explicit operator uint(Half value) // 0 for every case
{
bool sign = IsNegative(value);
int exp = value.Exponent;
uint sig = value.Significand;
int shiftDist = exp - 0x0F;
if (shiftDist < 0) // Value < 1
{
return 0;
}
if (exp == MaxExponent)
{
return IllegalValueToUInt32;
}
uint alignedSig = (sig | 0x0400) << shiftDist;
alignedSig >>= 10;
return (uint)(sign ? -(int)alignedSig : (int)alignedSig);
}
public static explicit operator long(Half value)
{
bool sign = IsNegative(value);
int exp = value.Exponent;
uint sig = value.Significand;
int shiftDist = exp - 0x0F;
if (shiftDist < 0) // value < 1
{
return 0;
}
if (exp == MaxExponent)
{
return IllegalValueToInt64;
}
int alignedSig = (int) (sig | 0x0400) << shiftDist;
alignedSig >>= 10;
return sign ? -alignedSig : alignedSig;
}
public static explicit operator ulong(Half value) // 0 for PosInfinity/NaN, long.MinValue for NegInfinity
{
bool sign = IsNegative(value);
int exp = value.Exponent;
uint sig = value.Significand;
int shiftDist = exp - 0x0F;
if (shiftDist < 0) // value < 1
{
return 0;
}
if (exp == MaxExponent)
{
return IllegalValueToUInt64;
}
uint alignedSig = (sig | 0x0400) << shiftDist;
alignedSig >>= 10;
return (ulong)(sign ? -alignedSig : alignedSig);
}
public static explicit operator short(Half value)
{
return (short)(int)value;
}
public static explicit operator ushort(Half value)
{
return (ushort)(short)(int)value;
}
public static explicit operator byte(Half value)
{
return (byte)(sbyte)(int)value;
}
public static explicit operator sbyte(Half value)
{
return (sbyte)(int)value;
}
public static implicit operator float(Half value)
{
bool sign = IsNegative(value);
@ -657,54 +522,11 @@ namespace System.Numerics.Experimental
return Ieee754Helpers.CreateDouble(sign, (ushort)(exp + 0x3F0), (ulong)sig << 42);
}
// IEEE 754 specifies NaNs to be propagated
public static Half operator -(Half value)
{
return IsNaN(value) ? value : new Half((ushort)(value.m_value ^ SignMask));
}
public static Half operator +(Half value)
{
return value;
}
#region Utilities
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)Ieee754Helpers.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;
}
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);
}
#endregion
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,279 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
namespace System
{
public readonly partial struct TableBasedHalf
{
// Tables used for conversion to/from float
// The tables are an implementation of an algorithm described in "Fast Half Float Conversions", Jeroen van der Zijp, http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf
private static uint[] s_mantissaTable = new uint[] {
0,
<#
for (int i = 1; i < 1024; i++)
{
#>
<#=ConvertMantissa(i)#>,
<#
}
for (int i = 1024; i < 2048; i++)
{
#>
(uint)(0x38000000 + ((<#=i#> - 1024) << 13)),
<#
}
#>
};
private static uint[] s_exponentTable = new uint[] {
0,
<#
for (int i = 1; i < 31; i++)
{
#>
(uint)(<#=i#> << 23),
<#
}
#>
0x47800000,
0x80000000,
<#
for (int i = 33; i < 63; i++)
{
#>
(uint)(0x80000000 + ((<#=i#> - 32) << 23)),
<#
}
#>
0xc7800000,
};
private static ushort[] offsetTable = new ushort[] {
0,
<#
for (int i = 1; i < 32; i++)
{
#>
1024,
<#
}
#>
0,
<#
for (int i = 33; i < 64; i++)
{
#>
1024,
<#
}
#>
};
private static ushort[] s_baseTable = new ushort[] {
<#
for (int i = 0; i < 512; ++i)
{
int e = i - 127;
if (i >= 256)
{
e -= 256;
}
if (e < -24)
{ // Very small numbers map to zero
if (i < 256)
{
#>
0x0000,
<#
}
else
{
#>
0x8000,
<#
}
}
else if (e < -14)
{
// Small numbers map to denorms
if (i < 256)
{
#>
0x0400 >> (-14 + <#=-e#>),
<#
}
else
{
#>
(0x0400 >> (-14 + <#=-e#>)) | 0x8000,
<#
}
}
else if (e <= 15)
{
// Normal numbers just lose precision
if (i < 256)
{
#>
(15 + <#=e#>) << 10,
<#
}
else
{
#>
((15 + <#=e#>) << 10) | 0x8000,
<#
}
}
else if (e < 128)
{
// Large numbers map to Infinity
if (i < 256)
{
#>
0x7C00,
<#
}
else
{
#>
0xFC00,
<#
}
}
else
{
// Infinity and NaN's stay Infinity and NaN's
if (i < 256)
{
#>
0x7C00,
<#
}
else
{
#>
0xFC00,
<#
}
}
}
#>
};
private static sbyte[] s_shiftTable = new sbyte[] {
<#
for (int i = 0; i < 512; ++i)
{
int e = i - 127;
if (i >= 256)
{
e -= 256;
}
if (e < -24)
{ // Very small numbers map to zero
if (i < 256)
{
#>
24,
<#
}
else
{
#>
24,
<#
}
}
else if (e < -14)
{
// Small numbers map to denorms
if (i < 256)
{
#>
-1 + <#=-e#>,
<#
}
else
{
#>
-1 + <#=-e#>,
<#
}
}
else if (e <= 15)
{
// Normal numbers just lose precision
if (i < 256)
{
#>
13,
<#
}
else
{
#>
13,
<#
}
}
else if (e < 128)
{
// Large numbers map to Infinity
if (i < 256)
{
#>
24,
<#
}
else
{
#>
24,
<#
}
}
else
{
// Infinity and NaN's stay Infinity and NaN's
if (i < 256)
{
#>
13,
<#
}
else
{
#>
13,
<#
}
}
}
#>
};
<#
uint ConvertMantissa(int i)
{
uint m = (uint)(i << 13); // Zero pad mantissa bits
uint e = 0; // Zero exponent
// While not normalized
while ((m & 0x00800000) == 0)
{
e -= 0x00800000; // Decrement exponent (1<<23)
m <<= 1; // Shift mantissa
}
m &= unchecked((uint)~0x00800000); // Clear leading 1 bit
e += 0x38800000; // Adjust bias ((127-14)<<23)
return m | e; // Return combined number
}
#>
}
}

Просмотреть файл

@ -6,6 +6,18 @@
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Include="HalfFloatConversionTables.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>HalfFloatConversionTables.tt</DependentUpon>
</None>
</ItemGroup>
<ItemGroup>
<Compile Update="HalfFloatConversionTables.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>HalfFloatConversionTables.tt</DependentUpon>
</Compile>
<Compile Update="Properties\Strings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
@ -19,4 +31,13 @@
<CustomToolNamespace>System</CustomToolNamespace>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Update="HalfFloatConversionTables.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>HalfFloatConversionTables.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,412 @@
// 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.
/*============================================================
**
**
**
** Purpose: An IEEE 768 compliant float16 type
**
**
===========================================================*/
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace System
{
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public readonly partial struct TableBasedHalf : IComparable, IFormattable, IComparable<TableBasedHalf>, IEquatable<TableBasedHalf>
{
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;
private const ushort FirstSignificandBitMask = 0x0200;
private const ushort SecondSignificandBitMask = 0x0100;
//
// 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 TableBasedHalf Epsilon = new TableBasedHalf(EpsilonBits); // 5.9605E-08
public static readonly TableBasedHalf PositiveInfinity = new TableBasedHalf(PositiveInfinityBits); // 1.0 / 0.0
public static readonly TableBasedHalf NegativeInfinity = new TableBasedHalf(NegativeInfinityBits); // -1.0 / 0.0
public static readonly TableBasedHalf NaN = new TableBasedHalf(NegativeQNaNBits); // 0.0 / 0.0
public static readonly TableBasedHalf MinValue = new TableBasedHalf(MinValueBits); // -65504
public static readonly TableBasedHalf MaxValue = new TableBasedHalf(MaxValueBits); // 65504
// We use these explicit definitions to avoid the confusion between 0.0 and -0.0.
private static readonly TableBasedHalf PositiveZero = new TableBasedHalf(PositiveZeroBits); // 0.0
private static readonly TableBasedHalf NegativeZero = new TableBasedHalf(NegativeZeroBits); // -0.0
private readonly ushort m_value; // Do not rename (binary serialization)
private TableBasedHalf(ushort value)
{
m_value = value;
}
private TableBasedHalf(bool sign, ushort exp, ushort sig)
=> m_value = (ushort)(((sign ? 1 : 0) << SignShift) + (exp << ExponentShift) + sig);
private TableBasedHalf(float single)
{
uint value = (uint)BitConverter.SingleToInt32Bits(single);
m_value = (ushort)(s_baseTable[(value >> 23) & 0x1ff] + ((value & 0x007fffff) >> s_shiftTable[value >> 23]));
}
private sbyte Exponent
{
get
{
return (sbyte)((m_value & ExponentMask) >> ExponentShift);
}
}
private ushort Significand
{
get
{
return (ushort)((m_value & SignificandMask) >> SignificandShift);
}
}
private bool Sign => (m_value & SignMask) >> SignShift == 1;
// <summary>Determines whether the specified value is finite (zero, subnormal, or normal).</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsFinite(TableBasedHalf value)
{
return StripSign(value) < PositiveInfinityBits;
}
/// <summary>Determines whether the specified value is infinite.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInfinity(TableBasedHalf value)
{
return StripSign(value) == PositiveInfinityBits;
}
/// <summary>Determines whether the specified value is NaN.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNaN(TableBasedHalf value)
{
return StripSign(value) > PositiveInfinityBits;
}
/// <summary>Determines whether the specified value is negative.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNegative(TableBasedHalf value)
{
return (short)(value.m_value) < 0;
}
/// <summary>Determines whether the specified value is negative infinity.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNegativeInfinity(TableBasedHalf value)
{
return value.m_value == NegativeInfinityBits;
}
/// <summary>Determines whether the specified value is normal.</summary>
// This is probably not worth inlining, it has branches and should be rarely called
public static bool IsNormal(TableBasedHalf 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)
}
/// <summary>Determines whether the specified value is positive infinity.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsPositiveInfinity(TableBasedHalf value)
{
return value.m_value == PositiveInfinityBits;
}
/// <summary>Determines whether the specified value is subnormal.</summary>
// This is probably not worth inlining, it has branches and should be rarely called
public static bool IsSubnormal(TableBasedHalf 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 TableBasedHalf Parse(string s, NumberStyles style = DefaultParseStyle, IFormatProvider formatProvider = null)
{
if (s is null)
{
throw new ArgumentNullException(nameof(s));
}
return Parse(s.AsSpan(), style, formatProvider);
}
public static TableBasedHalf Parse(ReadOnlySpan<char> s, NumberStyles style = DefaultParseStyle, IFormatProvider formatProvider = null)
{
return (TableBasedHalf)(float.Parse(s, style, formatProvider));
}
public static bool TryParse(string s, out TableBasedHalf result)
{
return TryParse(s, DefaultParseStyle, formatProvider: null, out result);
}
public static bool TryParse(ReadOnlySpan<char> s, out TableBasedHalf result)
{
return TryParse(s, DefaultParseStyle, formatProvider: null, out result);
}
public static bool TryParse(string s, NumberStyles style, IFormatProvider formatProvider, out TableBasedHalf result)
{
return TryParse(s.AsSpan(), style, formatProvider, out result);
}
public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider formatProvider, out TableBasedHalf result)
{
bool ret = false;
if (float.TryParse(s, style, formatProvider, out float floatResult))
{
result = (TableBasedHalf)floatResult;
ret = true;
}
else
{
result = new TableBasedHalf();
}
return ret;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool AreZero(TableBasedHalf left, TableBasedHalf 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(TableBasedHalf value)
{
return ((value.m_value - 1) & ~SignMask) >= PositiveInfinityBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ushort StripSign(TableBasedHalf value)
{
return (ushort)(value.m_value & ~SignMask);
}
public int CompareTo(object obj)
{
if (!(obj is TableBasedHalf))
{
return (obj is null) ? 1 : throw new ArgumentException(Strings.Arg_MustBeHalf);
}
return CompareTo((TableBasedHalf)(obj));
}
public int CompareTo(TableBasedHalf 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 TableBasedHalf) && Equals((TableBasedHalf)(obj));
}
public bool Equals(TableBasedHalf 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 override string ToString()
{
return ToString(format: null, formatProvider: null);
}
public string ToString(string format = null, IFormatProvider formatProvider = null)
{
return ((float)this).ToString(format, formatProvider);
}
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider formatProvider)
{
return ((float)this).TryFormat(destination, out charsWritten, format, formatProvider);
}
public static explicit operator TableBasedHalf(float value) => new TableBasedHalf(value);
public static explicit operator TableBasedHalf(double value) => new TableBasedHalf((float)value);
public static explicit operator float(TableBasedHalf half)
{
uint result = s_mantissaTable[offsetTable[half.m_value >> 10] + (half.m_value & 0x3ff)] + s_exponentTable[half.m_value >> 10];
return BitConverter.ToSingle(BitConverter.GetBytes(result));
}
public static explicit operator double(TableBasedHalf half) => (float)half;
public static bool operator <(TableBasedHalf left, TableBasedHalf 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 >(TableBasedHalf left, TableBasedHalf right)
{
return right < left;
}
public static bool operator <=(TableBasedHalf left, TableBasedHalf 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 >=(TableBasedHalf left, TableBasedHalf right)
{
return right <= left;
}
public static bool operator ==(TableBasedHalf left, TableBasedHalf 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 !=(TableBasedHalf left, TableBasedHalf right)
{
return !(left == right);
}
}
}

Просмотреть файл

@ -3,364 +3,12 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Xunit;
namespace System.Numerics.Experimental.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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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, true };
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)
{
if (obj is Half other)
{
Assert.Equal(expected, value.Equals(other));
if (Half.IsNaN(value) && Half.IsNaN(other))
{
Assert.Equal(!expected, value == other);
Assert.Equal(expected, value != other);
}
else
{
Assert.Equal(expected, value == other);
Assert.Equal(!expected, value != other);
}
Assert.Equal(expected, value.GetHashCode().Equals(other.GetHashCode()));
}
Assert.Equal(expected, value.Equals(obj));
}
// ---------- Start of To-half conversion tests ----------
public static IEnumerable<object[]> ImplicitConversion_FromInt64_TestData()
@ -613,98 +261,6 @@ namespace System.Numerics.Experimental.Tests
Assert.Equal(expected, h);
}
public static IEnumerable<object[]> 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.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<object[]> 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.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));
}
// ---------- Start of From-half conversion tests ----------
private const long IllegalValueToInt64 = long.MinValue;
@ -1108,98 +664,6 @@ namespace System.Numerics.Experimental.Tests
Assert.Equal(expected, i);
}
public static IEnumerable<object[]> ImplicitConversion_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(ImplicitConversion_ToSingle_TestData))]
[Theory]
public static void ImplicitConversion_ToSingle(Half value, float expected) // Check the underlying bits for verifying NaNs
{
float f = value;
Assert.Equal(BitConverter.SingleToInt32Bits(expected), BitConverter.SingleToInt32Bits(f));
}
public static IEnumerable<object[]> ImplicitConversion_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(ImplicitConversion_ToDouble_TestData))]
[Theory]
public static void ImplicitConversion_ToDouble(Half value, double expected) // Check the underlying bits for verifying NaNs
{
double d = value;
Assert.Equal(BitConverter.DoubleToInt64Bits(expected), BitConverter.DoubleToInt64Bits(d));
}
public static IEnumerable<object[]> UnaryNegativeOperator_TestData()
{
(Half original, Half expected)[] data =

Просмотреть файл

@ -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.Numerics.Experimental.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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> 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<object[]> RoundTripFloat_CornerCases()
{
// Magnitude smaller than 2^-24 maps to 0
yield return new object[] { (TableBasedHalf)(5.2e-20f), 0 };
yield return new object[] { (TableBasedHalf)(-5.2e-20f), 0 };
// Magnitude smaller than 2^(map to subnormals
yield return new object[] { (TableBasedHalf)(1.52e-5f), 1.52e-5f };
yield return new object[] { (TableBasedHalf)(-1.52e-5f), -1.52e-5f };
// Normal numbers
yield return new object[] { (TableBasedHalf)(55.77f), 55.75f };
yield return new object[] { (TableBasedHalf)(-55.77f), -55.75f };
// Magnitude smaller than 2^(map to infinity
yield return new object[] { (TableBasedHalf)(1.7e38f), float.PositiveInfinity };
yield return new object[] { (TableBasedHalf)(-1.7e38f), float.NegativeInfinity };
// Infinity and NaN map to infinity and Nan
yield return new object[] { TableBasedHalf.PositiveInfinity, float.PositiveInfinity };
yield return new object[] { TableBasedHalf.NegativeInfinity, float.NegativeInfinity };
yield return new object[] { TableBasedHalf.NaN, float.NaN };
}
[Theory]
[MemberData(nameof(RoundTripFloat_CornerCases))]
public static void ToSingle(TableBasedHalf half, float verify)
{
float f = (float)half;
Assert.Equal(f, verify, precision: 1);
}
}
}

Просмотреть файл

@ -0,0 +1,468 @@
// 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.Dynamic;
using System.Globalization;
using System.Runtime.CompilerServices;
using Xunit;
namespace System.Numerics.Experimental.Tests
{
public partial class TableBasedHalfTests
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe ushort HalfToUInt16Bits(TableBasedHalf value)
{
return *((ushort*)&value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe TableBasedHalf UInt16BitsToTableBasedHalf(ushort value)
{
return *((TableBasedHalf*)&value);
}
[Fact]
public static void Epsilon()
{
Assert.Equal(0x0001u, HalfToUInt16Bits(TableBasedHalf.Epsilon));
}
[Fact]
public static void PositiveInfinity()
{
Assert.Equal(0x7C00u, HalfToUInt16Bits(TableBasedHalf.PositiveInfinity));
}
[Fact]
public static void NegativeInfinity()
{
Assert.Equal(0xFC00u, HalfToUInt16Bits(TableBasedHalf.NegativeInfinity));
}
[Fact]
public static void NaN()
{
Assert.Equal(0xFE00u, HalfToUInt16Bits(TableBasedHalf.NaN));
}
[Fact]
public static void MinValue()
{
Assert.Equal(0xFBFFu, HalfToUInt16Bits(TableBasedHalf.MinValue));
}
[Fact]
public static void MaxValue()
{
Assert.Equal(0x7BFFu, HalfToUInt16Bits(TableBasedHalf.MaxValue));
}
[Fact]
public static void Ctor_Empty()
{
var value = new TableBasedHalf();
Assert.Equal(0x0000, HalfToUInt16Bits(value));
}
public static IEnumerable<object[]> IsFinite_TestData()
{
yield return new object[] { TableBasedHalf.NegativeInfinity, false }; // Negative Infinity
yield return new object[] { TableBasedHalf.MinValue, true }; // Min Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8400), true }; // Max Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x83FF), true }; // Min Negative Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8001), true }; // Max Negative Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8000), true }; // Negative Zero
yield return new object[] { TableBasedHalf.NaN, false }; // NaN
yield return new object[] { UInt16BitsToTableBasedHalf(0x0000), true }; // Positive Zero
yield return new object[] { TableBasedHalf.Epsilon, true }; // Min Positive Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x03FF), true }; // Max Positive Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x0400), true }; // Min Positive Normal
yield return new object[] { TableBasedHalf.MaxValue, true }; // Max Positive Normal
yield return new object[] { TableBasedHalf.PositiveInfinity, false }; // Positive Infinity
}
[Theory]
[MemberData(nameof(IsFinite_TestData))]
public static void IsFinite(TableBasedHalf value, bool expected)
{
Assert.Equal(expected, TableBasedHalf.IsFinite(value));
}
public static IEnumerable<object[]> IsInfinity_TestData()
{
yield return new object[] { TableBasedHalf.NegativeInfinity, true }; // Negative Infinity
yield return new object[] { TableBasedHalf.MinValue, false }; // Min Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8400), false }; // Max Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x83FF), false }; // Min Negative Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8001), false }; // Max Negative Subnormal (Negative Epsilon)
yield return new object[] { UInt16BitsToTableBasedHalf(0x8000), false }; // Negative Zero
yield return new object[] { TableBasedHalf.NaN, false }; // NaN
yield return new object[] { UInt16BitsToTableBasedHalf(0x0000), false }; // Positive Zero
yield return new object[] { TableBasedHalf.Epsilon, false }; // Min Positive Subnormal (Positive Epsilon)
yield return new object[] { UInt16BitsToTableBasedHalf(0x03FF), false }; // Max Positive Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x0400), false }; // Min Positive Normal
yield return new object[] { TableBasedHalf.MaxValue, false }; // Max Positive Normal
yield return new object[] { TableBasedHalf.PositiveInfinity, true }; // Positive Infinity
}
[Theory]
[MemberData(nameof(IsInfinity_TestData))]
public static void IsInfinity(TableBasedHalf value, bool expected)
{
Assert.Equal(expected, TableBasedHalf.IsInfinity(value));
}
public static IEnumerable<object[]> IsNaN_TestData()
{
yield return new object[] { TableBasedHalf.NegativeInfinity, false }; // Negative Infinity
yield return new object[] { TableBasedHalf.MinValue, false }; // Min Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8400), false }; // Max Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x83FF), false }; // Min Negative Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8001), false }; // Max Negative Subnormal (Negative Epsilon)
yield return new object[] { UInt16BitsToTableBasedHalf(0x8000), false }; // Negative Zero
yield return new object[] { TableBasedHalf.NaN, true }; // NaN
yield return new object[] { UInt16BitsToTableBasedHalf(0x0000), false }; // Positive Zero
yield return new object[] { TableBasedHalf.Epsilon, false }; // Min Positive Subnormal (Positive Epsilon)
yield return new object[] { UInt16BitsToTableBasedHalf(0x03FF), false }; // Max Positive Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x0400), false }; // Min Positive Normal
yield return new object[] { TableBasedHalf.MaxValue, false }; // Max Positive Normal
yield return new object[] { TableBasedHalf.PositiveInfinity, false }; // Positive Infinity
}
[Theory]
[MemberData(nameof(IsNaN_TestData))]
public static void IsNaN(TableBasedHalf value, bool expected)
{
Assert.Equal(expected, TableBasedHalf.IsNaN(value));
}
public static IEnumerable<object[]> IsNegative_TestData()
{
yield return new object[] { TableBasedHalf.NegativeInfinity, true }; // Negative Infinity
yield return new object[] { TableBasedHalf.MinValue, true }; // Min Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8400), true }; // Max Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x83FF), true }; // Min Negative Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8001), true }; // Max Negative Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8000), true }; // Negative Zero
yield return new object[] { TableBasedHalf.NaN, true }; // NaN
yield return new object[] { UInt16BitsToTableBasedHalf(0x0000), false }; // Positive Zero
yield return new object[] { TableBasedHalf.Epsilon, false }; // Min Positive Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x03FF), false }; // Max Positive Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x0400), false }; // Min Positive Normal
yield return new object[] { TableBasedHalf.MaxValue, false }; // Max Positive Normal
yield return new object[] { TableBasedHalf.PositiveInfinity, false }; // Positive Infinity
}
[Theory]
[MemberData(nameof(IsNegative_TestData))]
public static void IsNegative(TableBasedHalf value, bool expected)
{
Assert.Equal(expected, TableBasedHalf.IsNegative(value));
}
public static IEnumerable<object[]> IsNegativeInfinity_TestData()
{
yield return new object[] { TableBasedHalf.NegativeInfinity, true }; // Negative Infinity
yield return new object[] { TableBasedHalf.MinValue, false }; // Min Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8400), false }; // Max Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x83FF), false }; // Min Negative Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8001), false }; // Max Negative Subnormal (Negative Epsilon)
yield return new object[] { UInt16BitsToTableBasedHalf(0x8000), false }; // Negative Zero
yield return new object[] { TableBasedHalf.NaN, false }; // NaN
yield return new object[] { UInt16BitsToTableBasedHalf(0x0000), false }; // Positive Zero
yield return new object[] { TableBasedHalf.Epsilon, false }; // Min Positive Subnormal (Positive Epsilon)
yield return new object[] { UInt16BitsToTableBasedHalf(0x03FF), false }; // Max Positive Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x0400), false }; // Min Positive Normal
yield return new object[] { TableBasedHalf.MaxValue, false }; // Max Positive Normal
yield return new object[] { TableBasedHalf.PositiveInfinity, false }; // Positive Infinity
}
[Theory]
[MemberData(nameof(IsNegativeInfinity_TestData))]
public static void IsNegativeInfinity(TableBasedHalf value, bool expected)
{
Assert.Equal(expected, TableBasedHalf.IsNegativeInfinity(value));
}
public static IEnumerable<object[]> IsNormal_TestData()
{
yield return new object[] { TableBasedHalf.NegativeInfinity, false }; // Negative Infinity
yield return new object[] { TableBasedHalf.MinValue, true }; // Min Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8400), true }; // Max Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x83FF), false }; // Min Negative Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8001), false }; // Max Negative Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8000), false }; // Negative Zero
yield return new object[] { TableBasedHalf.NaN, false }; // NaN
yield return new object[] { UInt16BitsToTableBasedHalf(0x0000), false }; // Positive Zero
yield return new object[] { TableBasedHalf.Epsilon, false }; // Min Positive Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x03FF), false }; // Max Positive Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x0400), true }; // Min Positive Normal
yield return new object[] { TableBasedHalf.MaxValue, true }; // Max Positive Normal
yield return new object[] { TableBasedHalf.PositiveInfinity, false }; // Positive Infinity
}
[Theory]
[MemberData(nameof(IsNormal_TestData))]
public static void IsNormal(TableBasedHalf value, bool expected)
{
Assert.Equal(expected, TableBasedHalf.IsNormal(value));
}
public static IEnumerable<object[]> IsPositiveInfinity_TestData()
{
yield return new object[] { TableBasedHalf.NegativeInfinity, false }; // Negative Infinity
yield return new object[] { TableBasedHalf.MinValue, false }; // Min Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8400), false }; // Max Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x83FF), false }; // Min Negative Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8001), false }; // Max Negative Subnormal (Negative Epsilon)
yield return new object[] { UInt16BitsToTableBasedHalf(0x8000), false }; // Negative Zero
yield return new object[] { TableBasedHalf.NaN, false }; // NaN
yield return new object[] { UInt16BitsToTableBasedHalf(0x0000), false }; // Positive Zero
yield return new object[] { TableBasedHalf.Epsilon, false }; // Min Positive Subnormal (Positive Epsilon)
yield return new object[] { UInt16BitsToTableBasedHalf(0x03FF), false }; // Max Positive Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x0400), false }; // Min Positive Normal
yield return new object[] { TableBasedHalf.MaxValue, false }; // Max Positive Normal
yield return new object[] { TableBasedHalf.PositiveInfinity, true }; // Positive Infinity
}
[Theory]
[MemberData(nameof(IsPositiveInfinity_TestData))]
public static void IsPositiveInfinity(TableBasedHalf value, bool expected)
{
Assert.Equal(expected, TableBasedHalf.IsPositiveInfinity(value));
}
public static IEnumerable<object[]> IsSubnormal_TestData()
{
yield return new object[] { TableBasedHalf.NegativeInfinity, false }; // Negative Infinity
yield return new object[] { TableBasedHalf.MinValue, false }; // Min Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8400), false }; // Max Negative Normal
yield return new object[] { UInt16BitsToTableBasedHalf(0x83FF), true }; // Min Negative Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8001), true }; // Max Negative Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x8000), false }; // Negative Zero
yield return new object[] { TableBasedHalf.NaN, false }; // NaN
yield return new object[] { UInt16BitsToTableBasedHalf(0x0000), false }; // Positive Zero
yield return new object[] { TableBasedHalf.Epsilon, true }; // Min Positive Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x03FF), true }; // Max Positive Subnormal
yield return new object[] { UInt16BitsToTableBasedHalf(0x0400), false }; // Min Positive Normal
yield return new object[] { TableBasedHalf.MaxValue, false }; // Max Positive Normal
yield return new object[] { TableBasedHalf.PositiveInfinity, false }; // Positive Infinity
}
[Theory]
[MemberData(nameof(IsSubnormal_TestData))]
public static void IsSubnormal(TableBasedHalf value, bool expected)
{
Assert.Equal(expected, TableBasedHalf.IsSubnormal(value));
}
public static IEnumerable<object[]> 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), () => TableBasedHalf.MaxValue.CompareTo(obj));
}
public static IEnumerable<object[]> CompareTo_TestData()
{
yield return new object[] { TableBasedHalf.MaxValue, TableBasedHalf.MaxValue, 0 };
yield return new object[] { TableBasedHalf.MaxValue, TableBasedHalf.MinValue, 1 };
yield return new object[] { TableBasedHalf.Epsilon, UInt16BitsToTableBasedHalf(0x8001), 1 };
yield return new object[] { TableBasedHalf.MaxValue, UInt16BitsToTableBasedHalf(0x0000), 1 };
yield return new object[] { TableBasedHalf.MaxValue, TableBasedHalf.Epsilon, 1 };
yield return new object[] { TableBasedHalf.MaxValue, TableBasedHalf.PositiveInfinity, -1 };
yield return new object[] { TableBasedHalf.MinValue, TableBasedHalf.MaxValue, -1 };
yield return new object[] { TableBasedHalf.MaxValue, TableBasedHalf.NaN, 1 };
yield return new object[] { TableBasedHalf.NaN, TableBasedHalf.NaN, 0 };
yield return new object[] { TableBasedHalf.NaN, UInt16BitsToTableBasedHalf(0x0000), -1 };
yield return new object[] { TableBasedHalf.MaxValue, null, 1 };
}
[Theory]
[MemberData(nameof(CompareTo_TestData))]
public static void CompareTo(TableBasedHalf value, object obj, int expected)
{
Assert.Equal(expected, Math.Sign(value.CompareTo(obj)));
}
public static IEnumerable<object[]> Equals_TestData()
{
yield return new object[] { TableBasedHalf.MaxValue, TableBasedHalf.MaxValue, true };
yield return new object[] { TableBasedHalf.MaxValue, TableBasedHalf.MinValue, false };
yield return new object[] { TableBasedHalf.MaxValue, UInt16BitsToTableBasedHalf(0x0000), false };
yield return new object[] { TableBasedHalf.NaN, TableBasedHalf.NaN, false };
yield return new object[] { TableBasedHalf.MaxValue, 789.0f, false };
yield return new object[] { TableBasedHalf.MaxValue, "789", false };
}
[Theory]
[MemberData(nameof(Equals_TestData))]
public static void Equals(TableBasedHalf value, object obj, bool expected)
{
Assert.Equal(expected, value.Equals(obj));
}
public static IEnumerable<object[]> RoundTripFloat_CornerCases()
{
// Magnitude smaller than 2^-24 maps to 0
yield return new object[] { (TableBasedHalf)(5.2e-20f), 0 };
yield return new object[] { (TableBasedHalf)(-5.2e-20f), 0 };
// Magnitude smaller than 2^(map to subnormals
yield return new object[] { (TableBasedHalf)(1.52e-5f), 1.52e-5f };
yield return new object[] { (TableBasedHalf)(-1.52e-5f), -1.52e-5f };
// Normal numbers
yield return new object[] { (TableBasedHalf)(55.77f), 55.75f };
yield return new object[] { (TableBasedHalf)(-55.77f), -55.75f };
// Magnitude smaller than 2^(map to infinity
yield return new object[] { (TableBasedHalf)(1.7e38f), float.PositiveInfinity };
yield return new object[] { (TableBasedHalf)(-1.7e38f), float.NegativeInfinity };
// Infinity and NaN map to infinity and Nan
yield return new object[] { TableBasedHalf.PositiveInfinity, float.PositiveInfinity };
yield return new object[] { TableBasedHalf.NegativeInfinity, float.NegativeInfinity };
yield return new object[] { TableBasedHalf.NaN, float.NaN };
}
[Theory]
[MemberData(nameof(RoundTripFloat_CornerCases))]
public static void ToSingle(TableBasedHalf half, float verify)
{
float f = (float)half;
Assert.Equal(f, verify, precision: 1);
}
public static IEnumerable<object[]> FromSingle_TestData()
{
// Magnitude smaller than 2^-24 maps to 0
yield return new object[] { (TableBasedHalf)(5.2e-20f), 0, 0, false };
yield return new object[] { (TableBasedHalf)(-5.2e-20f), 0, 0, true };
// Magnitude smaller than 2^(map to subnf)rmals
yield return new object[] { (TableBasedHalf)(1.52e-5f), 0, 255, false };
yield return new object[] { (TableBasedHalf)(1.52e-5f), 0, 255, true };
// Normal numbers
yield return new object[] { (TableBasedHalf)(55.77f), 20, 760, false };
yield return new object[] { (TableBasedHalf)(-55.77f), 20, 760, true };
// Magnitude smaller than 2^(map to infif)ity
yield return new object[] { (TableBasedHalf)(1.7e38f), 31, 0, false };
yield return new object[] { (TableBasedHalf)(-1.7e38f), 31, 0, true };
// Infinity and NaN map to infinity and Nan
yield return new object[] { TableBasedHalf.PositiveInfinity, 31, 0, false };
yield return new object[] { TableBasedHalf.NegativeInfinity, 31, 0, true };
yield return new object[] { TableBasedHalf.NaN, 31, 512, true };
}
public static IEnumerable<object[]> ExplicitConversion_FromSingle_TestData()
{
yield return new object[] { MathF.PI, UInt16BitsToTableBasedHalf(0b0_10000_1001001000) }; // 3.140625
yield return new object[] { MathF.E, UInt16BitsToTableBasedHalf(0b0_10000_0101101111) }; // 2.71875
yield return new object[] { -MathF.PI, UInt16BitsToTableBasedHalf(0b1_10000_1001001000) }; // -3.140625
yield return new object[] { -MathF.E, UInt16BitsToTableBasedHalf(0b1_10000_0101101111) }; // -2.71875
yield return new object[] { float.MaxValue, TableBasedHalf.PositiveInfinity }; // Overflow
yield return new object[] { float.MinValue, TableBasedHalf.NegativeInfinity }; // Overflow
yield return new object[] { float.NaN, TableBasedHalf.NaN }; // Quiet Negative NaN
yield return new object[] { BitConverter.Int32BitsToSingle(0x7FC00000), UInt16BitsToTableBasedHalf(0b0_11111_1000000000) }; // Quiet Positive NaN
yield return new object[] { BitConverter.Int32BitsToSingle(unchecked((int)0xFFD55555)), UInt16BitsToTableBasedHalf(0b1_11111_1010101010) }; // Signalling Negative NaN
yield return new object[] { BitConverter.Int32BitsToSingle(0x7FD55555), UInt16BitsToTableBasedHalf(0b0_11111_1010101010) }; // Signalling Positive NaN
yield return new object[] { float.Epsilon, UInt16BitsToTableBasedHalf(0) }; // Underflow
yield return new object[] { -float.Epsilon, UInt16BitsToTableBasedHalf(0b1_00000_0000000000) }; // Underflow
yield return new object[] { 1f, UInt16BitsToTableBasedHalf(0b0_01111_0000000000) }; // 1
yield return new object[] { -1f, UInt16BitsToTableBasedHalf(0b1_01111_0000000000) }; // -1
yield return new object[] { 0f, UInt16BitsToTableBasedHalf(0) }; // 0
yield return new object[] { -0f, UInt16BitsToTableBasedHalf(0b1_00000_0000000000) }; // -0
yield return new object[] { 42f, UInt16BitsToTableBasedHalf(0b0_10100_0101000000) }; // 42
yield return new object[] { -42f, UInt16BitsToTableBasedHalf(0b1_10100_0101000000) }; // -42
yield return new object[] { 0.1f, UInt16BitsToTableBasedHalf(0b0_01011_1001100110) }; // 0.0999755859375
yield return new object[] { -0.1f, UInt16BitsToTableBasedHalf(0b1_01011_1001100110) }; // -0.0999755859375
yield return new object[] { 1.5f, UInt16BitsToTableBasedHalf(0b0_01111_1000000000) }; // 1.5
yield return new object[] { -1.5f, UInt16BitsToTableBasedHalf(0b1_01111_1000000000) }; // -1.5
yield return new object[] { 1.5009765625f, UInt16BitsToTableBasedHalf(0b0_01111_1000000001) }; // 1.5009765625
yield return new object[] { -1.5009765625f, UInt16BitsToTableBasedHalf(0b1_01111_1000000001) }; // -1.5009765625
}
[MemberData(nameof(ExplicitConversion_FromSingle_TestData))]
[Theory]
public static void ExplicitConversion_FromSingle(float f, TableBasedHalf expected) // Check the underlying bits for verifying NaNs
{
TableBasedHalf h = (TableBasedHalf)f;
Assert.Equal(HalfToUInt16Bits(expected), HalfToUInt16Bits(h));
}
public static IEnumerable<object[]> ExplicitConversion_FromDouble_TestData()
{
yield return new object[] { MathF.PI, UInt16BitsToTableBasedHalf(0b0_10000_1001001000) }; // 3.140625
yield return new object[] { MathF.E, UInt16BitsToTableBasedHalf(0b0_10000_0101101111) }; // 2.71875
yield return new object[] { -MathF.PI, UInt16BitsToTableBasedHalf(0b1_10000_1001001000) }; // -3.140625
yield return new object[] { -MathF.E, UInt16BitsToTableBasedHalf(0b1_10000_0101101111) }; // -2.71875
yield return new object[] { double.MaxValue, TableBasedHalf.PositiveInfinity }; // Overflow
yield return new object[] { double.MinValue, TableBasedHalf.NegativeInfinity }; // Overflow
yield return new object[] { double.NaN, TableBasedHalf.NaN }; // Quiet Negative NaN
yield return new object[] { BitConverter.Int32BitsToSingle(0x7FC00000), UInt16BitsToTableBasedHalf(0b0_11111_1000000000) }; // Quiet Positive NaN
yield return new object[] { BitConverter.Int32BitsToSingle(unchecked((int)0xFFD55555)), UInt16BitsToTableBasedHalf(0b1_11111_1010101010) }; // Signalling Negative NaN
yield return new object[] { BitConverter.Int32BitsToSingle(0x7FD55555), UInt16BitsToTableBasedHalf(0b0_11111_1010101010) }; // Signalling Positive NaN
yield return new object[] { double.Epsilon, UInt16BitsToTableBasedHalf(0) }; // Underflow
yield return new object[] { -double.Epsilon, UInt16BitsToTableBasedHalf(0b1_00000_0000000000) }; // Underflow
yield return new object[] { 1d, UInt16BitsToTableBasedHalf(0b0_01111_0000000000) }; // 1
yield return new object[] { -1d, UInt16BitsToTableBasedHalf(0b1_01111_0000000000) }; // -1
yield return new object[] { 0d, UInt16BitsToTableBasedHalf(0) }; // 0
yield return new object[] { -0d, UInt16BitsToTableBasedHalf(0b1_00000_0000000000) }; // -0
yield return new object[] { 42d, UInt16BitsToTableBasedHalf(0b0_10100_0101000000) }; // 42
yield return new object[] { -42d, UInt16BitsToTableBasedHalf(0b1_10100_0101000000) }; // -42
yield return new object[] { 0.1d, UInt16BitsToTableBasedHalf(0b0_01011_1001100110) }; // 0.0999755859375
yield return new object[] { -0.1d, UInt16BitsToTableBasedHalf(0b1_01011_1001100110) }; // -0.0999755859375
yield return new object[] { 1.5d, UInt16BitsToTableBasedHalf(0b0_01111_1000000000) }; // 1.5
yield return new object[] { -1.5d, UInt16BitsToTableBasedHalf(0b1_01111_1000000000) }; // -1.5
yield return new object[] { 1.5009765625d, UInt16BitsToTableBasedHalf(0b0_01111_1000000001) }; // 1.5009765625
yield return new object[] { -1.5009765625d, UInt16BitsToTableBasedHalf(0b1_01111_1000000001) }; // -1.5009765625
}
[MemberData(nameof(ExplicitConversion_FromDouble_TestData))]
[Theory]
public static void ExplicitConversion_FromDouble(double d, TableBasedHalf expected) // Check the underlying bits for verifying NaNs
{
TableBasedHalf h = (TableBasedHalf)d;
Assert.Equal(HalfToUInt16Bits(expected), HalfToUInt16Bits(h));
}
public static IEnumerable<object[]> Parse_TestData()
{
yield return new object[] { "123.456e-2", CultureInfo.CurrentCulture, 1.23456 };
yield return new object[] { "-123.456e-2", CultureInfo.CurrentCulture, -1.23456 };
}
[Theory]
[MemberData(nameof(Parse_TestData))]
public static void ParseTests(string value, IFormatProvider provider, float expected)
{
// The Parse method just relies on float.Parse, so the test is really just testing the constructor again
TableBasedHalf actual = TableBasedHalf.Parse(value, formatProvider: provider);
Assert.Equal(expected, (float)actual, precision: 1);
}
[Fact]
public unsafe void TestAllHalfValues()
{
for (ushort i = ushort.MinValue; i < ushort.MaxValue; i++)
{
TableBasedHalf half1 = UInt16BitsToTableBasedHalf(i);
TableBasedHalf half2 = (TableBasedHalf)((float)half1);
bool half1IsNaN = TableBasedHalf.IsNaN(half1);
bool half2IsNaN = TableBasedHalf.IsNaN(half2);
if (half1IsNaN && half2IsNaN)
{
continue;
}
Assert.Equal(half1IsNaN, half2IsNaN);
Assert.True(half1.Equals(half2), $"{i} is wrong");
}
}
}
}