Files
glitchy-engine-beef/GlitchyEngine/src/Math/half.bf
T

322 lines
8.5 KiB
Beef

using System;
namespace GlitchyEngine.Math;
/// Represents a 16bit floating point number. (IEEE 754 half-precision binary floating-point (binary16))
/// Note: Eventhough it is possible, it is not recommended to perform calculations on this type.
/// Most operations will simply convert the halfs to floats, perform the calculation and convert the result back to half.
struct half : IFloating, ISigned, IFormattable, IHashable, IEquatable<half>, ICanBeNaN
{
public const half MinValue = half(-65504); // Should be 0xFBFF
public const half MaxValue = half(65504); // Should be 0x7BFF
// The numbers for Inifnity, NaN and Zero need to be hardcoded as binaries,
// because the conversion intself relies on them.
public const half PositiveInfinity = half(0x7C00);
public const half NegativeInfinity = half(0xFC00);
public const half NaN = half(0x7CFF);
public const half Zero = half(0x0000);
public const half NegativeZero = half(0x8000);
private uint16 _data;
public bool IsNegative => (_data & Half_Sign_Mask) > 0;
public bool IsFinite => (_data & ~Half_Sign_Mask) < Half_Exponent_Mask;
public bool IsInfinity => (_data & ~Half_Sign_Mask) == Half_Exponent_Mask;
public bool IsPositiveInfinity => _data == PositiveInfinity._data;
public bool IsNegativeInfinity => _data == NegativeInfinity._data;
public bool IsNaN => (_data & ~Half_Sign_Mask) > Half_Exponent_Mask;
public bool IsSubnormal
{
get
{
var unsignedBits = _data & ~Half_Sign_Mask;
// Zero isn't normalized and if exponent is 0 we are unnormalized
return (unsignedBits != 0) && ((unsignedBits & Half_Exponent_Mask) == 0);
}
}
public this(float value)
{
this = FromFloat32(value);
}
private this(uint16 data)
{
_data = data;
}
public explicit static operator half(float value) => FromFloat32(value);
public explicit static operator float(half value) => ToFloat32(value);
public static half FromFloat32(float value)
{
if (value == 0.0f)
return half(0x0000);
if (value == -0.0f)
return half(0x8000);
#unwarn
uint32 singleBits = *(uint32*)&value;
uint32 sign = GetSingleSignBit(singleBits);
int32 exponent = (int32)GetSingleExponent(singleBits);
uint32 mantissa = GetSingleMantissa(singleBits);
// Shift 13 so we truncate mantissa from 23 to 10 bits.
uint32 halfMantissa = mantissa >> 13;
if (exponent == 0xFF)
{
// largest possible single exponent -> either infinity or NaN
if (mantissa == 0)
{
// Infinity
return (sign == 1) ? NegativeInfinity : PositiveInfinity;
}
else
{
// NaN -> Keeps sign and mantissa intact
uint16 halfBits = SetHalfSignBit(0, (uint16)sign);
// Set all five exponent bits to 1
halfBits = SetHalfExponent(halfBits, 0x1F);
halfBits = SetHalfMantissa(halfBits, (uint16)halfMantissa);
return half(halfBits);
}
}
// Normalized single
// excess-K decode and encode -127 is k for single, 15 is k for half
exponent = exponent - 127 + 15;
if (exponent < 0)
// The given number is too small for a half (even denormalized) -> return 0
return (sign == 1) ? NegativeZero : Zero;
if (exponent > 31)
// The given number is too large for a half -> return infinity
return (sign == 1) ? NegativeInfinity : PositiveInfinity;
// The given number fits -> convert (might become denormalized)
uint16 halfBits = SetHalfSignBit(0, (uint16)sign);
halfBits = SetHalfExponent(halfBits, (uint16)exponent);
halfBits = SetHalfMantissa(halfBits, (uint16)halfMantissa);
return half(halfBits);
}
public static float ToFloat32(half value)
{
#unwarn
uint16 halfBits = *(uint16*)&value;
if (halfBits == Zero._data)
return 0.0f;
if (halfBits == NegativeZero._data)
return -0.0f;
uint16 sign = GetHalfSignBit(halfBits);
int16 exponent = (int16)GetHalfExponent(halfBits);
uint16 mantissa = GetHalfMantissa(halfBits);
// Shift 13 so we extend mantissa from 10 to 23 bits.
uint32 singleMantissa = (uint32)mantissa << 13;
if (exponent == 0x1F)
{
// largest possible half exponent -> either infinity or NaN
if (mantissa == 0)
{
// Infinity
return (sign == 1) ? float.NegativeInfinity : float.PositiveInfinity;
}
else
{
// NaN -> Keeps sign and mantissa intact
uint32 singleBits = SetSingleSignBit(0, sign);
// Set all five exponent bits to 1
singleBits = SetSingleExponent(halfBits, 0xFF);
singleBits = SetSingleMantissa(halfBits, singleMantissa);
return *(float*)&singleBits;
}
}
// Normalized half
// excess-K decode and encode -127 is k for single, 15 is k for half
exponent = exponent - 15 + 127;
uint32 singleBits = SetSingleSignBit(0, sign);
singleBits = SetSingleExponent(singleBits, (uint32)exponent);
singleBits = SetSingleMantissa(singleBits, singleMantissa);
return *(float*)&singleBits;
}
#region Single <-> Half Helpers
const uint32 Single_Sign_Shift = 31;
const uint32 Single_Sign_Mask = 0x8000'0000; // 1 bit, offset 31
const uint32 Single_Exponent_Shift = 23;
const uint32 Single_Exponent_Mask = 0x7F80'0000; // 8 bit, offset 23
const uint32 Single_Mantissa_Mask = 0x007F'FFFF; // 23 bit, offset 0
const uint16 Half_Sign_Shift = 15;
const uint16 Half_Sign_Mask = 0x8000; // 1 bit, offset 15
const uint16 Half_Exponent_Shift = 10;
const uint16 Half_Exponent_Mask = 0x7C00; // 5 bit, offset 10
const uint16 Half_Mantissa_Mask = 0x03FF; // 10 bit, offset 0
private static uint32 GetSingleSignBit(uint32 singleData)
{
return (singleData >> Single_Sign_Shift);
}
private static uint32 GetSingleExponent(uint32 singleData)
{
return (singleData & Single_Exponent_Mask) >> Single_Exponent_Shift;
}
private static uint32 GetSingleMantissa(uint32 singleData)
{
return (singleData & Single_Mantissa_Mask);
}
private static uint32 SetSingleSignBit(uint32 singleData, uint32 signBit)
{
return ((signBit << Single_Sign_Shift) & Single_Sign_Mask) | (singleData & ~Single_Sign_Mask);
}
private static uint32 SetSingleExponent(uint32 singleData, uint32 exponent)
{
return ((exponent << Single_Exponent_Shift) & Single_Exponent_Mask) | (singleData & ~Single_Exponent_Mask);
}
private static uint32 SetSingleMantissa(uint32 singleData, uint32 mantissa)
{
return (mantissa & Single_Mantissa_Mask) | (singleData & ~Single_Mantissa_Mask);
}
private static uint16 GetHalfSignBit(uint16 HalfData)
{
return (HalfData >> Half_Sign_Shift);
}
private static uint16 GetHalfExponent(uint16 HalfData)
{
return (HalfData & Half_Exponent_Mask) >> Half_Exponent_Shift;
}
private static uint16 GetHalfMantissa(uint16 HalfData)
{
return (HalfData & Half_Mantissa_Mask);
}
private static uint16 SetHalfSignBit(uint16 HalfData, uint16 signBit)
{
return ((signBit << Half_Sign_Shift) & Half_Sign_Mask) | (HalfData & ~Half_Sign_Mask);
}
private static uint16 SetHalfExponent(uint16 HalfData, uint16 exponent)
{
return ((exponent << Half_Exponent_Shift) & Half_Exponent_Mask) | (HalfData & ~Half_Exponent_Mask);
}
private static uint16 SetHalfMantissa(uint16 HalfData, uint16 mantissa)
{
return (mantissa & Half_Mantissa_Mask) | (HalfData & ~Half_Mantissa_Mask);
}
#endregion
public int GetHashCode()
{
return _data;
}
public void ToString(String outString)
{
ToFloat32(this).ToString(outString);
}
public void ToString(String outString, String format, IFormatProvider formatProvider)
{
ToFloat32(this).ToString(outString, format, formatProvider);
}
public static half operator +(half lhs, half rhs)
{
return (half)((float)lhs + (float)rhs);
}
public static half operator +(half value) => value;
public static half operator -(half lhs, half rhs)
{
return (half)((float)lhs - (float)rhs);
}
public static half operator -(half value)
{
uint16 signBit = GetHalfSignBit(value._data);
// Invert the sign bit (~)
return half(SetHalfSignBit(value._data, ~signBit));
}
public static half operator *(half lhs, half rhs)
{
return (half)((float)lhs * (float)rhs);
}
public static half operator /(half lhs, half rhs)
{
return (half)((float)lhs / (float)rhs);
}
public static half operator %(half lhs, half rhs)
{
return (half)((float)lhs % (float)rhs);
}
public static half operator ++(half value)
{
return (half)((float)value + 1.0f);
}
public static half operator --(half value)
{
return (half)((float)value - 1.0f);
}
public static bool operator==(half value1, half value2) => value1._data == value2._data;
public static bool operator!=(half value1, half value2) => value1._data != value2._data;
public static int operator<=>(half value1, half value2) => (float)value1 <=> (float)value2;
public bool Equals(half other)
{
return _data == other._data;
}
}