365 lines
11 KiB
C#
365 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 );
|
|
}
|
|
|
|
} |