sharplib/math/Cent.cs
2025-10-15 21:50:37 -07:00

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