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 ); } }