using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; #nullable enable sealed public record Vector3Cm( Cm x, Cm y, Cm z ) { public bool Equals( Vector3Cm? other ) => x == other?.x && y == other?.y && z == other?.z; public override int GetHashCode() => x.GetHashCode() * 10000019 + z.GetHashCode() * 50000047 + y.GetHashCode(); } /// /// Represents a fixed-point value in centimeters, stored as an integer. /// Implements INumber for full generic math support. /// sealed public record Cm(int value) : INumber { // --- Constants and Identities --- /// /// Represents the multiplicative identity (1.0). /// The underlying value is 100 because 1.0 meter = 100 centimeters. /// public static Cm One => new Cm(100); public static Cm Zero => new Cm(0); public static int Radix => 10; public static Cm AdditiveIdentity => Zero; public static Cm MultiplicativeIdentity => One; public float Float => ((float)value) * 0.01f; public double Double => ((double)value) * 0.01; // --- Static Conversion Methods --- public static Cm From(float v) => new Cm((int)(v * 100.0f + 0.5f)); public static Cm From(double v) => new Cm((int)(v * 100.0 + 0.5)); // --- Standard Functions --- public static Cm Abs(Cm c) => new Cm(Math.Abs(c.value)); public static Cm MaxMagnitude(Cm x, Cm y) => Abs(x) > Abs(y) ? x : y; public static Cm MaxMagnitudeNumber(Cm x, Cm y) => MaxMagnitude(x, y); // Same for this type public static Cm MinMagnitude(Cm x, Cm y) => Abs(x) < Abs(y) ? x : y; public static Cm MinMagnitudeNumber(Cm x, Cm y) => MinMagnitude(x, y); // Same for this type // --- Type Property Checks --- public static bool IsCanonical(Cm c) => true; public static bool IsComplexNumber(Cm c) => false; public static bool IsEvenInteger(Cm c) => (c.value % 100 == 0) && (c.value / 100 % 2 == 0); public static bool IsFinite(Cm c) => true; public static bool IsImaginaryNumber(Cm c) => false; public static bool IsInfinity(Cm c) => false; public static bool IsInteger(Cm c) => c.value % 100 == 0; public static bool IsNaN(Cm c) => false; public static bool IsNegative(Cm c) => c.value < 0; public static bool IsNegativeInfinity(Cm c) => false; public static bool IsNormal(Cm c) => c.value != 0; public static bool IsOddInteger(Cm c) => (c.value % 100 == 0) && (c.value / 100 % 2 != 0); public static bool IsPositive(Cm c) => c.value > 0; public static bool IsPositiveInfinity(Cm c) => false; public static bool IsRealNumber(Cm c) => true; // It is a real number public static bool IsSubnormal(Cm c) => false; public static bool IsZero(Cm c) => c.value == 0; // --- Parsing --- public static Cm Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Number, provider); public static Cm Parse(ReadOnlySpan s, IFormatProvider? provider) => Parse(s, NumberStyles.Number, provider); public static Cm Parse(string s, NumberStyles style, IFormatProvider? provider) { if (TryParse(s, style, provider, out var result)) { return result; } throw new FormatException($"Input string '{s}' was not in a correct format."); } public static Cm Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) { if (TryParse(s, style, provider, out var result)) { return result; } throw new FormatException($"Input string was not in a correct format."); } public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Cm result) => TryParse(s, NumberStyles.Number, provider, out result); public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out Cm result) => TryParse(s, NumberStyles.Number, provider, out result); public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Cm result) { if (decimal.TryParse(s, style, provider, out decimal decValue)) { result = new Cm((int)(decValue * 100m)); return true; } result = Cm.Zero; return false; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Cm result) { if (decimal.TryParse(s, style, provider, out decimal decValue)) { result = new Cm((int)(decValue * 100m)); return true; } result = Cm.Zero; return false; } // --- Generic Type Conversion --- public static bool TryConvertFromChecked(TOther value, out Cm result) where TOther : INumberBase { // For integer types, scale up. For floating point, convert. if (TOther.IsInteger(value)) { try { checked { result = new Cm(int.CreateChecked(value) * 100); return true; } } catch (OverflowException) { result = Cm.Zero; return false; } } // Convert floating point types if (typeof(TOther) == typeof(double)) { double d = double.CreateChecked(value); result = From(d); return true; } if (typeof(TOther) == typeof(float)) { float f = float.CreateChecked(value); result = From(f); return true; } if (typeof(TOther) == typeof(decimal)) { decimal m = decimal.CreateChecked(value); result = new Cm((int)(m * 100m)); return true; } result = Cm.Zero; return false; } public static bool TryConvertFromSaturating(TOther value, out Cm result) where TOther : INumberBase { if (TOther.IsInteger(value)) { result = new Cm(int.CreateSaturating(value) * 100); return true; } if (typeof(TOther) == typeof(double)) { double d = double.CreateSaturating(value); result = From(d); return true; } if (typeof(TOther) == typeof(float)) { float f = float.CreateSaturating(value); result = From(f); return true; } if (typeof(TOther) == typeof(decimal)) { decimal m = decimal.CreateSaturating(value); result = new Cm((int)(m * 100m)); return true; } result = Cm.Zero; return false; } public static bool TryConvertFromTruncating(TOther value, out Cm result) where TOther : INumberBase { if (TOther.IsInteger(value)) { result = new Cm(int.CreateTruncating(value) * 100); return true; } if (typeof(TOther) == typeof(double)) { double d = double.CreateTruncating(value); result = From(d); return true; } if (typeof(TOther) == typeof(float)) { float f = float.CreateTruncating(value); result = From(f); return true; } if (typeof(TOther) == typeof(decimal)) { decimal m = decimal.CreateTruncating(value); result = new Cm((int)(m * 100m)); return true; } result = Cm.Zero; return false; } public static bool TryConvertToChecked(Cm value, out TOther result) where TOther : INumberBase { // Convert our value (a count of centimeters) to another type. // This typically involves scaling down by 100. try { checked { if (TOther.IsInteger(TOther.Zero)) { result = TOther.CreateChecked(value.value / 100); return true; } // For floating points, perform floating point division result = TOther.CreateChecked(value.value) / TOther.CreateChecked(100); return true; } } catch (OverflowException) { result = default!; return false; } } public static bool TryConvertToSaturating(Cm value, out TOther result) where TOther : INumberBase { if (TOther.IsInteger(TOther.Zero)) { result = TOther.CreateSaturating(value.value / 100); return true; } result = TOther.CreateSaturating(value.value) / TOther.CreateSaturating(100); return true; } public static bool TryConvertToTruncating(Cm value, out TOther result) where TOther : INumberBase { if (TOther.IsInteger(TOther.Zero)) { result = TOther.CreateTruncating(value.value / 100); return true; } result = TOther.CreateTruncating(value.value) / TOther.CreateTruncating(100); return true; } // --- Comparison --- public int CompareTo(object? obj) { if (obj is Cm other) { return CompareTo(other); } return obj is null ? 1 : throw new ArgumentException("Object must be of type Cm.", nameof(obj)); } public int CompareTo(Cm? other) => value.CompareTo(other?.value); public bool Equals(Cm? other) => value == other?.value; public override int GetHashCode() => value.GetHashCode(); // --- Formatting --- public override string ToString() => (value / 100.0).ToString("F2", CultureInfo.InvariantCulture); public string ToString(string? format, IFormatProvider? formatProvider) { // Format as a floating point number return (value / 100.0).ToString(format, formatProvider); } public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) { return (value / 100.0).TryFormat(destination, out charsWritten, format, provider); } // --- Operators --- public static Cm operator +(Cm c) => c; public static Cm operator +(Cm left, Cm right) => new Cm(left.value + right.value); public static Cm operator -(Cm c) => new Cm(-c.value); public static Cm operator -(Cm left, Cm right) => new Cm(left.value - right.value); public static Cm operator ++(Cm c) => new Cm(c.value + 1); public static Cm operator --(Cm c) => new Cm(c.value - 1); // For fixed-point, multiplication/division require scaling public static Cm operator *(Cm left, Cm right) { // (a/100) * (b/100) = (a*b)/10000. To get back to our format, multiply by 100. // So, (a*b)/100. Use long to prevent intermediate overflow. return new Cm((int)(((long)left.value * right.value) / 100)); } public static Cm operator /(Cm left, Cm right) { // (a/100) / (b/100) = a/b. To get back to our format, multiply by 100. // So, (a*100)/b. Use long to prevent intermediate overflow. return new Cm((int)(((long)left.value * 100) / right.value)); } public static Cm operator %(Cm left, Cm right) => new Cm(left.value % right.value); // --- Comparison Operators --- public static bool operator <(Cm left, Cm right) => left.value < right.value; public static bool operator >(Cm left, Cm right) => left.value > right.value; public static bool operator <=(Cm left, Cm right) => left.value <= right.value; public static bool operator >=(Cm left, Cm right) => left.value >= right.value; } /// /// Provides extension methods for easy conversion to the Cm type. /// public static class CentEx { // --- Static Conversion Methods --- public static Cm From(float v) => new Cm((int)(v * 100.0f + 0.5f)); public static Cm From(double v) => new Cm((int)(v * 100.0 + 0.5)); //public static Cm Cm(this float r) => CentEx.From(r); //public static Cm Cm(this double r) => CentEx.From(r); extension(float r) { public Cm Cm => CentEx.From( r ); } extension(double r) { public Cm Cm => CentEx.From( r ); } }