diff --git a/imm/Imm.cs b/imm/Imm.cs index 2657d78..30fc8f1 100644 --- a/imm/Imm.cs +++ b/imm/Imm.cs @@ -298,7 +298,7 @@ public record class Timed : Recorded where T : Timed public override T Process( Func fn, string reason = "", - [CallerMemberName] string dbgName = "", + [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0, [CallerArgumentExpression( "fn" )] string dbgExpression = "" ) @@ -313,7 +313,7 @@ public record class Timed : Recorded where T : Timed { Version = current.Meta.Version + 1, Reason = reason, - MemberName = dbgName, + MemberName = dbgMethod, FilePath = dbgPath, LineNumber = dbgLine, Expression = dbgExpression, diff --git a/imm/List.cs b/imm/List.cs index 5407809..cc63f13 100644 --- a/imm/List.cs +++ b/imm/List.cs @@ -24,39 +24,44 @@ public record class List : Timed>, IImmutableList // Helper to apply changes using the Process method private List Change( Func, ImmutableList> listChange, - [CallerMemberName] string memberName = "", - [CallerFilePath] string filePath = "", - [CallerLineNumber] int lineNumber = 0, - [CallerArgumentExpression("listChange")] string reason = "") + [CallerMemberName] string dbgMethod = "", + [CallerFilePath] string dbgPath = "", + [CallerLineNumber] int dbgLine = -1, + [CallerArgumentExpression( "listChange" )] string reason = "" ) { var newValues = listChange(Values); return ReferenceEquals(Values, newValues) ? this - : Process(l => l with { Values = newValues }, reason, memberName, filePath, lineNumber, reason); + : Process(l => l with { Values = newValues }, reason, dbgMethod, dbgPath, dbgLine, reason); } // --- IImmutableList implementation using the Change helper --- public T this[int index] => Values[index]; public int Count => Values.Count; - public List Add(T value) => Change(v => v.Add(value)); - public List AddRange(IEnumerable items) => Change(v => v.AddRange(items)); - public List Clear() => Change(v => v.Clear()); - // ... Implement all other IImmutableList methods similarly ... - #region IImmutableList Implementation - public List Insert( int index, T element ) => Change( v => v.Insert( index, element ) ); - public List InsertRange( int index, IEnumerable items ) => Change( v => v.InsertRange( index, items ) ); - public List Remove( T value, IEqualityComparer? equalityComparer ) => Change( v => v.Remove( value, equalityComparer ) ); - public List Remove( T value ) => Remove( value, EqualityComparer.Default ); - public List RemoveAll( Predicate match ) => Change( v => v.RemoveAll( match ) ); - public List RemoveAt( int index ) => Change( v => v.RemoveAt( index ) ); - public List RemoveRange( IEnumerable items, IEqualityComparer? equalityComparer ) => Change( v => v.RemoveRange( items, equalityComparer ) ); - public List RemoveRange( int index, int count ) => Change( v => v.RemoveRange( index, count ) ); - public List Replace( T oldValue, T newValue, IEqualityComparer? equalityComparer ) => Change( v => v.Replace( oldValue, newValue, equalityComparer ) ); - public List SetItem( int index, T value ) => Change( v => v.SetItem( index, value ) ); + public List Add(T value, [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1) => Change(v => v.Add(value), dbgMethod, dbgPath, dbgLine); + public List AddRange(IEnumerable items, [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1) => Change(v => v.AddRange(items), dbgMethod, dbgPath, dbgLine); + public List Clear([CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1) => Change(v => v.Clear(), dbgMethod, dbgPath, dbgLine); + #region IImmutableList Implementati, dbgMethod, dbgPath, dbgLineon + public List Insert( int index, T element, [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1 ) => Change( v => v.Insert( index, element ) , dbgMethod, dbgPath, dbgLine); + public List InsertRange( int index, IEnumerable items, [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1 ) => Change( v => v.InsertRange( index, items ) , dbgMethod, dbgPath, dbgLine); + public List Remove( T value, IEqualityComparer? equalityComparer, [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1 ) => Change( v => v.Remove( value, equalityComparer ) , dbgMethod, dbgPath, dbgLine); + public List Remove( T value, [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1 ) => Remove( value, EqualityComparer.Default , dbgMethod, dbgPath, dbgLine); + public List RemoveAll( Predicate match, [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1 ) => Change( v => v.RemoveAll( match ) , dbgMethod, dbgPath, dbgLine); + public List RemoveAt( int index, [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1 ) => Change( v => v.RemoveAt( index ) , dbgMethod, dbgPath, dbgLine); + public List RemoveRange( int index, int count, [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1 ) => Change( v => v.RemoveRange( index, count ) , dbgMethod, dbgPath, dbgLine); + public List RemoveRange( IEnumerable items, IEqualityComparer? equalityComparer, [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1 ) => Change( v => v.RemoveRange( items, equalityComparer ) , dbgMethod, dbgPath, dbgLine); + public List Replace( T oldValue, T newValue, IEqualityComparer? equalityComparer, [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1 ) => Change( v => v.Replace( oldValue, newValue, equalityComparer ) , dbgMethod, dbgPath, dbgLine); + public List SetItem( int index, T value, [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1 ) => Change( v => v.SetItem( index, value ) , dbgMethod, dbgPath, dbgLine); + + + public int IndexOf( T item, int index, int count, IEqualityComparer? equalityComparer ) => Values.IndexOf( item, index, count, equalityComparer ?? EqualityComparer.Default ); - public int IndexOf( T item ) => IndexOf( item, 0, Count, EqualityComparer.Default ); - public int LastIndexOf( T item, int index, int count, IEqualityComparer? equalityComparer ) => Values.LastIndexOf( item, index, count, equalityComparer ?? EqualityComparer.Default ); + public int LastIndexOf( T item, int index, int count, IEqualityComparer? equalityComparer) => Values.LastIndexOf( item, index, count, equalityComparer ?? EqualityComparer.Default ); + + public int IndexOf( T item, [CallerMemberName] string dbgMethod = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1 ) => IndexOf( item, 0, Count, EqualityComparer.Default); + + IImmutableList IImmutableList.Clear() => Clear(); IImmutableList IImmutableList.Add( T value ) => Add( value ); IImmutableList IImmutableList.AddRange( IEnumerable items ) => AddRange( items ); diff --git a/logging/Log.cs b/logging/Log.cs index 506ef88..0d72340 100644 --- a/logging/Log.cs +++ b/logging/Log.cs @@ -204,25 +204,25 @@ static public class log static ImmutableDictionary s_shortname = ImmutableDictionary.Empty; - public LogEvent( LogType logType, string msg, string path, int line, string member, string cat, string exp, object? obj ) + public LogEvent( LogType logType, string msg, string dbgPath, int dbgLine, string dbgMethod, string cat, string exp, object? obj ) { //Cache the automatic category names // R A R E and S L O W and S A F E if( string.IsNullOrEmpty( cat ) ) { - var pathHash = path.GetHashCode(); + var pathHash = dbgPath.GetHashCode(); if( s_shortname.TryGetValue( pathHash, out var autoCat ) ) { cat = autoCat; } else { - var pathPieces = path.Split( '\\' ); + var pathPieces = dbgPath.Split( '\\' ); if( pathPieces.Length < 2 ) { - pathPieces = path.Split( '/' ); + pathPieces = dbgPath.Split( '/' ); } var lastDir = pathPieces[pathPieces.Length - 2]; @@ -236,18 +236,18 @@ static public class log Time = DateTime.Now; LogType = logType; Msg = msg; - Path = path; - Line = line; - Member = member; + Path = dbgPath; + Line = dbgLine; + Member = dbgMethod; Cat = cat; Exp = exp; Obj = obj; } } - static LogEvent CreateLogEvent( LogType logType, string msg, string cat, object? obj, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", string exp = "" ) + static LogEvent CreateLogEvent( LogType logType, string msg, string cat, object? obj, [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "", string exp = "" ) { - var logEvent = new LogEvent( logType, msg, path, line, member, cat, exp, obj ); + var logEvent = new LogEvent( logType, msg, dbgPath, dbgLine, dbgMethod, cat, exp, obj ); return logEvent; } @@ -324,9 +324,9 @@ static public class log return rel; } - static public string thisFilePath( [CallerFilePath] string path = "" ) + static public string thisFilePath( [CallerFilePath] string dbgPath = "" ) { - return relativePath( path ); + return relativePath( dbgPath ); } #endregion // Util @@ -342,7 +342,7 @@ static public class log /* static public void info( string msg, string cat = "", object? obj = null, - [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) + [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) */ static public T var( T val, [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerArgumentExpression( "val" )] string dbgExp = "" ) @@ -358,42 +358,42 @@ static public class log log.info( $"| Done", "", null, dbgPath, dbgLine, dbgName, dbgExp ); } - static public void fatal( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) + static public void fatal( string msg, string cat = "", object? obj = null, [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) { - logBase( msg, LogType.Fatal, path, line, member, cat, dbgExp, obj ); + logBase( msg, LogType.Fatal, dbgPath, dbgLine, dbgMethod, cat, dbgExp, obj ); } [StackTraceHidden] - static public void error( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) + static public void error( string msg, string cat = "", object? obj = null, [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) { - logBase( msg, LogType.Error, path, line, member, cat, dbgExp, obj ); + logBase( $"{dbgMethod}: {msg}", LogType.Error, dbgPath, dbgLine, dbgMethod, cat, dbgExp, obj ); } [StackTraceHidden] - static public void warn( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) + static public void warn( string msg, string cat = "", object? obj = null, [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) { - logBase( msg, LogType.Warn, path, line, member, cat, dbgExp, obj ); + logBase( msg, LogType.Warn, dbgPath, dbgLine, dbgMethod, cat, dbgExp, obj ); } - static public void high( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) + static public void high( string msg, string cat = "", object? obj = null, [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) { - logBase( msg, LogType.High, path, line, member, cat, dbgExp, obj ); + logBase( msg, LogType.High, dbgPath, dbgLine, dbgMethod, cat, dbgExp, obj ); } static public void info( string msg, string cat = "", object? obj = null, - [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) + [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) { - logBase( msg, LogType.Info, path, line, member, cat, dbgExp, obj ); + logBase( msg, LogType.Info, dbgPath, dbgLine, dbgMethod, cat, dbgExp, obj ); } - static public void debug( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) + static public void debug( string msg, string cat = "", object? obj = null, [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) { - logBase( msg, LogType.Debug, path, line, member, cat, dbgExp, obj ); + logBase( msg, LogType.Debug, dbgPath, dbgLine, dbgMethod, cat, dbgExp, obj ); } - static public void trace( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) + static public void trace( string msg, string cat = "", object? obj = null, [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "", [CallerArgumentExpression( "msg" )] string dbgExp = "" ) { - logBase( msg, LogType.Trace, path, line, member, cat, dbgExp, obj ); + logBase( msg, LogType.Trace, dbgPath, dbgLine, dbgMethod, cat, dbgExp, obj ); } @@ -402,13 +402,13 @@ static public class log #endregion #region Helpers - static public void logProps( object obj, string header, LogType type = LogType.Debug, string cat = "", string prefix = "", [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression( "obj" )] string dbgExpObj = "" ) + static public void logProps( object obj, string header, LogType type = LogType.Debug, string cat = "", string prefix = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "", [CallerArgumentExpression( "obj" )] string dbgExpObj = "" ) { var list = refl.GetAllProperties( obj.GetType() ); lock( s_lock ) { - var evt = new LogEvent( type, header, path, line, member, cat, dbgExpObj, obj ); + var evt = new LogEvent( type, header, dbgPath, dbgLine, dbgMethod, cat, dbgExpObj, obj ); { // Use Add instead of Enqueue s_events.Add( evt ); @@ -419,11 +419,11 @@ static public class log { var v = pi.GetValue( obj ); - logBase( $"{prefix}{pi.Name} = {v}", type, path, line, member, dbgExpObj, cat ); + logBase( $"{prefix}{pi.Name} = {v}", type, dbgPath, dbgLine, dbgMethod, dbgExpObj, cat ); } catch( Exception ex ) { - logBase( $"Exception processing {pi.Name} {ex.Message}", LogType.Error, path, line, member, cat, dbgExpObj, obj ); + logBase( $"Exception processing {pi.Name} {ex.Message}", LogType.Error, dbgPath, dbgLine, dbgMethod, cat, dbgExpObj, obj ); } } @@ -462,16 +462,16 @@ static public class log } - static public LogEvent logCreateEvent( string msg, LogType type = LogType.Debug, string path = "", int line = -1, string member = "", string cat = "unk", string exp = "", object? obj = null ) + static public LogEvent logCreateEvent( string msg, LogType type = LogType.Debug, string dbgPath = "", int dbgLine = -1, string dbgMethod = "", string cat = "unk", string exp = "", object? obj = null ) { - LogEvent evt = new LogEvent( type, msg, path, line, member, cat, exp, obj ); + LogEvent evt = new LogEvent( type, msg, dbgPath, dbgLine, dbgMethod, cat, exp, obj ); return evt; } [StackTraceHidden] - static public void logBase( string msg, LogType type = LogType.Debug, string path = "", int line = -1, string member = "", string cat = "unk", string exp = "", object? obj = null ) + static public void logBase( string msg, LogType type = LogType.Debug, string dbgPath = "", int dbgLine = -1, string dbgMethod = "", string cat = "unk", string exp = "", object? obj = null ) { - var evt = logCreateEvent( msg, type, path, line, member, cat, exp ); + var evt = logCreateEvent( msg, type, dbgPath, dbgLine, dbgMethod, cat, exp ); s_callbacks.TryGetValue( type, out var callback ); diff --git a/math/Cent.cs b/math/Cent.cs new file mode 100644 index 0000000..6f799cc --- /dev/null +++ b/math/Cent.cs @@ -0,0 +1,365 @@ +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 ); + } + +} \ No newline at end of file diff --git a/ser/XmlSer.cs b/ser/XmlSer.cs index ba69168..70492dc 100644 --- a/ser/XmlSer.cs +++ b/ser/XmlSer.cs @@ -651,12 +651,25 @@ public class ObjectHandler : ITypeHandler // If POD-Attribute, write attribute if( memberMeta.IsPodAttribute ) { - writer.WriteAttributeString( memberMeta.XmlName, value.ToString() ); + try + { + writer.WriteAttributeString( memberMeta.XmlName, value.ToString() ); + } + catch( Exception ex ) + { + log.error( $"Writing Att {memberMeta.XmlName} = [{value}]" ); + } } else // Else, write element { - xml.WriteNode( writer, value, memberMeta.XmlName, memberMeta.Type, false ); - } + try + { + xml.WriteNode( writer, value, memberMeta.XmlName, memberMeta.Type, false ); + } + catch( Exception ex ) + { + log.error( $"Writing Node {memberMeta.XmlName} = [{value}]" ); + } } } } diff --git a/util/Guid.cs b/util/Guid.cs index 413ad06..4c47de7 100644 --- a/util/Guid.cs +++ b/util/Guid.cs @@ -3,10 +3,15 @@ using System; -namespace lib; +//namespace lib; -public static class Guid +public static class GuidExt { + extension(Guid g) + { + public string Log => $"{g.LastByte():X}"; + } + public static System.Guid GuidFromLongs( long a, long b ) { byte[] guidData = new byte[16]; @@ -59,6 +64,14 @@ public static class Guid return (a, b, c, d); } + + public static uint LastUInt( this System.Guid guid ) + { + var bytes = guid.ToByteArray(); + var d = BitConverter.ToUInt32( bytes, 12 ); + return d; + } + public static System.Guid GuidFromUInts( uint a, uint b, uint c, uint d ) { byte[] guidData = new byte[16]; @@ -78,4 +91,10 @@ public static class Guid var d = BitConverter.ToUInt32( bytes, 12 ); return (a, b, c, d); } + + public static byte LastByte( this System.Guid guid ) + { + var bytes = guid.ToByteArray(); + return bytes[7]; + } }