From 99492e3e3f7a79e017a9ed97fca68c9a687b9fbd Mon Sep 17 00:00:00 2001 From: Jezithyr Date: Mon, 22 Jul 2024 14:07:31 -0700 Subject: [PATCH] Added fixedpoint 4 (#29834) * Added fixedpoint 4, which is basically just fixedpoint2 but with 4 points of precision and using a long instead of an int to store values. --- Content.Shared/FixedPoint/FixedPoint2.cs | 10 +- Content.Shared/FixedPoint/FixedPoint4.cs | 339 +++++++++++++++++++++++ 2 files changed, 345 insertions(+), 4 deletions(-) create mode 100644 Content.Shared/FixedPoint/FixedPoint4.cs diff --git a/Content.Shared/FixedPoint/FixedPoint2.cs b/Content.Shared/FixedPoint/FixedPoint2.cs index 6439ee6c5e2d9c..7316d22f113a7a 100644 --- a/Content.Shared/FixedPoint/FixedPoint2.cs +++ b/Content.Shared/FixedPoint/FixedPoint2.cs @@ -48,6 +48,8 @@ public static FixedPoint2 New(int value) public static FixedPoint2 FromCents(int value) => new(value); + public static FixedPoint2 FromHundredths(int value) => new(value); + public static FixedPoint2 New(float value) { return new((int) ApplyFloatEpsilon(value * ShiftConstant)); @@ -245,14 +247,14 @@ public static FixedPoint2 Dist(FixedPoint2 a, FixedPoint2 b) return FixedPoint2.Abs(a - b); } - public static FixedPoint2 Clamp(FixedPoint2 reagent, FixedPoint2 min, FixedPoint2 max) + public static FixedPoint2 Clamp(FixedPoint2 number, FixedPoint2 min, FixedPoint2 max) { if (min > max) { throw new ArgumentException($"{nameof(min)} {min} cannot be larger than {nameof(max)} {max}"); } - return reagent < min ? min : reagent > max ? max : reagent; + return number < min ? min : number > max ? max : number; } public override readonly bool Equals(object? obj) @@ -274,7 +276,7 @@ public void Deserialize(string value) if (value == "MaxValue") Value = int.MaxValue; else - this = New(Parse.Float(value)); + this = New(Parse.Double(value)); } public override readonly string ToString() => $"{ShiftDown().ToString(CultureInfo.InvariantCulture)}"; @@ -314,7 +316,7 @@ public readonly int CompareTo(FixedPoint2 other) } - public static class FixedPointEnumerableExt + public static class FixedPoint2EnumerableExt { public static FixedPoint2 Sum(this IEnumerable source) { diff --git a/Content.Shared/FixedPoint/FixedPoint4.cs b/Content.Shared/FixedPoint/FixedPoint4.cs new file mode 100644 index 00000000000000..b17587f684b004 --- /dev/null +++ b/Content.Shared/FixedPoint/FixedPoint4.cs @@ -0,0 +1,339 @@ +using System.Globalization; +using System.Linq; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.FixedPoint +{ + /// + /// Represents a quantity of something, to a precision of 0.01. + /// To enforce this level of precision, floats are shifted by 2 decimal points, rounded, and converted to an int. + /// + [Serializable, CopyByRef] + public struct FixedPoint4 : ISelfSerialize, IComparable, IEquatable, IFormattable + { + public long Value { get; private set; } + private const long Shift = 4; + private const long ShiftConstant = 10000; // Must be equal to pow(10, Shift) + + public static FixedPoint4 MaxValue { get; } = new(long.MaxValue); + public static FixedPoint4 Epsilon { get; } = new(1); + public static FixedPoint4 Zero { get; } = new(0); + + // This value isn't picked by any proper testing, don't @ me. + private const float FloatEpsilon = 0.00001f; + +#if DEBUG + static FixedPoint4() + { + // ReSharper disable once CompareOfFloatsByEqualityOperator + DebugTools.Assert(Math.Pow(10, Shift) == ShiftConstant, "ShiftConstant must be equal to pow(10, Shift)"); + } +#endif + + private readonly double ShiftDown() + { + return Value / (double) ShiftConstant; + } + + private FixedPoint4(long value) + { + Value = value; + } + + public static FixedPoint4 New(long value) + { + return new(value * ShiftConstant); + } + public static FixedPoint4 FromTenThousandths(long value) => new(value); + + public static FixedPoint4 New(float value) + { + return new((long) ApplyFloatEpsilon(value * ShiftConstant)); + } + + private static float ApplyFloatEpsilon(float value) + { + return value + FloatEpsilon * Math.Sign(value); + } + + private static double ApplyFloatEpsilon(double value) + { + return value + FloatEpsilon * Math.Sign(value); + } + + /// + /// Create the closest for a float value, always rounding up. + /// + public static FixedPoint4 NewCeiling(float value) + { + return new((long) MathF.Ceiling(value * ShiftConstant)); + } + + public static FixedPoint4 New(double value) + { + return new((long) ApplyFloatEpsilon(value * ShiftConstant)); + } + + public static FixedPoint4 New(string value) + { + return New(Parse.Float(value)); + } + + public static FixedPoint4 operator +(FixedPoint4 a) => a; + + public static FixedPoint4 operator -(FixedPoint4 a) => new(-a.Value); + + public static FixedPoint4 operator +(FixedPoint4 a, FixedPoint4 b) + => new(a.Value + b.Value); + + public static FixedPoint4 operator -(FixedPoint4 a, FixedPoint4 b) + => new(a.Value - b.Value); + + public static FixedPoint4 operator *(FixedPoint4 a, FixedPoint4 b) + { + return new(b.Value * a.Value / ShiftConstant); + } + + public static FixedPoint4 operator *(FixedPoint4 a, float b) + { + return new((long) ApplyFloatEpsilon(a.Value * b)); + } + + public static FixedPoint4 operator *(FixedPoint4 a, double b) + { + return new((long) ApplyFloatEpsilon(a.Value * b)); + } + + public static FixedPoint4 operator *(FixedPoint4 a, long b) + { + return new(a.Value * b); + } + + public static FixedPoint4 operator /(FixedPoint4 a, FixedPoint4 b) + { + return new((long) (ShiftConstant * (long) a.Value / b.Value)); + } + + public static FixedPoint4 operator /(FixedPoint4 a, float b) + { + return new((long) ApplyFloatEpsilon(a.Value / b)); + } + + public static bool operator <=(FixedPoint4 a, long b) + { + return a <= New(b); + } + + public static bool operator >=(FixedPoint4 a, long b) + { + return a >= New(b); + } + + public static bool operator <(FixedPoint4 a, long b) + { + return a < New(b); + } + + public static bool operator >(FixedPoint4 a, long b) + { + return a > New(b); + } + + public static bool operator ==(FixedPoint4 a, long b) + { + return a == New(b); + } + + public static bool operator !=(FixedPoint4 a, long b) + { + return a != New(b); + } + + public static bool operator ==(FixedPoint4 a, FixedPoint4 b) + { + return a.Equals(b); + } + + public static bool operator !=(FixedPoint4 a, FixedPoint4 b) + { + return !a.Equals(b); + } + + public static bool operator <=(FixedPoint4 a, FixedPoint4 b) + { + return a.Value <= b.Value; + } + + public static bool operator >=(FixedPoint4 a, FixedPoint4 b) + { + return a.Value >= b.Value; + } + + public static bool operator <(FixedPoint4 a, FixedPoint4 b) + { + return a.Value < b.Value; + } + + public static bool operator >(FixedPoint4 a, FixedPoint4 b) + { + return a.Value > b.Value; + } + + public readonly float Float() + { + return (float) ShiftDown(); + } + + public readonly double Double() + { + return ShiftDown(); + } + + public readonly long Long() + { + return Value / ShiftConstant; + } + + public readonly int Int() + { + return (int)Long(); + } + + // Implicit operators ftw + public static implicit operator FixedPoint4(FixedPoint2 n) => New(n.Int()); + public static implicit operator FixedPoint4(float n) => New(n); + public static implicit operator FixedPoint4(double n) => New(n); + public static implicit operator FixedPoint4(int n) => New(n); + public static implicit operator FixedPoint4(long n) => New(n); + + public static explicit operator FixedPoint2(FixedPoint4 n) => n.Int(); + public static explicit operator float(FixedPoint4 n) => n.Float(); + public static explicit operator double(FixedPoint4 n) => n.Double(); + public static explicit operator int(FixedPoint4 n) => n.Int(); + public static explicit operator long(FixedPoint4 n) => n.Long(); + + public static FixedPoint4 Min(params FixedPoint4[] fixedPoints) + { + return fixedPoints.Min(); + } + + public static FixedPoint4 Min(FixedPoint4 a, FixedPoint4 b) + { + return a < b ? a : b; + } + + public static FixedPoint4 Max(FixedPoint4 a, FixedPoint4 b) + { + return a > b ? a : b; + } + + public static long Sign(FixedPoint4 value) + { + if (value < Zero) + { + return -1; + } + + if (value > Zero) + { + return 1; + } + + return 0; + } + + public static FixedPoint4 Abs(FixedPoint4 a) + { + return FromTenThousandths(Math.Abs(a.Value)); + } + + public static FixedPoint4 Dist(FixedPoint4 a, FixedPoint4 b) + { + return FixedPoint4.Abs(a - b); + } + + public static FixedPoint4 Clamp(FixedPoint4 number, FixedPoint4 min, FixedPoint4 max) + { + if (min > max) + { + throw new ArgumentException($"{nameof(min)} {min} cannot be larger than {nameof(max)} {max}"); + } + + return number < min ? min : number > max ? max : number; + } + + public override readonly bool Equals(object? obj) + { + return obj is FixedPoint4 unit && + Value == unit.Value; + } + + public override readonly int GetHashCode() + { + // ReSharper disable once NonReadonlyMemberInGetHashCode + return HashCode.Combine(Value); + } + + public void Deserialize(string value) + { + // TODO implement "lossless" serializer. + // I.e., dont use floats. + if (value == "MaxValue") + Value = int.MaxValue; + else + this = New(Parse.Double(value)); + } + + public override readonly string ToString() => $"{ShiftDown().ToString(CultureInfo.InvariantCulture)}"; + + public string ToString(string? format, IFormatProvider? formatProvider) + { + return ToString(); + } + + public readonly string Serialize() + { + // TODO implement "lossless" serializer. + // I.e., dont use floats. + if (Value == int.MaxValue) + return "MaxValue"; + + return ToString(); + } + + public readonly bool Equals(FixedPoint4 other) + { + return Value == other.Value; + } + + public readonly int CompareTo(FixedPoint4 other) + { + if (other.Value > Value) + { + return -1; + } + if (other.Value < Value) + { + return 1; + } + return 0; + } + + } + + public static class FixedPoint4EnumerableExt + { + public static FixedPoint4 Sum(this IEnumerable source) + { + var acc = FixedPoint4.Zero; + + foreach (var n in source) + { + acc += n; + } + + return acc; + } + } +}