You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1526 lines
47 KiB
1526 lines
47 KiB
namespace BigFloatNumerics
|
|
{
|
|
using System;
|
|
using System.Globalization;
|
|
using Random = System.Random;
|
|
using System.Numerics;
|
|
using System.Text;
|
|
|
|
// I'm not sure if there's a "Yes, this is Unity" define symbol
|
|
// (#if UNITY doesn't seem to work). If you happen to know one - please create
|
|
// an issue here https://github.com/Razenpok/BreakInfinity.cs/issues.
|
|
#if UNITY_2017_1_OR_NEWER
|
|
using UnityEngine;
|
|
using Sirenix.Utilities;
|
|
#endif
|
|
|
|
#if UNITY_2017_1_OR_NEWER
|
|
[Serializable]
|
|
#endif
|
|
public struct BigFloat : IFormattable, IComparable, IComparable<BigFloat>, IEquatable<BigFloat>
|
|
{
|
|
#if false
|
|
public const double Tolerance = 1e-18;
|
|
|
|
//for example: if two exponents are more than 17 apart, consider adding them together pointless, just return the larger one
|
|
private const int MaxSignificantDigits = 17;
|
|
|
|
private const long ExpLimit = long.MaxValue;
|
|
|
|
//the largest exponent that can appear in a Double, though not all mantissas are valid here.
|
|
private const long DoubleExpMax = 308;
|
|
|
|
//The smallest exponent that can appear in a Double, though not all mantissas are valid here.
|
|
private const long DoubleExpMin = -324;
|
|
|
|
#if UNITY_2017_1_OR_NEWER
|
|
[SerializeField]
|
|
private double mantissa;
|
|
[SerializeField]
|
|
private long exponent;
|
|
#else
|
|
private double mantissa;
|
|
private long exponent;
|
|
#endif
|
|
|
|
// This constructor is used to prevent non-normalized values to be created via constructor.
|
|
// ReSharper disable once UnusedParameter.Local
|
|
private BigFloat(double mantissa, long exponent, PrivateConstructorArg _)
|
|
{
|
|
this.mantissa = mantissa;
|
|
this.exponent = exponent;
|
|
}
|
|
|
|
public BigFloat(double mantissa, long exponent)
|
|
{
|
|
this = Normalize(mantissa, exponent);
|
|
}
|
|
|
|
public BigFloat(BigFloat other)
|
|
{
|
|
mantissa = other.mantissa;
|
|
exponent = other.exponent;
|
|
}
|
|
|
|
public BigFloat(double value)
|
|
{
|
|
//SAFETY: Handle Infinity and NaN in a somewhat meaningful way.
|
|
if (double.IsNaN(value))
|
|
{
|
|
this = NaN;
|
|
}
|
|
else if (double.IsPositiveInfinity(value))
|
|
{
|
|
this = PositiveInfinity;
|
|
}
|
|
else if (double.IsNegativeInfinity(value))
|
|
{
|
|
this = NegativeInfinity;
|
|
}
|
|
else if (IsZero(value))
|
|
{
|
|
this = Zero;
|
|
}
|
|
else
|
|
{
|
|
this = Normalize(value, 0);
|
|
}
|
|
}
|
|
|
|
public static BigFloat Normalize(double mantissa, long exponent)
|
|
{
|
|
if (mantissa >= 1 && mantissa < 10 || !IsFinite(mantissa))
|
|
{
|
|
return FromMantissaExponentNoNormalize(mantissa, exponent);
|
|
}
|
|
if (IsZero(mantissa))
|
|
{
|
|
return Zero;
|
|
}
|
|
|
|
var tempExponent = (long)Math.Floor(Math.Log10(Math.Abs(mantissa)));
|
|
//SAFETY: handle 5e-324, -5e-324 separately
|
|
if (tempExponent == DoubleExpMin)
|
|
{
|
|
mantissa = mantissa * 10 / 1e-323;
|
|
}
|
|
else
|
|
{
|
|
mantissa = mantissa / PowersOf10.Lookup(tempExponent);
|
|
}
|
|
|
|
return FromMantissaExponentNoNormalize(mantissa, exponent + tempExponent);
|
|
}
|
|
|
|
public double Mantissa => mantissa;
|
|
|
|
public long Exponent => exponent;
|
|
|
|
public static BigFloat FromMantissaExponentNoNormalize(double mantissa, long exponent)
|
|
{
|
|
return new BigFloat(mantissa, exponent, new PrivateConstructorArg());
|
|
}
|
|
|
|
public static BigFloat Zero = FromMantissaExponentNoNormalize(0, 0);
|
|
|
|
public static BigFloat One = FromMantissaExponentNoNormalize(1, 0);
|
|
|
|
public static BigFloat NaN = FromMantissaExponentNoNormalize(double.NaN, long.MinValue);
|
|
|
|
public static bool IsNaN(BigFloat value)
|
|
{
|
|
return double.IsNaN(value.Mantissa);
|
|
}
|
|
|
|
public static BigFloat PositiveInfinity = FromMantissaExponentNoNormalize(double.PositiveInfinity, 0);
|
|
|
|
public static bool IsPositiveInfinity(BigFloat value)
|
|
{
|
|
return double.IsPositiveInfinity(value.Mantissa);
|
|
}
|
|
|
|
public static BigFloat NegativeInfinity = FromMantissaExponentNoNormalize(double.NegativeInfinity, 0);
|
|
|
|
public static bool IsNegativeInfinity(BigFloat value)
|
|
{
|
|
return double.IsNegativeInfinity(value.Mantissa);
|
|
}
|
|
|
|
public static bool IsInfinity(BigFloat value)
|
|
{
|
|
return double.IsInfinity(value.Mantissa);
|
|
}
|
|
|
|
public static BigFloat Parse(string value)
|
|
{
|
|
if (value.IndexOf('e') != -1)
|
|
{
|
|
var parts = value.Split('e');
|
|
var mantissa = double.Parse(parts[0], CultureInfo.InvariantCulture);
|
|
var exponent = long.Parse(parts[1], CultureInfo.InvariantCulture);
|
|
return Normalize(mantissa, exponent);
|
|
}
|
|
|
|
if (value == "NaN")
|
|
{
|
|
return NaN;
|
|
}
|
|
|
|
var result = new BigFloat(double.Parse(value, CultureInfo.InvariantCulture));
|
|
if (IsNaN(result))
|
|
{
|
|
throw new Exception("Invalid argument: " + value);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public double ToDouble()
|
|
{
|
|
if (IsNaN(this))
|
|
{
|
|
return double.NaN;
|
|
}
|
|
|
|
if (Exponent > DoubleExpMax)
|
|
{
|
|
return Mantissa > 0 ? double.PositiveInfinity : double.NegativeInfinity;
|
|
}
|
|
|
|
if (Exponent < DoubleExpMin)
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
//SAFETY: again, handle 5e-324, -5e-324 separately
|
|
if (Exponent == DoubleExpMin)
|
|
{
|
|
return Mantissa > 0 ? 5e-324 : -5e-324;
|
|
}
|
|
|
|
var result = Mantissa * PowersOf10.Lookup(Exponent);
|
|
if (!IsFinite(result) || Exponent < 0)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
var resultrounded = Math.Round(result);
|
|
if (Math.Abs(resultrounded - result) < 1e-10) return resultrounded;
|
|
return result;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return BigNumber.FormatBigDouble(this, null, null);
|
|
}
|
|
|
|
public string ToString(string format)
|
|
{
|
|
return BigNumber.FormatBigDouble(this, format, null);
|
|
}
|
|
|
|
public string ToString(string format, IFormatProvider formatProvider)
|
|
{
|
|
return BigNumber.FormatBigDouble(this, format, formatProvider);
|
|
}
|
|
|
|
public static BigFloat Abs(BigFloat value)
|
|
{
|
|
return FromMantissaExponentNoNormalize(Math.Abs(value.Mantissa), value.Exponent);
|
|
}
|
|
|
|
public static BigFloat Negate(BigFloat value)
|
|
{
|
|
return FromMantissaExponentNoNormalize(-value.Mantissa, value.Exponent);
|
|
}
|
|
|
|
public static int Sign(BigFloat value)
|
|
{
|
|
return Math.Sign(value.Mantissa);
|
|
}
|
|
|
|
public static BigFloat Round(BigFloat value)
|
|
{
|
|
if (IsNaN(value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
if (value.Exponent < -1)
|
|
{
|
|
return Zero;
|
|
}
|
|
|
|
if (value.Exponent < MaxSignificantDigits)
|
|
{
|
|
return new BigFloat(Math.Round(value.ToDouble()));
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
public static BigFloat Round(BigFloat value, MidpointRounding mode)
|
|
{
|
|
if (IsNaN(value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
if (value.Exponent < -1)
|
|
{
|
|
return Zero;
|
|
}
|
|
|
|
if (value.Exponent < MaxSignificantDigits)
|
|
{
|
|
return new BigFloat(Math.Round(value.ToDouble(), mode));
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
public static BigFloat Floor(BigFloat value)
|
|
{
|
|
if (IsNaN(value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
if (value.Exponent < -1)
|
|
{
|
|
return Math.Sign(value.Mantissa) >= 0 ? Zero : -One;
|
|
}
|
|
|
|
if (value.Exponent < MaxSignificantDigits)
|
|
{
|
|
return new BigFloat(Math.Floor(value.ToDouble()));
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
public static BigFloat Ceiling(BigFloat value)
|
|
{
|
|
if (IsNaN(value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
if (value.Exponent < -1)
|
|
{
|
|
return Math.Sign(value.Mantissa) > 0 ? One : Zero;
|
|
}
|
|
|
|
if (value.Exponent < MaxSignificantDigits)
|
|
{
|
|
return new BigFloat(Math.Ceiling(value.ToDouble()));
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
public static BigFloat Truncate(BigFloat value)
|
|
{
|
|
if (IsNaN(value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
if (value.Exponent < 0)
|
|
{
|
|
return Zero;
|
|
}
|
|
|
|
if (value.Exponent < MaxSignificantDigits)
|
|
{
|
|
return new BigFloat(Math.Truncate(value.ToDouble()));
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
public static BigFloat Add(BigFloat left, BigFloat right)
|
|
{
|
|
//figure out which is bigger, shrink the mantissa of the smaller by the difference in exponents, add mantissas, normalize and return
|
|
|
|
//TODO: Optimizations and simplification may be possible, see https://github.com/Patashu/break_infinity.js/issues/8
|
|
|
|
if (IsZero(left.Mantissa))
|
|
{
|
|
return right;
|
|
}
|
|
|
|
if (IsZero(right.Mantissa))
|
|
{
|
|
return left;
|
|
}
|
|
|
|
if (IsNaN(left) || IsNaN(right) || IsInfinity(left) || IsInfinity(right))
|
|
{
|
|
// Let Double handle these cases.
|
|
return left.Mantissa + right.Mantissa;
|
|
}
|
|
|
|
BigFloat bigger, smaller;
|
|
if (left.Exponent >= right.Exponent)
|
|
{
|
|
bigger = left;
|
|
smaller = right;
|
|
}
|
|
else
|
|
{
|
|
bigger = right;
|
|
smaller = left;
|
|
}
|
|
|
|
if (bigger.Exponent - smaller.Exponent > MaxSignificantDigits)
|
|
{
|
|
return bigger;
|
|
}
|
|
|
|
//have to do this because adding numbers that were once integers but scaled down is imprecise.
|
|
//Example: 299 + 18
|
|
return Normalize(
|
|
Math.Round(1e14 * bigger.Mantissa + 1e14 * smaller.Mantissa *
|
|
PowersOf10.Lookup(smaller.Exponent - bigger.Exponent)),
|
|
bigger.Exponent - 14);
|
|
}
|
|
|
|
public static BigFloat Subtract(BigFloat left, BigFloat right)
|
|
{
|
|
return left + -right;
|
|
}
|
|
|
|
public static BigFloat Multiply(BigFloat left, BigFloat right)
|
|
{
|
|
// 2e3 * 4e5 = (2 * 4)e(3 + 5)
|
|
return Normalize(left.Mantissa * right.Mantissa, left.Exponent + right.Exponent);
|
|
}
|
|
|
|
public static BigFloat Divide(BigFloat left, BigFloat right)
|
|
{
|
|
return left * Reciprocate(right);
|
|
}
|
|
|
|
public static BigFloat Reciprocate(BigFloat value)
|
|
{
|
|
return Normalize(1.0 / value.Mantissa, -value.Exponent);
|
|
}
|
|
|
|
public static implicit operator BigFloat(double value)
|
|
{
|
|
return new BigFloat(value);
|
|
}
|
|
|
|
public static implicit operator BigFloat(int value)
|
|
{
|
|
return new BigFloat((double)value);
|
|
}
|
|
|
|
public static implicit operator BigFloat(long value)
|
|
{
|
|
return new BigFloat((double)value);
|
|
}
|
|
|
|
public static implicit operator BigFloat(float value)
|
|
{
|
|
return new BigFloat((double)value);
|
|
}
|
|
|
|
public static implicit operator float(BigFloat value)
|
|
{
|
|
return (float)value.ToDouble();
|
|
}
|
|
|
|
public static implicit operator double(BigFloat value)
|
|
{
|
|
return value.ToDouble();
|
|
}
|
|
|
|
public static implicit operator BigFloat(BigInteger value)
|
|
{
|
|
return Parse(value.ToString());
|
|
}
|
|
|
|
public static implicit operator BigInteger(BigFloat value)
|
|
{
|
|
return BigInteger.Parse(value.ToString("F0"));
|
|
}
|
|
|
|
public static BigFloat operator -(BigFloat value)
|
|
{
|
|
return Negate(value);
|
|
}
|
|
|
|
public static BigFloat operator +(BigFloat left, BigFloat right)
|
|
{
|
|
return Add(left, right);
|
|
}
|
|
|
|
public static BigFloat operator -(BigFloat left, BigFloat right)
|
|
{
|
|
return Subtract(left, right);
|
|
}
|
|
|
|
public static BigFloat operator *(BigFloat left, BigFloat right)
|
|
{
|
|
return Multiply(left, right);
|
|
}
|
|
|
|
public static BigFloat operator /(BigFloat left, BigFloat right)
|
|
{
|
|
return Divide(left, right);
|
|
}
|
|
|
|
public static BigFloat operator ++(BigFloat value)
|
|
{
|
|
return value.Add(1);
|
|
}
|
|
|
|
public static BigFloat operator --(BigFloat value)
|
|
{
|
|
return value.Subtract(1);
|
|
}
|
|
|
|
public int CompareTo(object other)
|
|
{
|
|
if (other == null)
|
|
{
|
|
return 1;
|
|
}
|
|
if (other is BigFloat)
|
|
{
|
|
return CompareTo((BigFloat)other);
|
|
}
|
|
throw new ArgumentException("The parameter must be a BigDouble.");
|
|
}
|
|
|
|
public int CompareTo(BigFloat other)
|
|
{
|
|
if (
|
|
IsZero(Mantissa) || IsZero(other.Mantissa)
|
|
|| IsNaN(this) || IsNaN(other)
|
|
|| IsInfinity(this) || IsInfinity(other))
|
|
{
|
|
// Let Double handle these cases.
|
|
return Mantissa.CompareTo(other.Mantissa);
|
|
}
|
|
if (Mantissa > 0 && other.Mantissa < 0)
|
|
{
|
|
return 1;
|
|
}
|
|
if (Mantissa < 0 && other.Mantissa > 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
var exponentComparison = Exponent.CompareTo(other.Exponent);
|
|
return exponentComparison != 0
|
|
? (Mantissa > 0 ? exponentComparison : -exponentComparison)
|
|
: Mantissa.CompareTo(other.Mantissa);
|
|
}
|
|
|
|
public override bool Equals(object other)
|
|
{
|
|
return other is BigFloat && Equals((BigFloat)other);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
unchecked
|
|
{
|
|
return (Mantissa.GetHashCode() * 397) ^ Exponent.GetHashCode();
|
|
}
|
|
}
|
|
|
|
public bool Equals(BigFloat other)
|
|
{
|
|
return !IsNaN(this) && !IsNaN(other) && (AreSameInfinity(this, other)
|
|
|| Exponent == other.Exponent && AreEqual(Mantissa, other.Mantissa));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Relative comparison with tolerance being adjusted with greatest exponent.
|
|
/// <para>
|
|
/// For example, if you put in 1e-9, then any number closer to the larger number
|
|
/// than (larger number) * 1e-9 will be considered equal.
|
|
/// </para>
|
|
/// </summary>
|
|
public bool Equals(BigFloat other, double tolerance)
|
|
{
|
|
return !IsNaN(this) && !IsNaN(other) && (AreSameInfinity(this, other)
|
|
|| Abs(this - other) <= Max(Abs(this), Abs(other)) * tolerance);
|
|
}
|
|
|
|
private static bool AreSameInfinity(BigFloat first, BigFloat second)
|
|
{
|
|
return IsPositiveInfinity(first) && IsPositiveInfinity(second)
|
|
|| IsNegativeInfinity(first) && IsNegativeInfinity(second);
|
|
}
|
|
|
|
public static bool operator ==(BigFloat left, BigFloat right)
|
|
{
|
|
return left.Equals(right);
|
|
}
|
|
|
|
public static bool operator !=(BigFloat left, BigFloat right)
|
|
{
|
|
return !(left == right);
|
|
}
|
|
|
|
public static bool operator <(BigFloat a, BigFloat b)
|
|
{
|
|
if (IsNaN(a) || IsNaN(b))
|
|
{
|
|
return false;
|
|
}
|
|
if (IsZero(a.Mantissa)) return b.Mantissa > 0;
|
|
if (IsZero(b.Mantissa)) return a.Mantissa < 0;
|
|
if (a.Exponent == b.Exponent) return a.Mantissa < b.Mantissa;
|
|
if (a.Mantissa > 0) return b.Mantissa > 0 && a.Exponent < b.Exponent;
|
|
return b.Mantissa > 0 || a.Exponent > b.Exponent;
|
|
}
|
|
|
|
public static bool operator <=(BigFloat a, BigFloat b)
|
|
{
|
|
if (IsNaN(a) || IsNaN(b))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return !(a > b);
|
|
}
|
|
|
|
public static bool operator >(BigFloat a, BigFloat b)
|
|
{
|
|
if (IsNaN(a) || IsNaN(b))
|
|
{
|
|
return false;
|
|
}
|
|
if (IsZero(a.Mantissa)) return b.Mantissa < 0;
|
|
if (IsZero(b.Mantissa)) return a.Mantissa > 0;
|
|
if (a.Exponent == b.Exponent) return a.Mantissa > b.Mantissa;
|
|
if (a.Mantissa > 0) return b.Mantissa < 0 || a.Exponent > b.Exponent;
|
|
return b.Mantissa < 0 && a.Exponent < b.Exponent;
|
|
}
|
|
|
|
public static bool operator >=(BigFloat a, BigFloat b)
|
|
{
|
|
if (IsNaN(a) || IsNaN(b))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return !(a < b);
|
|
}
|
|
|
|
public static BigFloat Max(BigFloat left, BigFloat right)
|
|
{
|
|
if (IsNaN(left) || IsNaN(right))
|
|
{
|
|
return NaN;
|
|
}
|
|
return left > right ? left : right;
|
|
}
|
|
|
|
public static BigFloat Min(BigFloat left, BigFloat right)
|
|
{
|
|
if (IsNaN(left) || IsNaN(right))
|
|
{
|
|
return NaN;
|
|
}
|
|
return left > right ? right : left;
|
|
}
|
|
|
|
public static double AbsLog10(BigFloat value)
|
|
{
|
|
return value.Exponent + Math.Log10(Math.Abs(value.Mantissa));
|
|
}
|
|
|
|
public static double Log10(BigFloat value)
|
|
{
|
|
return value.Exponent + Math.Log10(value.Mantissa);
|
|
}
|
|
|
|
public static double Log(BigFloat value, BigFloat @base)
|
|
{
|
|
return Log(value, @base.ToDouble());
|
|
}
|
|
|
|
public static double Log(BigFloat value, double @base)
|
|
{
|
|
if (IsZero(@base))
|
|
{
|
|
return double.NaN;
|
|
}
|
|
|
|
//UN-SAFETY: Most incremental game cases are log(number := 1 or greater, base := 2 or greater). We assume this to be true and thus only need to return a number, not a BigDouble, and don't do any other kind of error checking.
|
|
return 2.30258509299404568402 / Math.Log(@base) * Log10(value);
|
|
}
|
|
|
|
public static double Log2(BigFloat value)
|
|
{
|
|
return 3.32192809488736234787 * Log10(value);
|
|
}
|
|
|
|
public static double Ln(BigFloat value)
|
|
{
|
|
return 2.30258509299404568402 * Log10(value);
|
|
}
|
|
|
|
public static BigFloat Pow10(double power)
|
|
{
|
|
return IsInteger(power)
|
|
? Pow10((long)power)
|
|
: Normalize(Math.Pow(10, power % 1), (long)Math.Truncate(power));
|
|
}
|
|
|
|
public static BigFloat Pow10(long power)
|
|
{
|
|
return FromMantissaExponentNoNormalize(1, power);
|
|
}
|
|
|
|
public static BigFloat Pow(BigFloat value, BigFloat power)
|
|
{
|
|
return Pow(value, power.ToDouble());
|
|
}
|
|
|
|
public static BigFloat Pow(BigFloat value, long power)
|
|
{
|
|
if (Is10(value))
|
|
{
|
|
return Pow10(power);
|
|
}
|
|
|
|
var mantissa = Math.Pow(value.Mantissa, power);
|
|
if (double.IsInfinity(mantissa))
|
|
{
|
|
// TODO: This is rather dumb, but works anyway
|
|
// Power is too big for our mantissa, so we do multiple Pow with smaller powers.
|
|
return Pow(Pow(value, 2), (double)power / 2);
|
|
}
|
|
|
|
return Normalize(mantissa, value.Exponent * power);
|
|
}
|
|
|
|
public static BigFloat Pow(BigFloat value, double power)
|
|
{
|
|
// TODO: power can be greater that long.MaxValue, which can bring troubles in fast track
|
|
var powerIsInteger = IsInteger(power);
|
|
if (value < 0 && !powerIsInteger)
|
|
{
|
|
return NaN;
|
|
}
|
|
return Is10(value) && powerIsInteger ? Pow10(power) : PowInternal(value, power);
|
|
}
|
|
|
|
private static bool Is10(BigFloat value)
|
|
{
|
|
return value.Exponent == 1 && value.Mantissa - 1 < double.Epsilon;
|
|
}
|
|
|
|
private static BigFloat PowInternal(BigFloat value, double other)
|
|
{
|
|
//UN-SAFETY: Accuracy not guaranteed beyond ~9~11 decimal places.
|
|
|
|
//TODO: Fast track seems about neutral for performance. It might become faster if an integer pow is implemented, or it might not be worth doing (see https://github.com/Patashu/break_infinity.js/issues/4 )
|
|
|
|
//Fast track: If (this.exponent*value) is an integer and mantissa^value fits in a Number, we can do a very fast method.
|
|
var temp = value.Exponent * other;
|
|
double newMantissa;
|
|
if (IsInteger(temp) && IsFinite(temp) && Math.Abs(temp) < ExpLimit)
|
|
{
|
|
newMantissa = Math.Pow(value.Mantissa, other);
|
|
if (IsFinite(newMantissa))
|
|
{
|
|
return Normalize(newMantissa, (long)temp);
|
|
}
|
|
}
|
|
|
|
//Same speed and usually more accurate. (An arbitrary-precision version of this calculation is used in break_break_infinity.js, sacrificing performance for utter accuracy.)
|
|
|
|
var newexponent = Math.Truncate(temp);
|
|
var residue = temp - newexponent;
|
|
newMantissa = Math.Pow(10, other * Math.Log10(value.Mantissa) + residue);
|
|
if (IsFinite(newMantissa))
|
|
{
|
|
return Normalize(newMantissa, (long)newexponent);
|
|
}
|
|
|
|
//UN-SAFETY: This should return NaN when mantissa is negative and value is noninteger.
|
|
var result = Pow10(other * AbsLog10(value)); //this is 2x faster and gives same values AFAIK
|
|
if (Sign(value) == -1 && AreEqual(other % 2, 1))
|
|
{
|
|
return -result;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public static BigFloat Factorial(BigFloat value)
|
|
{
|
|
//Using Stirling's Approximation. https://en.wikipedia.org/wiki/Stirling%27s_approximation#Versions_suitable_for_calculators
|
|
|
|
var n = value.ToDouble() + 1;
|
|
|
|
return Pow(n / 2.71828182845904523536 * Math.Sqrt(n * Math.Sinh(1 / n) + 1 / (810 * Math.Pow(n, 6))), n) * Math.Sqrt(2 * 3.141592653589793238462 / n);
|
|
}
|
|
|
|
public static BigFloat Exp(BigFloat value)
|
|
{
|
|
return Pow(2.71828182845904523536, value);
|
|
}
|
|
|
|
public static BigFloat Sqrt(BigFloat value)
|
|
{
|
|
if (value.Mantissa < 0)
|
|
{
|
|
return new BigFloat(double.NaN);
|
|
}
|
|
|
|
if (value.Exponent % 2 != 0)
|
|
{
|
|
// mod of a negative number is negative, so != means '1 or -1'
|
|
return Normalize(Math.Sqrt(value.Mantissa) * 3.16227766016838, (long)Math.Floor(value.Exponent / 2.0));
|
|
}
|
|
|
|
return Normalize(Math.Sqrt(value.Mantissa), (long)Math.Floor(value.Exponent / 2.0));
|
|
}
|
|
|
|
public static BigFloat Cbrt(BigFloat value)
|
|
{
|
|
var sign = 1;
|
|
var mantissa = value.Mantissa;
|
|
if (mantissa < 0)
|
|
{
|
|
sign = -1;
|
|
mantissa = -mantissa;
|
|
}
|
|
|
|
var newmantissa = sign * Math.Pow(mantissa, 1 / 3.0);
|
|
|
|
var mod = value.Exponent % 3;
|
|
if (mod == 1 || mod == -1)
|
|
{
|
|
return Normalize(newmantissa * 2.1544346900318837, (long)Math.Floor(value.Exponent / 3.0));
|
|
}
|
|
|
|
if (mod != 0)
|
|
{
|
|
return Normalize(newmantissa * 4.6415888336127789, (long)Math.Floor(value.Exponent / 3.0));
|
|
} //mod != 0 at this point means 'mod == 2 || mod == -2'
|
|
|
|
return Normalize(newmantissa, (long)Math.Floor(value.Exponent / 3.0));
|
|
}
|
|
|
|
public static BigFloat Sinh(BigFloat value)
|
|
{
|
|
return (Exp(value) - Exp(-value)) / 2;
|
|
}
|
|
|
|
public static BigFloat Cosh(BigFloat value)
|
|
{
|
|
return (Exp(value) + Exp(-value)) / 2;
|
|
}
|
|
|
|
public static BigFloat Tanh(BigFloat value)
|
|
{
|
|
return Sinh(value) / Cosh(value);
|
|
}
|
|
|
|
public static double Asinh(BigFloat value)
|
|
{
|
|
return Ln(value + Sqrt(Pow(value, 2) + 1));
|
|
}
|
|
|
|
public static double Acosh(BigFloat value)
|
|
{
|
|
return Ln(value + Sqrt(Pow(value, 2) - 1));
|
|
}
|
|
|
|
public static double Atanh(BigFloat value)
|
|
{
|
|
if (Abs(value) >= 1) return double.NaN;
|
|
return Ln((value + 1) / (One - value)) / 2;
|
|
}
|
|
|
|
private static bool IsZero(double value)
|
|
{
|
|
return Math.Abs(value) < double.Epsilon;
|
|
}
|
|
|
|
private static bool AreEqual(double first, double second)
|
|
{
|
|
return Math.Abs(first - second) < Tolerance;
|
|
}
|
|
|
|
private static bool IsInteger(double value)
|
|
{
|
|
return IsZero(Math.Abs(value % 1));
|
|
}
|
|
|
|
private static bool IsFinite(double value)
|
|
{
|
|
return !double.IsNaN(value) && !double.IsInfinity(value);
|
|
}
|
|
|
|
public static BigFloat Clamp(BigFloat v0, BigFloat min, BigFloat max)
|
|
{
|
|
return v0 < min ? min : v0 > max ? max : v0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The BigNumber class implements methods for formatting and parsing big numeric values.
|
|
/// </summary>
|
|
private static class BigNumber
|
|
{
|
|
public static string FormatBigDouble(BigFloat value, string format, IFormatProvider formatProvider)
|
|
{
|
|
if (IsNaN(value)) return "NaN";
|
|
if (value.Exponent >= ExpLimit)
|
|
{
|
|
return value.Mantissa > 0 ? "Infinity" : "-Infinity";
|
|
}
|
|
|
|
int formatDigits;
|
|
var formatSpecifier = ParseFormatSpecifier(format, out formatDigits);
|
|
switch (formatSpecifier)
|
|
{
|
|
case 'R':
|
|
case 'G':
|
|
return FormatGeneral(value, formatDigits);
|
|
case 'E':
|
|
return FormatExponential(value, formatDigits);
|
|
case 'F':
|
|
return FormatFixed(value, formatDigits);
|
|
}
|
|
throw new FormatException($"Unknown string format '{formatSpecifier}'");
|
|
}
|
|
|
|
private static char ParseFormatSpecifier(string format, out int digits)
|
|
{
|
|
const char customFormat = (char)0;
|
|
digits = -1;
|
|
if (string.IsNullOrEmpty(format))
|
|
{
|
|
return 'R';
|
|
}
|
|
|
|
var i = 0;
|
|
var ch = format[i];
|
|
if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z'))
|
|
{
|
|
return customFormat;
|
|
}
|
|
|
|
i++;
|
|
var n = -1;
|
|
|
|
if (i < format.Length && format[i] >= '0' && format[i] <= '9')
|
|
{
|
|
n = format[i++] - '0';
|
|
while (i < format.Length && format[i] >= '0' && format[i] <= '9')
|
|
{
|
|
n = n * 10 + (format[i++] - '0');
|
|
if (n >= 10)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i < format.Length && format[i] != '\0')
|
|
{
|
|
return customFormat;
|
|
}
|
|
|
|
digits = n;
|
|
return ch;
|
|
}
|
|
|
|
private static string FormatGeneral(BigFloat value, int places)
|
|
{
|
|
if (value.Exponent <= -ExpLimit || IsZero(value.Mantissa))
|
|
{
|
|
return "0";
|
|
}
|
|
|
|
var format = places > 0 ? $"G{places}" : "G";
|
|
if (value.Exponent < 21 && value.Exponent > -7)
|
|
{
|
|
return value.ToDouble().ToString(format, CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
return value.Mantissa.ToString(format, CultureInfo.InvariantCulture)
|
|
+ "E" + (value.Exponent >= 0 ? "+" : "")
|
|
+ value.Exponent.ToString(CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
private static string ToFixed(double value, int places)
|
|
{
|
|
return value.ToString($"F{places}", CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
private static string FormatExponential(BigFloat value, int places)
|
|
{
|
|
if (value.Exponent <= -ExpLimit || IsZero(value.Mantissa))
|
|
{
|
|
return "0" + (places > 0 ? ".".PadRight(places + 1, '0') : "") + "E+0";
|
|
}
|
|
|
|
var len = (places >= 0 ? places : MaxSignificantDigits) + 1;
|
|
var numDigits = (int)Math.Ceiling(Math.Log10(Math.Abs(value.Mantissa)));
|
|
var rounded = Math.Round(value.Mantissa * Math.Pow(10, len - numDigits)) * Math.Pow(10, numDigits - len);
|
|
|
|
var mantissa = ToFixed(rounded, Math.Max(len - numDigits, 0));
|
|
if (mantissa != "0" && places < 0)
|
|
{
|
|
mantissa = mantissa.TrimEnd('0', '.');
|
|
}
|
|
return mantissa + "E" + (value.Exponent >= 0 ? "+" : "")
|
|
+ value.Exponent;
|
|
}
|
|
|
|
private static string FormatFixed(BigFloat value, int places)
|
|
{
|
|
if (places < 0)
|
|
{
|
|
places = MaxSignificantDigits;
|
|
}
|
|
if (value.Exponent <= -ExpLimit || IsZero(value.Mantissa))
|
|
{
|
|
return "0" + (places > 0 ? ".".PadRight(places + 1, '0') : "");
|
|
}
|
|
|
|
// two cases:
|
|
// 1) exponent is 17 or greater: just print out mantissa with the appropriate number of zeroes after it
|
|
// 2) exponent is 16 or less: use basic toFixed
|
|
|
|
if (value.Exponent >= MaxSignificantDigits)
|
|
{
|
|
// TODO: StringBuilder-optimizable
|
|
return value.Mantissa
|
|
.ToString(CultureInfo.InvariantCulture)
|
|
.Replace(".", "")
|
|
.PadRight((int)value.Exponent + 1, '0')
|
|
+ (places > 0 ? ".".PadRight(places + 1, '0') : "");
|
|
}
|
|
return ToFixed(value.ToDouble(), places);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// We need this lookup table because Math.pow(10, exponent) when exponent's absolute value
|
|
/// is large is slightly inaccurate. you can fix it with the power of math... or just make
|
|
/// a lookup table. Faster AND simpler.
|
|
/// </summary>
|
|
private static class PowersOf10
|
|
{
|
|
private static double[] Powers { get; } = new double[DoubleExpMax - DoubleExpMin];
|
|
|
|
private const long IndexOf0 = -DoubleExpMin - 1;
|
|
|
|
static PowersOf10()
|
|
{
|
|
var index = 0;
|
|
for (var i = 0; i < Powers.Length; i++)
|
|
{
|
|
Powers[index++] = double.Parse("1e" + (i - IndexOf0), CultureInfo.InvariantCulture);
|
|
}
|
|
}
|
|
|
|
public static double Lookup(long power)
|
|
{
|
|
return Powers[IndexOf0 + power];
|
|
}
|
|
}
|
|
|
|
private struct PrivateConstructorArg { }
|
|
}
|
|
|
|
public static class BigMath
|
|
{
|
|
private static readonly Random Random = new Random();
|
|
|
|
/// <summary>
|
|
/// This doesn't follow any kind of sane random distribution, so use this for testing purposes only.
|
|
/// <para>5% of the time, mantissa is 0.</para>
|
|
/// <para>10% of the time, mantissa is round.</para>
|
|
/// </summary>
|
|
public static BigFloat RandomBigDouble(double absMaxExponent)
|
|
{
|
|
if (Random.NextDouble() * 20 < 1)
|
|
{
|
|
return BigFloat.Normalize(0, 0);
|
|
}
|
|
|
|
var mantissa = Random.NextDouble() * 10;
|
|
if (Random.NextDouble() * 10 < 1)
|
|
{
|
|
mantissa = Math.Round(mantissa);
|
|
}
|
|
|
|
mantissa *= Math.Sign(Random.NextDouble() * 2 - 1);
|
|
var exponent = (long)(Math.Floor(Random.NextDouble() * absMaxExponent * 2) - absMaxExponent);
|
|
return BigFloat.Normalize(mantissa, exponent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// If you're willing to spend 'resourcesAvailable' and want to buy something with
|
|
/// exponentially increasing cost each purchase (start at priceStart, multiply by priceRatio,
|
|
/// already own currentOwned), how much of it can you buy?
|
|
/// <para>
|
|
/// Adapted from Trimps source code.
|
|
/// </para>
|
|
/// </summary>
|
|
public static BigFloat AffordGeometricSeries(BigFloat resourcesAvailable, BigFloat priceStart,
|
|
BigFloat priceRatio, BigFloat currentOwned)
|
|
{
|
|
var actualStart = priceStart * BigFloat.Pow(priceRatio, currentOwned);
|
|
|
|
//return Math.floor(log10(((resourcesAvailable / (priceStart * Math.pow(priceRatio, currentOwned))) * (priceRatio - 1)) + 1) / log10(priceRatio));
|
|
|
|
return BigFloat.Floor(BigFloat.Log10(resourcesAvailable / actualStart * (priceRatio - 1) + 1) / BigFloat.Log10(priceRatio));
|
|
}
|
|
|
|
/// <summary>
|
|
/// How much resource would it cost to buy (numItems) items if you already have currentOwned,
|
|
/// the initial price is priceStart and it multiplies by priceRatio each purchase?
|
|
/// </summary>
|
|
public static BigFloat SumGeometricSeries(BigFloat numItems, BigFloat priceStart, BigFloat priceRatio,
|
|
BigFloat currentOwned)
|
|
{
|
|
var actualStart = priceStart * BigFloat.Pow(priceRatio, currentOwned);
|
|
|
|
return actualStart * (1 - BigFloat.Pow(priceRatio, numItems)) / (1 - priceRatio);
|
|
}
|
|
|
|
/// <summary>
|
|
/// If you're willing to spend 'resourcesAvailable' and want to buy something with
|
|
/// additively increasing cost each purchase (start at priceStart, add by priceAdd,
|
|
/// already own currentOwned), how much of it can you buy?
|
|
/// </summary>
|
|
public static BigFloat AffordArithmeticSeries(BigFloat resourcesAvailable, BigFloat priceStart,
|
|
BigFloat priceAdd, BigFloat currentOwned)
|
|
{
|
|
var actualStart = priceStart + currentOwned * priceAdd;
|
|
|
|
//n = (-(a-d/2) + sqrt((a-d/2)^2+2dS))/d
|
|
//where a is actualStart, d is priceAdd and S is resourcesAvailable
|
|
//then floor it and you're done!
|
|
|
|
var b = actualStart - priceAdd / 2;
|
|
var b2 = BigFloat.Pow(b, 2);
|
|
|
|
return BigFloat.Floor(
|
|
(BigFloat.Sqrt(b2 + priceAdd * resourcesAvailable * 2) - b) / priceAdd
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// How much resource would it cost to buy (numItems) items if you already have currentOwned,
|
|
/// the initial price is priceStart and it adds priceAdd each purchase?
|
|
/// <para>
|
|
/// Adapted from http://www.mathwords.com/a/arithmetic_series.htm
|
|
/// </para>
|
|
/// </summary>
|
|
public static BigFloat SumArithmeticSeries(BigFloat numItems, BigFloat priceStart, BigFloat priceAdd,
|
|
BigFloat currentOwned)
|
|
{
|
|
var actualStart = priceStart + currentOwned * priceAdd;
|
|
|
|
//(n/2)*(2*a+(n-1)*d)
|
|
|
|
return numItems / 2 * (2 * actualStart + (numItems - 1) * priceAdd);
|
|
}
|
|
|
|
/// <summary>
|
|
/// When comparing two purchases that cost (resource) and increase your resource/sec by (delta_RpS),
|
|
/// the lowest efficiency score is the better one to purchase.
|
|
/// <para>
|
|
/// From Frozen Cookies: http://cookieclicker.wikia.com/wiki/Frozen_Cookies_(JavaScript_Add-on)#Efficiency.3F_What.27s_that.3F
|
|
/// </para>
|
|
/// </summary>
|
|
public static BigFloat EfficiencyOfPurchase(BigFloat cost, BigFloat currentRpS, BigFloat deltaRpS)
|
|
{
|
|
return cost / currentRpS + cost / deltaRpS;
|
|
}
|
|
}
|
|
|
|
public static class BigDoubleExtensions
|
|
{
|
|
public static BigFloat Abs(this BigFloat value)
|
|
{
|
|
return BigFloat.Abs(value);
|
|
}
|
|
|
|
public static BigFloat Negate(this BigFloat value)
|
|
{
|
|
return BigFloat.Negate(value);
|
|
}
|
|
|
|
public static int Sign(this BigFloat value)
|
|
{
|
|
return BigFloat.Sign(value);
|
|
}
|
|
|
|
public static BigFloat Round(this BigFloat value)
|
|
{
|
|
return BigFloat.Round(value);
|
|
}
|
|
|
|
public static BigFloat Floor(this BigFloat value)
|
|
{
|
|
return BigFloat.Floor(value);
|
|
}
|
|
|
|
public static BigFloat Ceiling(this BigFloat value)
|
|
{
|
|
return BigFloat.Ceiling(value);
|
|
}
|
|
|
|
public static BigFloat Truncate(this BigFloat value)
|
|
{
|
|
return BigFloat.Truncate(value);
|
|
}
|
|
|
|
public static BigFloat Add(this BigFloat value, BigFloat other)
|
|
{
|
|
return BigFloat.Add(value, other);
|
|
}
|
|
|
|
public static BigFloat Subtract(this BigFloat value, BigFloat other)
|
|
{
|
|
return BigFloat.Subtract(value, other);
|
|
}
|
|
|
|
public static BigFloat Multiply(this BigFloat value, BigFloat other)
|
|
{
|
|
return BigFloat.Multiply(value, other);
|
|
}
|
|
|
|
public static BigFloat Divide(this BigFloat value, BigFloat other)
|
|
{
|
|
return BigFloat.Divide(value, other);
|
|
}
|
|
|
|
public static BigFloat Reciprocate(this BigFloat value)
|
|
{
|
|
return BigFloat.Reciprocate(value);
|
|
}
|
|
|
|
public static BigFloat Max(this BigFloat value, BigFloat other)
|
|
{
|
|
return BigFloat.Max(value, other);
|
|
}
|
|
|
|
public static BigFloat Min(this BigFloat value, BigFloat other)
|
|
{
|
|
return BigFloat.Min(value, other);
|
|
}
|
|
|
|
public static double AbsLog10(this BigFloat value)
|
|
{
|
|
return BigFloat.AbsLog10(value);
|
|
}
|
|
|
|
public static double Log10(this BigFloat value)
|
|
{
|
|
return BigFloat.Log10(value);
|
|
}
|
|
|
|
public static double Log(BigFloat value, BigFloat @base)
|
|
{
|
|
return BigFloat.Log(value, @base);
|
|
}
|
|
|
|
public static double Log(this BigFloat value, double @base)
|
|
{
|
|
return BigFloat.Log(value, @base);
|
|
}
|
|
|
|
public static double Log2(this BigFloat value)
|
|
{
|
|
return BigFloat.Log2(value);
|
|
}
|
|
|
|
public static double Ln(this BigFloat value)
|
|
{
|
|
return BigFloat.Ln(value);
|
|
}
|
|
|
|
public static BigFloat Exp(this BigFloat value)
|
|
{
|
|
return BigFloat.Exp(value);
|
|
}
|
|
|
|
public static BigFloat Sinh(this BigFloat value)
|
|
{
|
|
return BigFloat.Sinh(value);
|
|
}
|
|
|
|
public static BigFloat Cosh(this BigFloat value)
|
|
{
|
|
return BigFloat.Cosh(value);
|
|
}
|
|
|
|
public static BigFloat Tanh(this BigFloat value)
|
|
{
|
|
return BigFloat.Tanh(value);
|
|
}
|
|
|
|
public static double Asinh(this BigFloat value)
|
|
{
|
|
return BigFloat.Asinh(value);
|
|
}
|
|
|
|
public static double Acosh(this BigFloat value)
|
|
{
|
|
return BigFloat.Acosh(value);
|
|
}
|
|
|
|
public static double Atanh(this BigFloat value)
|
|
{
|
|
return BigFloat.Atanh(value);
|
|
}
|
|
|
|
public static BigFloat Pow(this BigFloat value, BigFloat power)
|
|
{
|
|
return BigFloat.Pow(value, power);
|
|
}
|
|
|
|
public static BigFloat Pow(this BigFloat value, long power)
|
|
{
|
|
return BigFloat.Pow(value, power);
|
|
}
|
|
|
|
public static BigFloat Pow(this BigFloat value, double power)
|
|
{
|
|
return BigFloat.Pow(value, power);
|
|
}
|
|
|
|
public static BigFloat Factorial(this BigFloat value)
|
|
{
|
|
return BigFloat.Factorial(value);
|
|
}
|
|
|
|
public static BigFloat Sqrt(this BigFloat value)
|
|
{
|
|
return BigFloat.Sqrt(value);
|
|
}
|
|
|
|
public static BigFloat Cbrt(this BigFloat value)
|
|
{
|
|
return BigFloat.Cbrt(value);
|
|
}
|
|
|
|
public static BigFloat Sqr(this BigFloat value)
|
|
{
|
|
return BigFloat.Pow(value, 2);
|
|
}
|
|
|
|
#if EXTENSIONS_EASTER_EGGS
|
|
/// <summary>
|
|
/// Joke function from Realm Grinder.
|
|
/// </summary>
|
|
public static BigDouble AscensionPenalty(this BigDouble value, double ascensions)
|
|
{
|
|
return Math.Abs(ascensions) < double.Epsilon ? value : BigDouble.Pow(value, Math.Pow(10, -ascensions));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Joke function from Cookie Clicker. It's an 'egg'.
|
|
/// </summary>
|
|
public static BigDouble Egg(this BigDouble value)
|
|
{
|
|
return value + 9;
|
|
}
|
|
#endif
|
|
#else
|
|
static readonly int _decimalZeros = 6;
|
|
static readonly int _decimalSize = (int)Math.Pow(10, _decimalZeros);
|
|
|
|
BigInteger v;
|
|
|
|
public BigFloat(string value)
|
|
{
|
|
v = Parse(value);
|
|
}
|
|
|
|
public BigFloat(int value)
|
|
{
|
|
v = value;
|
|
v *= _decimalSize;
|
|
}
|
|
|
|
public BigFloat(float value)
|
|
{
|
|
v = (BigInteger)value;
|
|
v *= _decimalSize;
|
|
v += (BigInteger)(((decimal)value % 1) * _decimalSize);
|
|
}
|
|
|
|
public BigFloat(double value)
|
|
{
|
|
v = (BigInteger)value;
|
|
v *= _decimalSize;
|
|
v += (BigInteger)(((decimal)value % 1) * _decimalSize);
|
|
}
|
|
|
|
public BigFloat(BigInteger value)
|
|
{
|
|
v = value * _decimalSize;
|
|
}
|
|
|
|
public static BigFloat operator +(BigFloat a, BigFloat b)
|
|
{
|
|
return new BigFloat()
|
|
{
|
|
v = a.v + b.v
|
|
};
|
|
}
|
|
|
|
public static BigFloat operator -(BigFloat a) => new BigFloat() { v = -a.v };
|
|
public static BigFloat operator -(BigFloat a, BigFloat b) => a + (-b);
|
|
public static BigFloat operator *(BigFloat a, BigFloat b)
|
|
{
|
|
return new BigFloat()
|
|
{
|
|
v = (a.v * b.v) / _decimalSize
|
|
};
|
|
}
|
|
|
|
public static BigFloat operator /(BigFloat a, BigFloat b)
|
|
{
|
|
return new BigFloat()
|
|
{
|
|
v = (a.v * _decimalSize) / b.v
|
|
};
|
|
}
|
|
|
|
public static BigFloat operator %(BigFloat a, BigFloat b)
|
|
{
|
|
return new BigFloat()
|
|
{
|
|
v = a.v % b.v
|
|
};
|
|
}
|
|
|
|
public static BigFloat operator ++(BigFloat a) => a + 1;
|
|
public static BigFloat operator --(BigFloat a) => a - 1;
|
|
|
|
public static bool operator ==(BigFloat a, BigFloat b) => a.v == b.v;
|
|
public static bool operator !=(BigFloat a, BigFloat b) => a.v != b.v;
|
|
|
|
public static bool operator <(BigFloat a, BigFloat b) => a.v < b.v;
|
|
public static bool operator >(BigFloat a, BigFloat b) => a.v > b.v;
|
|
|
|
public static bool operator <=(BigFloat a, BigFloat b) => a.v <= b.v;
|
|
public static bool operator >=(BigFloat a, BigFloat b) => a.v >= b.v;
|
|
|
|
public static implicit operator BigFloat(int value) => new BigFloat(value);
|
|
public static implicit operator BigFloat(float value) => new BigFloat(value);
|
|
public static implicit operator BigFloat(double value) => new BigFloat(value);
|
|
public static implicit operator BigFloat(BigInteger value) => new BigFloat(value);
|
|
|
|
public static implicit operator int(BigFloat value) => (int)(value.v / _decimalSize);
|
|
public static implicit operator float(BigFloat value) => (float)value.v / _decimalSize;
|
|
public static implicit operator double(BigFloat value) => (double)value.v / _decimalSize;
|
|
public static implicit operator BigInteger(BigFloat value) => value.v / _decimalSize;
|
|
|
|
public static BigFloat Parse(string value)
|
|
{
|
|
if (value is null || value.Length == 0) return new BigFloat(0);
|
|
|
|
bool isStartDec = false;
|
|
int decCount = 0;
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
for (int i = 0; i < value.Length; i++)
|
|
{
|
|
if (value[i] == '.')
|
|
{
|
|
isStartDec = true;
|
|
}
|
|
else
|
|
{
|
|
sb.Append(value[i]);
|
|
|
|
if (isStartDec && decCount++ > _decimalZeros)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
sb.Append('0', _decimalZeros - decCount);
|
|
|
|
return new BigFloat() {
|
|
v = BigInteger.Parse(sb.ToString())
|
|
};
|
|
}
|
|
|
|
public static BigFloat Clamp(BigFloat value, BigFloat min, BigFloat max)
|
|
{
|
|
return value < min ? min : value > max ? max : value;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return ToString(_decimalZeros);
|
|
}
|
|
|
|
public string ToString(int decimalCount)
|
|
{
|
|
decimalCount = Math.Clamp(decimalCount, 0, _decimalZeros);
|
|
|
|
StringBuilder sb = new StringBuilder(v.ToString());
|
|
bool isNegative = v < 0;
|
|
|
|
if (isNegative)
|
|
sb.Remove(0, 1);
|
|
|
|
if (sb.Length <= _decimalZeros)
|
|
sb.Insert(0, "0", _decimalZeros - sb.Length + 1);
|
|
|
|
sb.Insert(sb.Length - _decimalZeros, ".");
|
|
sb.Remove(sb.Length - _decimalZeros + decimalCount, _decimalZeros - decimalCount);
|
|
|
|
if (isNegative)
|
|
sb.Insert(0, "-");
|
|
|
|
return sb.ToString().Trim('.');
|
|
}
|
|
|
|
public string ToString(string format, IFormatProvider formatProvider)
|
|
{
|
|
//TODO: Implement format
|
|
return ToString();
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
BigFloat other = (BigFloat)obj;
|
|
return v == other.v;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return v.GetHashCode();
|
|
}
|
|
|
|
public int CompareTo(object obj)
|
|
{
|
|
return obj is BigFloat other ? v.CompareTo(other.v) : 1;
|
|
}
|
|
|
|
public int CompareTo(BigFloat other)
|
|
{
|
|
return v.CompareTo(other.v);
|
|
}
|
|
|
|
public bool Equals(BigFloat other)
|
|
{
|
|
return v == other.v;
|
|
}
|
|
#endif
|
|
}
|
|
}
|