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:
Родитель
00144d77d5
Коммит
6b0545e49b
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче