1
0
mirror of https://github.com/chylex/Query.git synced 2025-07-20 00:04:32 +02:00
Query/Calculator/Math/Number.cs

206 lines
6.7 KiB
C#

using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Numerics;
using ExtendedNumerics;
namespace Calculator.Math;
[SuppressMessage("ReSharper", "MemberCanBeProtected.Global")]
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
public abstract record Number : IAdditionOperators<Number, Number, Number>,
ISubtractionOperators<Number, Number, Number>,
IMultiplyOperators<Number, Number, Number>,
IDivisionOperators<Number, Number, Number>,
IModulusOperators<Number, Number, Number>,
IUnaryPlusOperators<Number, Number>,
IUnaryNegationOperators<Number, Number>,
IAdditiveIdentity<Number, Number.Rational>,
IMultiplicativeIdentity<Number, Number.Rational>,
IComparable<Number> {
protected abstract decimal AsDecimal { get; }
public abstract Number WholePart { get; }
public abstract bool IsZero { get; }
public abstract Number Pow(Number exponent);
public abstract string ToString(IFormatProvider? formatProvider);
public virtual int CompareTo(Number? other) {
return AsDecimal.CompareTo(other?.AsDecimal);
}
public sealed override string ToString() {
return ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// Represents an integer number with arbitrary precision.
/// </summary>
public sealed record Rational(BigRational Value) : Number {
protected override decimal AsDecimal => (decimal) Value;
public override Rational WholePart => new (Value.WholePart);
public override bool IsZero => Value.IsZero;
public override Number Pow(Number exponent) {
if (exponent is Rational { Value: {} rationalExponent }) {
Fraction fractionExponent = Fraction.ReduceToProperFraction(rationalExponent.GetImproperFraction());
if (fractionExponent.Numerator >= 0 && fractionExponent.Denominator == 1) {
try {
return new Rational(BigRational.Pow(Value, fractionExponent.Numerator));
} catch (OverflowException) {}
}
if (fractionExponent.Numerator == 1 && fractionExponent.Denominator > 1) {
Number result = PowAsDecimal(exponent);
BigRational assumedPerfectPowerRoot = new BigRational(decimal.Floor(result.AsDecimal));
BigRational assumedPerfectPower = BigRational.Pow(assumedPerfectPowerRoot, fractionExponent.Denominator);
return assumedPerfectPower == Value ? assumedPerfectPowerRoot : result;
}
}
return PowAsDecimal(exponent);
Number PowAsDecimal(Number number) {
return new Decimal(AsDecimal).Pow(number);
}
}
public override string ToString(IFormatProvider? formatProvider) {
BigRational value = Fraction.ReduceToProperFraction(Value.GetImproperFraction());
string wholePartStr = value.WholePart.ToString(formatProvider);
if (value.FractionalPart.IsZero) {
return wholePartStr;
}
decimal fractionalPart = (decimal) value.FractionalPart;
if (fractionalPart == 0) {
return wholePartStr + ".0...";
}
string decimalPartStr = fractionalPart.ToString(formatProvider);
int decimalSeparatorIndex = decimalPartStr.TakeWhile(char.IsAsciiDigit).Count();
return wholePartStr + decimalPartStr[decimalSeparatorIndex..];
}
public override int CompareTo(Number? other) {
if (other is Rational rational) {
return Value.CompareTo(rational.Value);
}
else {
return base.CompareTo(other);
}
}
}
/// <summary>
/// Represents a decimal number with limited precision.
/// </summary>
public sealed record Decimal(decimal Value) : Number {
public Decimal(double value) : this((decimal) value) {}
protected override decimal AsDecimal => Value;
public override Decimal WholePart => new (decimal.Floor(Value));
public override bool IsZero => Value == 0;
public override Number Pow(Number exponent) {
double doubleValue = (double) Value;
double doubleExponent = (double) exponent.AsDecimal;
return new Decimal(System.Math.Pow(doubleValue, doubleExponent));
}
public override string ToString(IFormatProvider? formatProvider) {
return Value.ToString(formatProvider);
}
}
public static implicit operator Number(BigRational value) {
return new Rational(value);
}
public static implicit operator Number(int value) {
return new Rational(value);
}
public static implicit operator Number(long value) {
return new Rational(value);
}
public static Rational AdditiveIdentity => new (BigInteger.Zero);
public static Rational MultiplicativeIdentity => new (BigInteger.One);
public static Number Add(Number left, Number right) {
return left + right;
}
public static Number Subtract(Number left, Number right) {
return left - right;
}
public static Number Multiply(Number left, Number right) {
return left * right;
}
public static Number Divide(Number left, Number right) {
return left / right;
}
public static Number Remainder(Number left, Number right) {
return left % right;
}
public static Number Pow(Number left, Number right) {
return left.Pow(right);
}
public static Number operator +(Number value) {
return value;
}
public static Number operator -(Number value) {
return Operate(value, BigRational.Negate, decimal.Negate);
}
public static Number operator +(Number left, Number right) {
return Operate(left, right, BigRational.Add, decimal.Add);
}
public static Number operator -(Number left, Number right) {
return Operate(left, right, BigRational.Subtract, decimal.Subtract);
}
public static Number operator *(Number left, Number right) {
return Operate(left, right, BigRational.Multiply, decimal.Multiply);
}
public static Number operator /(Number left, Number right) {
return Operate(left, right, BigRational.Divide, decimal.Divide);
}
public static Number operator %(Number left, Number right) {
return Operate(left, right, BigRational.Mod, decimal.Remainder);
}
private static Number Operate(Number value, Func<BigRational, BigRational> rationalOperation, Func<decimal, decimal> decimalOperation) {
return value is Rational rational
? new Rational(rationalOperation(rational.Value))
: new Decimal(decimalOperation(value.AsDecimal));
}
private static Number Operate(Number left, Number right, Func<BigRational, BigRational, BigRational> rationalOperation, Func<decimal, decimal, decimal> decimalOperation) {
return left is Rational leftRational && right is Rational rightRational
? new Rational(rationalOperation(leftRational.Value, rightRational.Value))
: new Decimal(decimalOperation(left.AsDecimal, right.AsDecimal));
}
}