mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
322 lines
8.5 KiB
Beef
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;
|
|
}
|
|
} |