1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

421 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
namespace Artemis.Core;
/// <summary>
/// Represents a number, either decimal or not, with arbitrary precision.
/// <para>
/// Note: This struct is intended to be used by the node system when implementing your own <see cref="Node" />.
/// Usage outside that context is not recommended due to conversion overhead.
/// </para>
/// </summary>
public readonly struct Numeric : IComparable<Numeric>, IConvertible
{
private readonly float _value;
#region Constructors
/// <summary>
/// Creates a new instance of <see cref="Numeric" /> from a <see cref="float" />
/// </summary>
public Numeric(float value)
{
_value = value;
}
/// <summary>
/// Creates a new instance of <see cref="Numeric" /> from an <see cref="int" />
/// </summary>
public Numeric(int value)
{
_value = value;
}
/// <summary>
/// Creates a new instance of <see cref="Numeric" /> from a <see cref="double" />
/// </summary>
public Numeric(double value)
{
_value = (float) value;
}
/// <summary>
/// Creates a new instance of <see cref="Numeric" /> from a <see cref="byte" />
/// </summary>
public Numeric(byte value)
{
_value = value;
}
/// <summary>
/// Creates a new instance of <see cref="Numeric" /> from a <see cref="long" />
/// </summary>
public Numeric(long value)
{
_value = value;
}
/// <summary>
/// Creates a new instance of <see cref="Numeric" /> from an <see cref="object" />
/// </summary>
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
/// <inheritdoc />
public int CompareTo(Numeric other)
{
return _value.CompareTo(other._value);
}
#endregion
#region Equality members
/// <summary>
/// Indicates whether this instance and a specified numeric are equal
/// </summary>
/// <param name="other">The numeric to compare with the current instance</param>
/// <returns>
/// <see langword="true" /> if this numeric and the provided <paramref name="other" /> are equal; otherwise,
/// <see langword="false" />.
/// </returns>
public bool Equals(Numeric other)
{
return _value.Equals(other._value);
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
return obj is Numeric other && Equals(other);
}
/// <inheritdoc />
public override int GetHashCode()
{
return _value.GetHashCode();
}
#endregion
#region Formatting members
/// <inheritdoc />
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
/// <summary>
/// Converts the string representation of a number into a numeric. A return value indicates whether the conversion
/// succeeded or failed.
/// </summary>
/// <param name="s">A string representing a number to convert.</param>
/// <param name="result">
/// When this method returns, contains numeric equivalent to the numeric value or symbol contained in
/// <paramref name="s" />, if the conversion succeeded, or zero if the conversion failed.
/// </param>
/// <returns><see langword="true" /> if s was converted successfully; otherwise, <see langword="false" />.</returns>
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;
}
/// <summary>
/// Returns a boolean indicating whether the provided type can be used as a <see cref="Numeric" />.
/// </summary>
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
/// <inheritdoc />
public TypeCode GetTypeCode()
{
return _value.GetTypeCode();
}
/// <inheritdoc />
public bool ToBoolean(IFormatProvider? provider)
{
return Convert.ToBoolean(_value);
}
/// <inheritdoc />
public byte ToByte(IFormatProvider? provider)
{
return (byte) Math.Clamp(_value, 0, 255);
}
/// <inheritdoc />
public char ToChar(IFormatProvider? provider)
{
return Convert.ToChar(_value);
}
/// <inheritdoc />
public DateTime ToDateTime(IFormatProvider? provider)
{
return Convert.ToDateTime(_value);
}
/// <inheritdoc />
public decimal ToDecimal(IFormatProvider? provider)
{
return (decimal) _value;
}
/// <inheritdoc />
public double ToDouble(IFormatProvider? provider)
{
return _value;
}
/// <inheritdoc />
public short ToInt16(IFormatProvider? provider)
{
return (short) MathF.Round(_value, MidpointRounding.AwayFromZero);
}
/// <inheritdoc />
public int ToInt32(IFormatProvider? provider)
{
return (int) MathF.Round(_value, MidpointRounding.AwayFromZero);
}
/// <inheritdoc />
public long ToInt64(IFormatProvider? provider)
{
return (long) MathF.Round(_value, MidpointRounding.AwayFromZero);
}
/// <inheritdoc />
public sbyte ToSByte(IFormatProvider? provider)
{
return (sbyte) Math.Clamp(_value, 0, 255);
}
/// <inheritdoc />
public float ToSingle(IFormatProvider? provider)
{
return _value;
}
/// <inheritdoc />
public string ToString(IFormatProvider? provider)
{
return _value.ToString(provider);
}
/// <inheritdoc />
public object ToType(Type conversionType, IFormatProvider? provider)
{
return Convert.ChangeType(_value, conversionType);
}
/// <inheritdoc />
public ushort ToUInt16(IFormatProvider? provider)
{
return (ushort) MathF.Round(_value, MidpointRounding.AwayFromZero);
}
/// <inheritdoc />
public uint ToUInt32(IFormatProvider? provider)
{
return (uint) MathF.Round(_value, MidpointRounding.AwayFromZero);
}
/// <inheritdoc />
public ulong ToUInt64(IFormatProvider? provider)
{
return (ulong) MathF.Round(_value, MidpointRounding.AwayFromZero);
}
#endregion
}
/// <summary>
/// Provides <see cref="Numeric" /> alternatives for common number-type extensions
/// </summary>
public static class NumericExtensions
{
#region Extensions
/// <summary>
/// Sums the numerics in the provided collection
/// </summary>
/// <returns>The sum of all numerics in the collection</returns>
/// <exception cref="ArgumentNullException"></exception>
public static Numeric Sum(this IEnumerable<Numeric> source)
{
if (source == null) throw new ArgumentNullException(nameof(source));
float sum = 0;
foreach (float v in source)
sum += v;
return new Numeric(sum);
}
#endregion
}