#if DEBUG #define RANGE_CHECK #endif using System; using System.Runtime.CompilerServices; namespace MagmaEngine.Math; public struct FixPoint16Long : IComparable, IComparable, IEquatable { public FixPoint16Long(FixPoint16Long other) { m_Value = other.m_Value; } public FixPoint16Long(int value) { m_Value = (long)value << c_Shift; } public FixPoint16Long(long value) { #if RANGE_CHECK if (value < c_IntegerMin || value > c_IntegerMax) { throw new ArithmeticException($"Long to FixPoint argument out of range: {value}"); } #endif m_Value = value << c_Shift; } public FixPoint16Long(double value) { #if RANGE_CHECK if (value < c_IntegerMin || value > c_IntegerMax) { throw new ArithmeticException($"Double to FixPoint argument out of range: {value}"); } #endif if (value < 0.0) { m_Value = (int)((value * c_Multiplier) - 0.5); } else { m_Value = (int)((value * c_Multiplier) + 0.5); } } public FixPoint16Long(float value) { #if RANGE_CHECK if (value < c_IntegerMin || value > c_IntegerMax) { throw new ArithmeticException($"Single to FixPoint argument out of range: {value}"); } #endif if (value < 0.0f) { m_Value = (int)((value * c_MultiplierFloat) - 0.5f); } else { m_Value = (int)((value * c_MultiplierFloat) + 0.5f); } } public long ToLongFloor() { return m_Value >> c_Shift; } public long ToLongCeil() { return (m_Value + c_FractionMask) >> c_Shift; } public long ToLongRound() { if (m_Value < 0) { return -((-m_Value + c_Half) >> c_Shift); } return (m_Value + c_Half) >> c_Shift; } public long ToLong() { if (m_Value < 0) { return -(-m_Value >> c_Shift); } return m_Value >> c_Shift; } public double ToDouble() { return c_Divisor * m_Value; } public float ToFloat() { return c_DivisorFloat * m_Value; } public override string ToString() { return $"{ToDouble()}[0x{m_Value:x16}]"; } public override int GetHashCode() { return m_Value.GetHashCode(); } public int CompareTo(object? obj) { if (obj is not FixPoint16Long other) return -1; return m_Value.CompareTo(other.m_Value); } public int CompareTo(FixPoint16Long other) { return m_Value.CompareTo(other.m_Value); } public override bool Equals(object? obj) { if (obj == null) { return false; } return ((FixPoint16Long)obj).m_Value == m_Value; } public bool Equals(FixPoint16Long other) { return other.m_Value == m_Value; } public bool IsZero() { return m_Value == 0L; } public static bool operator ==(FixPoint16Long a, FixPoint16Long b) { return a.m_Value == b.m_Value; } public static bool operator !=(FixPoint16Long a, FixPoint16Long b) { return a.m_Value != b.m_Value; } public static bool operator <(FixPoint16Long a, FixPoint16Long b) { return a.m_Value < b.m_Value; } public static bool operator >(FixPoint16Long a, FixPoint16Long b) { return a.m_Value > b.m_Value; } public static bool operator <=(FixPoint16Long a, FixPoint16Long b) { return a.m_Value <= b.m_Value; } public static bool operator >=(FixPoint16Long a, FixPoint16Long b) { return a.m_Value >= b.m_Value; } public static FixPoint16Long operator <<(FixPoint16Long a, int shift) { return new() { m_Value = a.m_Value << shift }; } public static FixPoint16Long operator >> (FixPoint16Long a, int shift) { return new() { m_Value = a.m_Value >> shift }; } public static FixPoint16Long operator +(FixPoint16Long a, FixPoint16Long b) { return new() { m_Value = a.m_Value + b.m_Value }; } public static FixPoint16Long operator -(FixPoint16Long a, FixPoint16Long b) { return new() { m_Value = a.m_Value - b.m_Value }; } public static FixPoint16Long operator *(FixPoint16Long a, FixPoint16Long b) { Int128 bigA = a.m_Value; Int128 bigB = b.m_Value; var result = ((bigA * bigB) + c_Half) >> c_Shift; #if RANGE_CHECK if (result < long.MinValue || result > long.MaxValue) { throw new ArithmeticException($"Multiplication result out of range: {result}"); } #endif return new() { m_Value = (long)result }; } public static FixPoint16Long operator /(FixPoint16Long a, FixPoint16Long b) { #if RANGE_CHECK if (b.m_Value == 0) { throw new ArithmeticException("Divison by zero"); } #endif Int128 result; if (((ulong)a.m_Value & 0x8000000000000000UL) == ((ulong)b.m_Value & 0x8000000000000000UL)) { result = (((Int128)a.m_Value << c_Shift) + (b.m_Value / 2)) / b.m_Value; } else { result = (((Int128)a.m_Value << c_Shift) - (b.m_Value / 2)) / b.m_Value; } #if RANGE_CHECK if (result < long.MinValue || result > long.MaxValue) { throw new ArithmeticException($"Division result out of range: {result}"); } #endif return new() { m_Value = (long)result }; } public static FixPoint16Long operator -(FixPoint16Long a) { return new() { m_Value = -a.m_Value }; } public static FixPoint16Long operator *(FixPoint16Long a, int value) { return new() { m_Value = a.m_Value * value }; } public static FixPoint16Long operator /(FixPoint16Long a, int value) { #if RANGE_CHECK if (value == 0) { throw new ArithmeticException("Divison by zero"); } #endif if (((a.m_Value >> 32) & 0x80000000) == (value & 0x80000000)) { return new() { m_Value = (long)(((Int128)a.m_Value + (value / 2)) / value) }; } return new() { m_Value = (long)(((Int128)a.m_Value - (value / 2)) / value) }; } public static implicit operator FixPoint16Long(int value) { return new(value); } public static implicit operator FixPoint16Long(long value) { return new(value); } public static implicit operator FixPoint16Long(FixPoint16 value) { return new() { m_Value = value.m_Value }; } public static explicit operator FixPoint16Long(double value) { return new(value); } public static explicit operator FixPoint16Long(float value) { return new(value); } public static explicit operator int(FixPoint16Long value) { return (int)value.ToLong(); } public static explicit operator long(FixPoint16Long value) { return value.ToLong(); } public static explicit operator double(FixPoint16Long value) { return value.ToDouble(); } public static explicit operator float(FixPoint16Long value) { return value.ToFloat(); } public static FixPoint16Long Floor(FixPoint16Long value) { return value.ToLongFloor(); } public static FixPoint16Long Ceil(FixPoint16Long value) { return value.ToLongCeil(); } public static FixPoint16Long Round(FixPoint16Long value) { return value.ToLongRound(); } public static int Sign(FixPoint16Long value) { return System.Math.Sign(value.m_Value); } public static FixPoint16Long Abs(FixPoint16Long value) { return new() { m_Value = System.Math.Abs(value.m_Value) }; } public static FixPoint16Long Min(FixPoint16Long value1, FixPoint16Long value2) { return new() { m_Value = System.Math.Min(value1.m_Value, value2.m_Value) }; } public static FixPoint16Long Max(FixPoint16Long value1, FixPoint16Long value2) { return new() { m_Value = System.Math.Max(value1.m_Value, value2.m_Value) }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static FixPoint16Long Length(FixPoint16Long a, FixPoint16Long b) { var aSquared = a.m_Value * a.m_Value; var bSquared = b.m_Value * b.m_Value; var value = aSquared + bSquared; #if RANGE_CHECK if (value < 0) { throw new ArithmeticException($"Length squared out of range: {value}"); } #endif return new() { m_Value = IntMath.Sqrt(value) }; } private const int c_Shift = 16; private const long c_IntegerMin = -140737488355327L; private const long c_IntegerMax = 140737488355327L; private const long c_Half = 32786L; private const long c_FractionMask = 0x000000000000ffffL; private const double c_Multiplier = 65536.0; private const double c_Divisor = 1.0 / 65536.0; private const float c_MultiplierFloat = 65536.0f; private const float c_DivisorFloat = (float)c_Divisor; public long m_Value; public static readonly FixPoint16Long Zero = new() { m_Value = 0 }; public static readonly FixPoint16Long MinValue = new() { m_Value = long.MinValue }; public static readonly FixPoint16Long MaxValue = new() { m_Value = long.MaxValue }; public static readonly FixPoint16Long Epsilon = new() { m_Value = 1 }; public static readonly FixPoint16Long MinusEpsilon = new() { m_Value = -1 }; public static readonly FixPoint16Long One = new(1); public static readonly FixPoint16Long MinusOne = new(-1); public static readonly FixPoint16Long Half = new() { m_Value = One.m_Value / 2 }; public static readonly FixPoint16Long MinusHalf = -Half; }