using System; using System.Collections.Generic; using System.Globalization; namespace Artemis.Core; /// /// Represents a number, either decimal or not, with arbitrary precision. /// /// Note: This struct is intended to be used by the node system when implementing your own . /// Usage outside that context is not recommended due to conversion overhead. /// /// public readonly struct Numeric : IComparable, IConvertible { private readonly float _value; #region Constructors /// /// Creates a new instance of from a /// public Numeric(float value) { _value = value; } /// /// Creates a new instance of from an /// public Numeric(int value) { _value = value; } /// /// Creates a new instance of from a /// public Numeric(double value) { _value = (float) value; } /// /// Creates a new instance of from a /// public Numeric(byte value) { _value = value; } /// /// Creates a new instance of from a /// public Numeric(long value) { _value = value; } /// /// Creates a new instance of from an /// public Numeric(object? pathValue) { _value = pathValue switch { float value => value, int value => value, double value => (float) value, byte value => value, long value => value, Numeric value => value, _ => ParseFloatOrDefault(pathValue?.ToString()) }; } private static float ParseFloatOrDefault(string? pathValue) { float.TryParse(pathValue, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out float parsedFloat); return parsedFloat; } #endregion #region Relational members /// public int CompareTo(Numeric other) { return _value.CompareTo(other._value); } #endregion #region Equality members /// /// Indicates whether this instance and a specified numeric are equal /// /// The numeric to compare with the current instance /// /// if this numeric and the provided are equal; otherwise, /// . /// public bool Equals(Numeric other) { return _value.Equals(other._value); } /// public override bool Equals(object? obj) { return obj is Numeric other && Equals(other); } /// public override int GetHashCode() { return _value.GetHashCode(); } #endregion #region Formatting members /// public override string ToString() { return _value.ToString(CultureInfo.InvariantCulture); } #endregion #region Operators #pragma warning disable 1591 public static implicit operator float(Numeric p) { return p._value; } public static implicit operator int(Numeric p) { return (int) MathF.Round(p._value, MidpointRounding.AwayFromZero); } public static implicit operator double(Numeric p) { return p._value; } public static implicit operator decimal(Numeric p) { return (decimal) p._value; } public static implicit operator byte(Numeric p) { return (byte) Math.Clamp(p._value, 0, 255); } public static implicit operator Numeric(double d) => new(d); public static implicit operator Numeric(float f) => new(f); public static implicit operator Numeric(int i) => new(i); public static implicit operator Numeric(byte b) => new(b); public static implicit operator long(Numeric p) { return (long) p._value; } public static bool operator >(Numeric a, Numeric b) { return a._value > b._value; } public static bool operator <(Numeric a, Numeric b) { return a._value < b._value; } public static bool operator ==(Numeric left, Numeric right) { return left.Equals(right); } public static bool operator !=(Numeric left, Numeric right) { return !(left == right); } public static bool operator <=(Numeric left, Numeric right) { return left.CompareTo(right) <= 0; } public static bool operator >=(Numeric left, Numeric right) { return left.CompareTo(right) >= 0; } public static Numeric operator +(Numeric a) { return new Numeric(+a._value); } public static Numeric operator -(Numeric a) { return new Numeric(-a._value); } public static Numeric operator ++(Numeric a) { return new Numeric(a._value + 1); } public static Numeric operator --(Numeric a) { return new Numeric(a._value - 1); } public static Numeric operator +(Numeric a, Numeric b) { return new Numeric(a._value + b._value); } public static Numeric operator -(Numeric a, Numeric b) { return new Numeric(a._value - b._value); } public static Numeric operator *(Numeric a, Numeric b) { return new Numeric(a._value * b._value); } public static Numeric operator %(Numeric a, Numeric b) { return new Numeric(a._value % b._value); } public static Numeric operator /(Numeric a, Numeric b) { if (b._value == 0) throw new DivideByZeroException(); return new Numeric(a._value / b._value); } #pragma warning restore 1591 #endregion /// /// Converts the string representation of a number into a numeric. A return value indicates whether the conversion /// succeeded or failed. /// /// A string representing a number to convert. /// /// When this method returns, contains numeric equivalent to the numeric value or symbol contained in /// , if the conversion succeeded, or zero if the conversion failed. /// /// if s was converted successfully; otherwise, . public static bool TryParse(string? s, out Numeric result) { bool parsed = float.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out float parsedFloat); if (!parsed) { result = new Numeric(0); return false; } result = new Numeric(parsedFloat); return true; } /// /// Returns a boolean indicating whether the provided type can be used as a . /// public static bool IsTypeCompatible(Type? type) { return type == typeof(Numeric) || type == typeof(float) || type == typeof(double) || type == typeof(int) || type == typeof(long) || type == typeof(byte); } #region Implementation of IConvertible /// public TypeCode GetTypeCode() { return _value.GetTypeCode(); } /// public bool ToBoolean(IFormatProvider? provider) { return Convert.ToBoolean(_value); } /// public byte ToByte(IFormatProvider? provider) { return (byte) Math.Clamp(_value, 0, 255); } /// public char ToChar(IFormatProvider? provider) { return Convert.ToChar(_value); } /// public DateTime ToDateTime(IFormatProvider? provider) { return Convert.ToDateTime(_value); } /// public decimal ToDecimal(IFormatProvider? provider) { return (decimal) _value; } /// public double ToDouble(IFormatProvider? provider) { return _value; } /// public short ToInt16(IFormatProvider? provider) { return (short) MathF.Round(_value, MidpointRounding.AwayFromZero); } /// public int ToInt32(IFormatProvider? provider) { return (int) MathF.Round(_value, MidpointRounding.AwayFromZero); } /// public long ToInt64(IFormatProvider? provider) { return (long) MathF.Round(_value, MidpointRounding.AwayFromZero); } /// public sbyte ToSByte(IFormatProvider? provider) { return (sbyte) Math.Clamp(_value, 0, 255); } /// public float ToSingle(IFormatProvider? provider) { return _value; } /// public string ToString(IFormatProvider? provider) { return _value.ToString(provider); } /// public object ToType(Type conversionType, IFormatProvider? provider) { return Convert.ChangeType(_value, conversionType); } /// public ushort ToUInt16(IFormatProvider? provider) { return (ushort) MathF.Round(_value, MidpointRounding.AwayFromZero); } /// public uint ToUInt32(IFormatProvider? provider) { return (uint) MathF.Round(_value, MidpointRounding.AwayFromZero); } /// public ulong ToUInt64(IFormatProvider? provider) { return (ulong) MathF.Round(_value, MidpointRounding.AwayFromZero); } #endregion } /// /// Provides alternatives for common number-type extensions /// public static class NumericExtensions { #region Extensions /// /// Sums the numerics in the provided collection /// /// The sum of all numerics in the collection /// public static Numeric Sum(this IEnumerable source) { if (source == null) throw new ArgumentNullException(nameof(source)); float sum = 0; foreach (float v in source) sum += v; return new Numeric(sum); } #endregion }