mirror of
https://github.com/chylex/Query.git
synced 2025-07-20 00:04:32 +02:00
206 lines
6.7 KiB
C#
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));
|
|
}
|
|
}
|