Created
January 25, 2025 23:13
-
-
Save lucasteles/9356e0074339df510f098855d1f83025 to your computer and use it in GitHub Desktop.
Fixed point number
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public readonly struct Fixed : | |
IEquatable<Fixed>, | |
IComparable<Fixed>, | |
IFormattable, IUtf8SpanFormattable, | |
IComparisonOperators<Fixed, Fixed, bool>, | |
IAdditionOperators<Fixed, Fixed, Fixed>, | |
ISubtractionOperators<Fixed, Fixed, Fixed>, | |
IIncrementOperators<Fixed>, | |
IDecrementOperators<Fixed> | |
{ | |
const float FloatFactor = 65536.0f; | |
const int DigitOffset = 16; | |
public static readonly Fixed One = new(1); | |
public static readonly Fixed Zero = new(0); | |
public static readonly Fixed E = new(Math.E); | |
public static readonly Fixed Pi = new(Math.PI); | |
public static readonly Fixed MaxValue = new(raw: long.MaxValue); | |
public static readonly Fixed MinValue = new(raw: long.MinValue); | |
public readonly long RawValue; | |
Fixed(long raw) => RawValue = raw; | |
public Fixed(float value) | |
{ | |
var input = value * FloatFactor; | |
var rounded = MathF.Round(input); | |
RawValue = (long)rounded; | |
} | |
public Fixed(double value) | |
{ | |
var input = value * FloatFactor; | |
var rounded = Math.Round(input); | |
RawValue = (long)rounded; | |
} | |
public Fixed(int value) => RawValue = value << DigitOffset; | |
public Fixed(short value) => RawValue = value << DigitOffset; | |
public static Fixed Abs(Fixed fix) => fix.RawValue < 0 ? new(-fix.RawValue) : fix; | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static bool IsInteger(Fixed value) => value == Truncate(value); | |
public static Fixed Truncate(Fixed x) => new(x.ToInt()); | |
public static Fixed Round(Fixed x, int digits, MidpointRounding mode = MidpointRounding.ToEven) => | |
new(Math.Round(x.ToDouble(), digits, mode)); | |
public static bool IsNegative(Fixed value) => value.RawValue < 0; | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static bool IsPositive(Fixed value) => value.RawValue > 0; | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static bool IsZero(Fixed value) => value.RawValue is 0; | |
public static Fixed Max(Fixed a, Fixed b) => b.RawValue > a.RawValue ? b : a; | |
public static Fixed Min(Fixed a, Fixed b) => b.RawValue < a.RawValue ? b : a; | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static bool Equals(Fixed left, Fixed right) => left.RawValue == right.RawValue; | |
public bool Equals(Fixed other) => Equals(this, other); | |
public override bool Equals(object? obj) => obj is Fixed other && Equals(other); | |
public override int GetHashCode() => RawValue.GetHashCode(); | |
public Fixed Truncate() => Truncate(this); | |
public Fixed Round(int digits, MidpointRounding mode = MidpointRounding.ToEven) => Round(this, digits, mode); | |
public float ToFloat() => RawValue / FloatFactor; | |
public double ToDouble() => RawValue / (double)FloatFactor; | |
public long ToLong() => RawValue >> DigitOffset; | |
public int ToInt() => (int)ToLong(); | |
public override string ToString() => $"F{ToDouble():F16}"; | |
public string ToString(string? format, IFormatProvider? formatProvider) => | |
ToDouble().ToString(format, formatProvider); | |
public int CompareTo(Fixed other) => RawValue.CompareTo(other.RawValue); | |
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, | |
IFormatProvider? provider) => ToDouble().TryFormat(destination, out charsWritten, format, provider); | |
public bool TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, | |
IFormatProvider? provider) => ToDouble().TryFormat(utf8Destination, out bytesWritten, format, provider); | |
public static Fixed operator +(Fixed left, Fixed right) => new(raw: left.RawValue + right.RawValue); | |
public static Fixed operator +(Fixed value) => new(raw: +value.RawValue); | |
public static Fixed operator ++(Fixed value) => new(raw: value.RawValue + 1); | |
public static Fixed operator -(Fixed left, Fixed right) => new(raw: left.RawValue - right.RawValue); | |
public static Fixed operator -(Fixed value) => new(raw: -value.RawValue); | |
public static Fixed operator --(Fixed value) => new(raw: value.RawValue - 1); | |
public static Fixed operator *(Fixed left, Fixed right) => | |
new(raw: (left.RawValue * right.RawValue) >> DigitOffset); | |
public static Fixed operator /(Fixed left, Fixed right) => | |
new(raw: (left.RawValue << DigitOffset) / right.RawValue); | |
public static Fixed operator %(Fixed left, Fixed right) => new(left.ToDouble() % right.ToDouble()); | |
public static bool operator ==(Fixed left, Fixed right) => Equals(left, right); | |
public static bool operator !=(Fixed left, Fixed right) => !Equals(left, right); | |
public static bool operator >(Fixed left, Fixed right) => left.RawValue > right.RawValue; | |
public static bool operator >=(Fixed left, Fixed right) => left.RawValue >= right.RawValue; | |
public static bool operator <(Fixed left, Fixed right) => left.RawValue < right.RawValue; | |
public static bool operator <=(Fixed left, Fixed right) => left.RawValue <= right.RawValue; | |
public static implicit operator Fixed(int value) => new(value); | |
public static implicit operator Fixed(short value) => new(value); | |
public static implicit operator Fixed(float value) => new(value); | |
public static implicit operator Fixed(double value) => new(value); | |
public static implicit operator double(Fixed value) => value.ToDouble(); | |
public static implicit operator float(Fixed value) => value.ToFloat(); | |
public static explicit operator int(Fixed value) => value.ToInt(); | |
public static explicit operator long(Fixed value) => value.ToLong(); | |
public static bool Gt(Fixed left, Fixed right) => left > right; | |
public static bool Lt(Fixed left, Fixed right) => left < right; | |
public static bool Gte(Fixed left, Fixed right) => left >= right; | |
public static bool Lte(Fixed left, Fixed right) => left <= right; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class FixedTests | |
{ | |
[Test] | |
public void ShouldReturnMax() => Fixed.MaxValue.RawValue.Should().Be(long.MaxValue); | |
[Test] | |
public void ShouldReturnMin() => Fixed.MinValue.RawValue.Should().Be(long.MinValue); | |
[Test] | |
public void ShouldPi() => Fixed.Pi.ToFloat().Should().BeApproximately(MathF.PI, 0.0001f); | |
[Test] | |
public void ShouldTruncate() | |
{ | |
Fixed value = 1.785; | |
value.Truncate().Should().Be(Fixed.One); | |
} | |
[Test] | |
public void ShouldRoundUp() | |
{ | |
Fixed value = 1.785; | |
value.Round(2).Should().Be(1.79); | |
} | |
[Test] | |
public void ShouldRoundDown() | |
{ | |
Fixed value = 1.784; | |
value.Round(2).Should().Be(1.78); | |
} | |
[Test] | |
public void ShouldSum() | |
{ | |
Fixed a = 1.56; | |
Fixed b = 1.56; | |
(a + b).Should().Be(3.12); | |
} | |
[Test] | |
public void ShouldSubtract() | |
{ | |
Fixed a = 3.12; | |
Fixed b = 1.56; | |
(a - b).Should().Be(1.56); | |
} | |
[Test] | |
public void ShouldSubtractNegative() | |
{ | |
Fixed a = 1.56; | |
Fixed b = 3.12; | |
(a - b).Should().Be(-1.56); | |
} | |
[Test] | |
public void ShouldMultiply() | |
{ | |
Fixed a = 1.56; | |
(a * 2).Should().Be(3.12); | |
} | |
[Test] | |
public void ShouldDivide() | |
{ | |
Fixed a = 3.12; | |
(a / 2).Should().Be(1.56); | |
} | |
[Test] | |
public void ShouldCompareGreater() | |
{ | |
Fixed a = 1.12; | |
Fixed b = 1.1; | |
(a > b).Should().BeTrue(); | |
(a >= b).Should().BeTrue(); | |
} | |
[Test] | |
public void ShouldCompareLess() | |
{ | |
Fixed a = 1.1; | |
Fixed b = 1.12; | |
(a < b).Should().BeTrue(); | |
(a <= b).Should().BeTrue(); | |
} | |
[Test] | |
public void ShouldModInt() | |
{ | |
Fixed a = 3; | |
Fixed b = 2; | |
(a % b).Should().Be(1); | |
} | |
[Test] | |
public void ShouldModFloat() | |
{ | |
Fixed a = 3.5; | |
Fixed b = 2; | |
(a % b).Should().Be(1.5); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment