366 lines
11 KiB
C#
366 lines
11 KiB
C#
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Represents a fixed-point value in centimeters, stored as an integer.
|
|
/// Implements INumber<Cm> for full generic math support.
|
|
/// </summary>
|
|
sealed public record Cm( int value ) : INumber<Cm>
|
|
{
|
|
// --- Constants and Identities ---
|
|
|
|
/// <summary>
|
|
/// Represents the multiplicative identity (1.0).
|
|
/// The underlying value is 100 because 1.0 meter = 100 centimeters.
|
|
/// </summary>
|
|
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<char> 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<char> 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<char> 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<char> 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>( TOther value, out Cm result ) where TOther : INumberBase<TOther>
|
|
{
|
|
// 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>( TOther value, out Cm result ) where TOther : INumberBase<TOther>
|
|
{
|
|
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>( TOther value, out Cm result ) where TOther : INumberBase<TOther>
|
|
{
|
|
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<TOther>( Cm value, out TOther result ) where TOther : INumberBase<TOther>
|
|
{
|
|
// 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<TOther>( Cm value, out TOther result ) where TOther : INumberBase<TOther>
|
|
{
|
|
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<TOther>( Cm value, out TOther result ) where TOther : INumberBase<TOther>
|
|
{
|
|
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<char> destination, out int charsWritten, ReadOnlySpan<char> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides extension methods for easy conversion to the Cm type.
|
|
/// </summary>
|
|
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 );
|
|
}
|
|
|
|
}
|