// Copyright Keysight Technologies 2012-2019
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at http://mozilla.org/MPL/2.0/.
using System;
using System.ComponentModel;
using System.Globalization;
using System.Numerics;
using System.Text;
namespace OpenTap
{
/// Arbitrary precision floating point numbers for exact numeric computations for when performance is not an issue.
[TypeConverter(typeof(BigFloatConverter))]
struct BigFloat : IFormattable, IComparable, IComparable, IEquatable
{
/// The numerator as an arbitrarily sized integer.
BigInteger Numerator;
/// The denominator as an arbitrarily sized integer.
BigInteger Denominator;
/// Creates a new BigFloat from fractional values.
///
///
public BigFloat(BigInteger nominator, BigInteger denominator)
{
Numerator = nominator;
Denominator = denominator;
Normalize();
}
public BigFloat Invert()
{
return new BigFloat { Numerator = Denominator, Denominator = Numerator };
}
/// Normalizes the fraction by dividing by greates common divisor.
/// The normalized fraction.
public BigFloat Normalize()
{
if(Denominator == 0)
{
if(Numerator > 1)
{
Numerator = 1;
}else if(Numerator < -1)
{
Numerator = -1;
}
return this;
}
else if (Denominator == Numerator)
{
Numerator = 1;
Denominator = 1;
}
if (Denominator < 0)
{
Numerator *= -1;
Denominator *= -1;
}
BigInteger gcd = BigInteger.GreatestCommonDivisor(Numerator, Denominator);
if (Denominator == gcd)
{
Numerator = Numerator / gcd;
Denominator = 1;
return this;
}
Numerator = Numerator / gcd;
Denominator = Denominator / gcd;
return this;
}
static char getDigitSeparator(IFormatProvider prov)
{
if (prov == null)
prov = CultureInfo.CurrentCulture;
if(prov is CultureInfo)
{
return ((CultureInfo)prov).NumberFormat.NumberDecimalSeparator[0];
}
if(prov is NumberFormatInfo)
{
return ((NumberFormatInfo)prov).NumberDecimalSeparator[0];
}
return '.';
}
static string fracToString(BigInteger a, BigInteger b, IFormatProvider prov)
{
var sb = new StringBuilder();
fracToString(sb, a, b, prov);
return sb.ToString();
}
static void fracToString(StringBuilder sb, BigInteger a, BigInteger b, IFormatProvider prov)
{
if (prov == null)
prov = CultureInfo.CurrentCulture.NumberFormat;
var digits = (int)Math.Ceiling(BigInteger.Log10(a) - BigInteger.Log10(b));
if (digits < 0)
sb.Append("0" + getDigitSeparator(prov));
bool firstDigitFound = false;
int firstDigit = 0;
BigInteger ten = 10;
while (digits >= 0 || firstDigitFound == false || (firstDigit - digits) < 15)
{
if (a == 0 && digits < 0)
break;
BigInteger newb = b;
if (digits >= 0)
{
newb = b * BigInteger.Pow(ten, digits);
}
else if (digits < 0)
{
a = a * 10;
}
var dig = (a / newb);
if (dig != 0 && !firstDigitFound)
{
firstDigitFound = true;
firstDigit = digits;
}
if (dig == 10)
{
sb.Append("10");
}
else if (firstDigitFound || digits <= 0)
{
sb.Append((char)('0' + (int)dig));
}
a = a - (dig * newb);
if (digits == 0 && a != 0)
sb.Append(getDigitSeparator(prov));
digits--;
}
}
public void AppendTo(StringBuilder sb, IFormatProvider prov)
{
if (prov == null)
prov = CultureInfo.CurrentCulture.NumberFormat;
var numberFormat = CultureInfo.CurrentCulture.NumberFormat;
if (prov is CultureInfo) numberFormat = (prov as CultureInfo).NumberFormat;
else if (prov is NumberFormatInfo) numberFormat = (prov as NumberFormatInfo);
if (Denominator == 0)
{
if (Numerator == 0){
sb.Append(numberFormat.NaNSymbol);
return;
}
else if (Numerator == 1)
{
sb.Append(numberFormat.PositiveInfinitySymbol);
return;
}
else if (Numerator == -1)
{
sb.Append(numberFormat.NegativeInfinitySymbol);
return;
}
}
if (Numerator == 0)
{
sb.Append("0");
return;
}
if (Denominator < 0)
{
Numerator *= -1;
Denominator *= -1;
}
var num = Numerator;
bool isNegative = false;
if (num < 0)
{
num = -num;
isNegative = true;
}
if (isNegative)
sb.Append(numberFormat.NegativeSign);
fracToString(sb, num, Denominator, prov);
}
public string ToString(IFormatProvider prov)
{
if (prov == null)
prov = CultureInfo.CurrentCulture.NumberFormat;
var numberFormat = CultureInfo.CurrentCulture.NumberFormat;
if (prov is CultureInfo) numberFormat = (prov as CultureInfo).NumberFormat;
else if (prov is NumberFormatInfo) numberFormat = (prov as NumberFormatInfo);
if (Denominator == 0)
{
if (Numerator == 0) return numberFormat.NaNSymbol;
else if (Numerator == 1) return numberFormat.PositiveInfinitySymbol;
else if (Numerator == -1) return numberFormat.NegativeInfinitySymbol;
}
if (Numerator == 0)
return "0";
if (Denominator < 0)
{
Numerator *= -1;
Denominator *= -1;
}
var num = Numerator;
bool isNegative = false;
if (num < 0)
{
num = -num;
isNegative = true;
}
var stringResult = fracToString(num, Denominator, prov);
if (isNegative)
return numberFormat.NegativeSign + stringResult;
return stringResult;
}
/// Converts the fraction to a decimal string.
///
public override string ToString()
{
return ToString(null);
}
/// Compares two numbers.
///
/// True if they are equal.
public bool Equals(BigFloat other)
{
return CompareTo(other) == 0;
}
///
/// Compares this bigfloat with another object.
///
///
///
public override bool Equals(object obj)
{
if (obj is BigFloat)
return Equals((BigFloat)obj);
return base.Equals(obj);
}
///
/// Gets the hash code of this value.
///
///
public override int GetHashCode()
{
return Numerator.GetHashCode() ^ Denominator.GetHashCode();
}
/// Compares two numbers.
///
/// -1 if other is less, 1 if other is greater and 0 if other is equal to this.
public int CompareTo(BigFloat other)
{
if (Denominator == 0)
{
if (Numerator == 0)
return -1;
if (other.Denominator == 0)
{
if (other.Numerator == 0) // NaN
return -1;
return Numerator.CompareTo(other.Numerator); //compare negiative/positive infinities.
}
return Numerator > 0 ? 1 : -1; // infinity
}
var a = Numerator * other.Denominator;
var b = other.Numerator * Denominator;
return a.CompareTo(b);
}
///
/// Converts obj before doing comparison using CompareTo. Throws an exception if obj cannot be compared to a BigFloat.
///
///
///
public int CompareTo(object obj)
{
if (obj == null)
throw new ArgumentNullException("obj");
switch (obj)
{
case double d: return CompareTo(new BigFloat(d));
case short d: return CompareTo(new BigFloat(d));
case int d: return CompareTo(new BigFloat(d));
case float d: return CompareTo(new BigFloat(d));
case decimal d: return CompareTo(new BigFloat(d));
case uint d: return CompareTo(new BigFloat(d));
case BigFloat d: return CompareTo(d);
default:
throw new InvalidCastException(string.Format("Unable to compare BigFloat to {0}", obj.GetType()));
}
}
/// Big float 1.
public static readonly BigFloat One = new BigFloat(BigInteger.One, BigInteger.One);
/// Supports parsing BigFloat without throwing an exception. Returns an exception in case something went wrong otherwise it will return a BigFloat.
///
///
///
internal static object Parse(string value, IFormatProvider format = null)
{
if (string.Equals(value, "infinity", StringComparison.OrdinalIgnoreCase))
return Infinity;
if (string.Equals(value, "-infinity", StringComparison.OrdinalIgnoreCase))
return NegativeInfinity;
if (string.Equals(value, "nan", StringComparison.OrdinalIgnoreCase))
return NaN;
if (value.Contains("/"))
{
// parse fractions: X/Y
var splitted = value.Split('/');
if (splitted.Length != 2)
{
return new FormatException("value contains multiple '/'");
}
var numerator = BigInteger.Parse(splitted[0]);
var denominator = BigInteger.Parse(splitted[1]);
return new BigFloat(numerator, denominator);
}
var sep = getDigitSeparator(format);
int index = 0;
bool dotHit = false;
bool exp = false;
BigInteger sign = 1;
BigFloat mantissa = 0;
BigInteger denom = 1;
BigInteger nomerator = 0;
for (; index < value.Length; index++)
{
var chr = value[index];
if (chr == '-' && nomerator == 0 && sign == 1)
{
sign = -1;
continue;
}
if (chr == sep && !dotHit && !exp)
{
dotHit = true;
continue;
}
if ((chr == 'e' || chr == 'E') && !exp)
{
exp = true;
mantissa = new BigFloat(nomerator * sign, denom);
denom = 1;
nomerator = 0;
dotHit = false;
sign = 1;
continue;
}
if (char.IsWhiteSpace(chr)) continue;
if ((chr == '+') && exp) continue;
if (char.IsDigit(chr) == false)
return new FormatException("Format not supported.");
if (dotHit)
denom *= 10;
int v = (chr - '0');
nomerator = nomerator * 10 + v;
}
if (exp)
{
//The scientific format "xEy" was detected, e.g 1.5e6
if (nomerator > 1000)
{ // Extremely large numbers will cause the application to stall
// the biggest number in .NET is double.Infinty: 1.7976931348623157E+308
// so 1E+1000 is probably an ok max limit.
throw new FormatException($"The value {value} is too huge or too precise to be presented as a number.");
}
var powerTerm = BigInteger.Pow(10, (int)nomerator);
if (sign < 0)
return mantissa * new BigFloat(1, powerTerm);
return mantissa * new BigFloat(powerTerm, 1);
}
return new BigFloat(nomerator * sign, denom);
}
/// Big float 0.
public static readonly BigFloat Zero = new BigFloat(BigInteger.Zero, BigInteger.One);
/// Big float Infinity.
public static readonly BigFloat Infinity = new BigFloat(BigInteger.One, BigInteger.Zero);
/// Big float negative infinity.
public static readonly BigFloat NegativeInfinity = new BigFloat(BigInteger.MinusOne, BigInteger.Zero);
/// Big float not a number.
public static readonly BigFloat NaN = new BigFloat(BigInteger.Zero, BigInteger.Zero);
///
/// Converts this value to a string.
///
///
///
///
public string ToString(string format, IFormatProvider formatProvider)
{
return ToString(formatProvider);
}
static bool fastButImpreciseEnabled = false;
/// Creates a BigFloat.
///
public BigFloat(double value) : this(value.ToString("R", CultureInfo.InvariantCulture), CultureInfo.InvariantCulture)
{
if (fastButImpreciseEnabled)
{
if (value == 0)
{
Numerator = 0;
Denominator = 1;
return;
}
double tolerance = 1.0E-9;
double orig = value;
var bc = BigFloat.Zero;
BigInteger frac = BigInteger.One;
double frac2 = 1.0;
while (true)
{
if (Math.Abs(value / orig) < tolerance)
break;
double a = Math.Round(value);
value -= a;
var fc = new BigFloat((BigInteger)a, frac);
bc = bc + fc;
frac2 *= 1000;
value *= 1000.0;
orig *= 1000.0;
frac *= 1000;
}
this = bc.Normalize();
}
}
public BigFloat(BigInteger v):this(v, BigInteger.One)
{
}
public BigFloat(float value) : this(value.ToString("r", CultureInfo.InvariantCulture), CultureInfo.InvariantCulture) { }
public BigFloat(decimal value) : this(value.ToString("F9", CultureInfo.InvariantCulture), CultureInfo.InvariantCulture)
{
}
public BigFloat(string value, IFormatProvider format = null)
{
var result = Parse(value, format);
if (result is BigFloat bf)
{
this = bf;
}
else throw (Exception)result;
}
private bool IsNan { get { return (Denominator == 0) && (Numerator == 0); } }
private bool IsPosInf { get { return (Denominator == 0) && (Numerator == 1); } }
private bool IsNegInf { get { return (Denominator == 0) && (Numerator == -1); } }
public BigFloat(int value) : this(value, 1) { }
public BigFloat(uint value) : this(value, 1) { }
public BigFloat(short value) : this(value, 1) { }
public BigFloat(long value) : this(value, 1) { }
public static implicit operator BigFloat(double d)
{
return new BigFloat(d);
}
public static implicit operator BigFloat(long d)
{
return new BigFloat(d, 1);
}
public static implicit operator BigFloat(int d)
{
return new BigFloat(d, 1);
}
public static explicit operator int(BigFloat d)
{
return (int)d.Rounded();
}
public static explicit operator double(BigFloat d)
{
return ((double)d.Numerator) / ((double)d.Denominator);
}
public static bool operator ==(BigFloat a, BigFloat b)
{
if ((a.Denominator == 0) || (b.Denominator == 0))
{
if (a.IsNan || b.IsNan)
return false;
else
return (a.Denominator == b.Denominator) && (a.Numerator == b.Numerator);
}
else
return a.Equals(b);
}
public static bool operator !=(BigFloat a, BigFloat b)
{
if ((a.Denominator == 0) || (b.Denominator == 0))
{
if (a.IsNan || b.IsNan)
return true;
else
return (a.Denominator != b.Denominator) || (a.Numerator != b.Numerator);
}
else
return a.Equals(b) == false;
}
public static bool operator ==(BigFloat a, double b)
{
return a.Equals(b);
}
public static bool operator !=(BigFloat a, double b)
{
return a.Equals(b) == false;
}
public static bool operator >=(BigFloat a, BigFloat b)
{
if ((a.Denominator == 0) || (b.Denominator == 0))
{
if (a.IsNan || b.IsNan) return false;
else if (a.IsNegInf) return b.IsNegInf;
else if (b.IsPosInf) return a.IsPosInf;
else return true;
}
else
return a.CompareTo(b) >= 0;
}
public static bool operator <=(BigFloat a, BigFloat b)
{
if ((a.Denominator == 0) || (b.Denominator == 0))
{
if (a.IsNan || b.IsNan) return false;
else if (a.IsPosInf) return b.IsPosInf;
else if (b.IsNegInf) return a.IsNegInf;
else return true;
}
else
return a.CompareTo(b) <= 0;
}
public static bool operator >(BigFloat a, BigFloat b)
{
if ((a.Denominator == 0) || (b.Denominator == 0))
{
if (a.IsNan || b.IsNan)
return false;
else if (a.IsPosInf) return !b.IsPosInf;
else if (b.IsNegInf) return !a.IsNegInf;
else
return false;
}
else
return a.CompareTo(b) > 0;
}
public static bool operator <(BigFloat a, BigFloat b)
{
if ((a.Denominator == 0) || (b.Denominator == 0))
{
if (a.IsNan || b.IsNan) return false;
else if (a.IsNegInf) return !b.IsNegInf;
else if (b.IsPosInf) return !a.IsPosInf;
else return false;
}
else
return a.CompareTo(b) < 0;
}
public BigFloat Sign()
{
if (Numerator >= 0)
return new BigFloat(1, 1);
return new BigFloat(-1, 1);
}
public static BigFloat operator +(BigFloat a, BigFloat b)
{
if (a.IsNan || b.IsNan) return NaN;
if (a.IsZero) return b;
if (b.IsZero) return a;
if (a.Denominator != b.Denominator)
{
var adenum = a.Denominator;
a.Denominator *= b.Denominator;
a.Numerator *= b.Denominator;
b.Denominator *= adenum;
b.Numerator *= adenum;
}
return new BigFloat(a.Numerator + b.Numerator, b.Denominator).Normalize();
}
public bool IsZero => Numerator.IsZero;
public static BigFloat operator -(BigFloat a, BigFloat b)
{
if (a.IsNan || b.IsNan) return NaN;
if (b.IsZero) return a;
if (a.IsZero) return new BigFloat(-b.Numerator, b.Denominator);
if (a.Denominator != b.Denominator)
{
var adenum = a.Denominator;
a.Denominator *= b.Denominator;
a.Numerator *= b.Denominator;
b.Denominator *= adenum;
b.Numerator *= adenum;
}
if (a.Numerator == b.Numerator)
{
if (a.IsPosInf || b.IsNegInf) return NaN;
return Zero;
}
return new BigFloat(a.Numerator - b.Numerator, b.Denominator).Normalize();
}
public static BigFloat operator /(BigFloat a, BigFloat b)
{
if (a.IsNan || b.IsNan) return NaN;
else if (a.IsNegInf)
{
if (b.IsPosInf || b.IsNegInf) return NaN;
else if (b < 0) return Infinity;
}
else if (a.IsPosInf)
{
if (b.IsPosInf || b.IsNegInf) return NaN;
else if (b < 0) return NegativeInfinity;
}
else if (b == One) return a;
else if (a.IsZero)
{
if (b.IsZero) return NaN;
return Zero;
}
else if (b.IsZero)
{
return a.Numerator > 0 ? Infinity : NegativeInfinity;
}
return new BigFloat(a.Numerator * b.Denominator, b.Numerator * a.Denominator).Normalize();
}
public static BigFloat operator *(BigFloat a, BigFloat b)
{
if (a.IsNan || b.IsNan) return NaN;
if (a.IsZero)
{
if (b.IsPosInf || b.IsNegInf) return NaN;
return Zero;
}
if (b.IsZero)
{
if (a.IsPosInf || a.IsNegInf) return NaN;
return Zero;
}
return new BigFloat(a.Numerator * b.Numerator, a.Denominator * b.Denominator).Normalize();
}
public BigFloat Abs()
{
return new BigFloat(Numerator * Numerator.Sign, Denominator * Denominator.Sign);
}
public BigInteger Rounded()
{
if (Denominator == 0) return int.MinValue;
if (Numerator == 0) return 0;
if (Denominator == 1) return Numerator;
return Numerator / Denominator;
}
public BigFloat Round()
{
if (IsNan || IsNegInf || IsPosInf) return this;
return new BigFloat(Rounded(), 1);
}
public static BigFloat Convert(object y, IFormatProvider prov = null)
{
switch (y)
{
case BigFloat x: return x;
case double x: return new BigFloat(x);
case float x: return new BigFloat(x);
case decimal x: return new BigFloat(x);
case int x: return new BigFloat(x);
case long x: return new BigFloat(x);
case string x: return new BigFloat(x, prov);
default: return new BigFloat((long)System.Convert.ChangeType(y, typeof(long)));
}
}
public object ConvertTo(Type t)
{
Normalize();
if (t == typeof(BigFloat))
return this;
if (t == typeof(double))
return (((double)Numerator) / ((double)Denominator));
if (t == typeof(float))
return (float)((double)((double)Numerator) / ((double)Denominator));
if (t == typeof(decimal))
return ((decimal)Numerator) / ((decimal)Denominator);
if (t == typeof(int))
return (int)(Numerator / Denominator);
if (t == typeof(long))
return (long)(Numerator / Denominator);
if (t == typeof(string))
return ToString();
if (typeof(BigFloat).DescendsTo(t))
return this;
return System.Convert.ChangeType(ConvertTo(typeof(long)), t);
}
public static BigFloat operator -(BigFloat a)
{
return new BigFloat(-a.Numerator, a.Denominator);
}
}
/// Converter for OpenTAP arbitrary precision number types.
class BigFloatConverter : TypeConverter
{
/// returns true if it can convert from
///
///
///
public override bool CanConvertFrom(ITypeDescriptorContext context,
Type sourceType)
{
if (sourceType.IsNumeric() || sourceType == typeof(string) || sourceType == typeof(BigFloat))
return true;
return base.CanConvertFrom(context, sourceType);
}
///
/// Converts from an object to a number.
///
///
///
///
///
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
return BigFloat.Convert(value);
}
///
/// Converts to a number.
///
///
///
///
///
///
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture, object value, Type destinationType)
{
return ((BigFloat)value).ConvertTo(destinationType);
}
}
}