private static void CalculateEOnly(int numBits) { PrecisionSpec extendedPrecision = new PrecisionSpec(numBits + 1, PrecisionSpec.BaseType.BIN); PrecisionSpec normalPrecision = new PrecisionSpec(numBits, PrecisionSpec.BaseType.BIN); int iExponent = (int)(Math.Sqrt(numBits)); BigFloat factorial = new BigFloat(1, extendedPrecision); BigFloat constant = new BigFloat(1, extendedPrecision); constant.exponent -= iExponent; BigFloat numerator = new BigFloat(constant); BigFloat reciprocal; //Calculate the 2^iExponent th root of e BigFloat e = new BigFloat(1, extendedPrecision); int i; for (i = 1; i < Int32.MaxValue; i++) { BigFloat number = new BigFloat(i, extendedPrecision); factorial.Mul(number); reciprocal = factorial.Reciprocal(); reciprocal.Mul(numerator); if (-reciprocal.exponent > numBits) break; e.Add(reciprocal); numerator.Mul(constant); System.GC.Collect(); } for (i = 0; i < iExponent; i++) { numerator.Assign(e); e.Mul(numerator); } //Set the cached static values. eCache = new BigFloat(e, normalPrecision); eRCPCache = new BigFloat(e.Reciprocal(), normalPrecision); }
/// <summary> /// Uses the Gauss-Legendre formula for pi /// Taken from http://en.wikipedia.org/wiki/Gauss%E2%80%93Legendre_algorithm /// </summary> /// <param name="numBits"></param> private static void CalculatePi(int numBits) { int bits = numBits + 32; //Precision extend taken out. PrecisionSpec normalPres = new PrecisionSpec(numBits, PrecisionSpec.BaseType.BIN); PrecisionSpec extendedPres = new PrecisionSpec(bits, PrecisionSpec.BaseType.BIN); if (scratch.Precision.NumBits != bits) { scratch = new BigInt(extendedPres); } //a0 = 1 BigFloat an = new BigFloat(1, extendedPres); //b0 = 1/sqrt(2) BigFloat bn = new BigFloat(2, extendedPres); bn.Sqrt(); bn.exponent--; //to = 1/4 BigFloat tn = new BigFloat(1, extendedPres); tn.exponent -= 2; int pn = 0; BigFloat anTemp = new BigFloat(extendedPres); int iteration = 0; int cutoffBits = numBits >> 5; for (iteration = 0; ; iteration++) { //Save a(n) anTemp.Assign(an); //Calculate new an an.Add(bn); an.exponent--; //Calculate new bn bn.Mul(anTemp); bn.Sqrt(); //Calculate new tn anTemp.Sub(an); anTemp.mantissa.SquareHiFast(scratch); anTemp.exponent += anTemp.exponent + pn + 1 - anTemp.mantissa.Normalise(); tn.Sub(anTemp); anTemp.Assign(an); anTemp.Sub(bn); if (anTemp.exponent < -(bits - cutoffBits)) break; //New pn pn++; } an.Add(bn); an.mantissa.SquareHiFast(scratch); an.exponent += an.exponent + 1 - an.mantissa.Normalise(); tn.exponent += 2; an.Div(tn); pi = new BigFloat(an, normalPres); piBy2 = new BigFloat(pi); piBy2.exponent--; twoPi = new BigFloat(pi, normalPres); twoPi.exponent++; piRecip = new BigFloat(an.Reciprocal(), normalPres); twoPiRecip = new BigFloat(piRecip); twoPiRecip.exponent--; //1/3 is going to be useful for sin. threeRecip = new BigFloat((new BigFloat(3, extendedPres)).Reciprocal(), normalPres); }
private static BigFloat TenPow(int power, PrecisionSpec precision) { BigFloat acc = new BigFloat(1, precision); BigFloat temp = new BigFloat(1, precision); int powerTemp = power; BigFloat multiplierToUse = new BigFloat(10, precision); if (power < 0) { multiplierToUse = multiplierToUse.Reciprocal(); powerTemp = -power; } //Fast power function while (powerTemp != 0) { temp.Mul(multiplierToUse); multiplierToUse.Assign(temp); if ((powerTemp & 1) != 0) { acc.Mul(temp); } powerTemp >>= 1; } return acc; }
private static BigFloat R(BigFloat a0, BigFloat b0) { //Precision extend taken out. int bits = a0.mantissa.Precision.NumBits; PrecisionSpec extendedPres = new PrecisionSpec(bits, PrecisionSpec.BaseType.BIN); BigFloat an = new BigFloat(a0, extendedPres); BigFloat bn = new BigFloat(b0, extendedPres); BigFloat sum = new BigFloat(extendedPres); BigFloat term = new BigFloat(extendedPres); BigFloat temp1 = new BigFloat(extendedPres); BigFloat one = new BigFloat(1, extendedPres); int iteration = 0; for (iteration = 0; ; iteration++) { //Get the sum term for this iteration. term.Assign(an); term.Mul(an); temp1.Assign(bn); temp1.Mul(bn); //term = an^2 - bn^2 term.Sub(temp1); //term = 2^(n-1) * (an^2 - bn^2) term.exponent += iteration - 1; sum.Add(term); if (term.exponent < -(bits - 8)) break; //Calculate the new AGM estimates. temp1.Assign(an); an.Add(bn); //a(n+1) = (an + bn) / 2 an.MulPow2(-1); //b(n+1) = sqrt(an*bn) bn.Mul(temp1); bn.Sqrt(); } one.Sub(sum); one = one.Reciprocal(); return new BigFloat(one, a0.mantissa.Precision); }
private void Exp(int numBits) { if (IsSpecialValue) { if (SpecialValue == SpecialValueType.ZERO) { //e^0 = 1 exponent = 0; mantissa.SetHighDigit(0x80000000); } else if (SpecialValue == SpecialValueType.INF_MINUS) { //e^-inf = 0 SetZero(); } return; } PrecisionSpec prec = new PrecisionSpec(numBits, PrecisionSpec.BaseType.BIN); numBits = prec.NumBits; if (scratch.Precision.NumBits != prec.NumBits) { scratch = new BigInt(prec); } if (inverseFactorialCache == null || invFactorialCutoff < numBits) { CalculateFactorials(numBits); } //let x = 1 * 'this'.mantissa (i.e. 1 <= x < 2) //exp(2^n * x) = e^(2^n * x) = (e^x)^2n = exp(x)^2n int oldExponent = 0; if (exponent > -4) { oldExponent = exponent + 4; exponent = -4; } BigFloat thisSave = new BigFloat(this, prec); BigFloat temp = new BigFloat(1, prec); BigFloat temp2 = new BigFloat(this, prec); BigFloat res = new BigFloat(1, prec); int length = inverseFactorialCache.Length; int iterations; for (int i = 1; i < length; i++) { //temp = x^i temp.Mul(thisSave); temp2.Assign(inverseFactorialCache[i]); temp2.Mul(temp); if (temp2.exponent < -(numBits + 4)) { iterations = i; break; } res.Add(temp2); } //res = exp(x) //Now... x^(2^n) = (x^2)^(2^(n - 1)) for (int i = 0; i < oldExponent; i++) { res.mantissa.SquareHiFast(scratch); int shift = res.mantissa.Normalise(); res.exponent = res.exponent << 1; res.exponent += 1 - shift; } //Deal with +/- inf if (res.exponent == Int32.MaxValue) { res.mantissa.Zero(); } Assign(res); }
/// <summary> /// Calculates ln(2) and returns -10^(n/2 + a bit) for reuse, using the AGM method as described in /// http://lacim.uqam.ca/~plouffe/articles/log2.pdf /// </summary> /// <param name="numBits"></param> /// <returns></returns> private static void CalculateLog2(int numBits) { //Use the AGM method formula to get log2 to N digits. //R(a0, b0) = 1 / (1 - Sum(2^-n*(an^2 - bn^2))) //log(1/2) = R(1, 10^-n) - R(1, 10^-n/2) PrecisionSpec normalPres = new PrecisionSpec(numBits, PrecisionSpec.BaseType.BIN); PrecisionSpec extendedPres = new PrecisionSpec(numBits + 1, PrecisionSpec.BaseType.BIN); BigFloat a0 = new BigFloat(1, extendedPres); BigFloat b0 = TenPow(-(int)((double)((numBits >> 1) + 2) * 0.302), extendedPres); BigFloat pow10saved = new BigFloat(b0); BigFloat firstAGMcacheSaved = new BigFloat(extendedPres); //save power of 10 (in normal precision) pow10cache = new BigFloat(b0, normalPres); ln2cache = R(a0, b0); //save the first half of the log calculation firstAGMcache = new BigFloat(ln2cache, normalPres); firstAGMcacheSaved.Assign(ln2cache); b0.MulPow2(-1); ln2cache.Sub(R(a0, b0)); //Convert to log(2) ln2cache.mantissa.Sign = false; //Save magic constant for newton log //First guess in range 1 <= x < 2 is x0 = ln2 * (x - 1) + C logNewtonConstant = new BigFloat(ln2cache); logNewtonConstant.Mul(new BigFloat(3, extendedPres)); logNewtonConstant.exponent--; logNewtonConstant.Sub(new BigFloat(1, extendedPres)); logNewtonConstant = new BigFloat(logNewtonConstant, normalPres); //Save the inverse. log2ecache = new BigFloat(ln2cache); log2ecache = new BigFloat(log2ecache.Reciprocal(), normalPres); //Now cache log10 //Because the log functions call this function to the precision to which they //are called, we cannot call them without causing an infinite loop, so we need //to inline the code. log10recip = new BigFloat(10, extendedPres); { int power2 = log10recip.exponent + 1; log10recip.exponent = -1; //BigFloat res = new BigFloat(firstAGMcache); BigFloat ax = new BigFloat(1, extendedPres); BigFloat bx = new BigFloat(pow10saved); bx.Mul(log10recip); BigFloat r = R(ax, bx); log10recip.Assign(firstAGMcacheSaved); log10recip.Sub(r); ax.Assign(ln2cache); ax.Mul(new BigFloat(power2, log10recip.mantissa.Precision)); log10recip.Add(ax); } log10recip = log10recip.Reciprocal(); log10recip = new BigFloat(log10recip, normalPres); //Trim to n bits ln2cache = new BigFloat(ln2cache, normalPres); }
/// <summary> /// Tried the newton method for logs, but the exponential function is too slow to do it. /// </summary> private void LogNewton() { if (mantissa.IsZero() || mantissa.Sign) { return; } //Compute ln2. if (ln2cache == null || mantissa.Precision.NumBits > ln2cache.mantissa.Precision.NumBits) { CalculateLog2(mantissa.Precision.NumBits); } int numBits = mantissa.Precision.NumBits; //Use inverse exp function with Newton's method. BigFloat xn = new BigFloat(this); BigFloat oldExponent = new BigFloat(xn.exponent, mantissa.Precision); xn.exponent = 0; this.exponent = 0; //Hack to subtract 1 xn.mantissa.ClearBit(numBits - 1); //x0 = (x - 1) * log2 - this is a straight line fit between log(1) = 0 and log(2) = ln2 xn.Mul(ln2cache); //x0 = (x - 1) * log2 + C - this corrects for minimum error over the range. xn.Add(logNewtonConstant); BigFloat term = new BigFloat(mantissa.Precision); BigFloat one = new BigFloat(1, mantissa.Precision); int precision = 32; int normalPrecision = mantissa.Precision.NumBits; int iterations = 0; while (true) { term.Assign(xn); term.mantissa.Sign = true; term.Exp(precision); term.Mul(this); term.Sub(one); iterations++; if (term.exponent < -((precision >> 1) - 4)) { if (precision == normalPrecision) { if (term.exponent < -(precision - 4)) break; } else { precision = precision << 1; if (precision > normalPrecision) precision = normalPrecision; } } xn.Add(term); } //log(2^n*s) = log(2^n) + log(s) = nlog(2) + log(s) term.Assign(ln2cache); term.Mul(oldExponent); this.Assign(xn); this.Add(term); }
/// <summary> /// arctan(): the inverse function of sin(), range of (-pi/2..pi/2) /// </summary> public void Arctan() { //With 2 argument reductions, we increase precision by a minimum of 4 bits per term. int numBits = mantissa.Precision.NumBits; int maxTerms = numBits >> 2; if (pi == null || pi.mantissa.Precision.NumBits != numBits) { CalculatePi(mantissa.Precision.NumBits); } //Make domain positive bool sign = mantissa.Sign; mantissa.Sign = false; if (IsSpecialValue) { if (SpecialValue == SpecialValueType.INF_MINUS || SpecialValue == SpecialValueType.INF_PLUS) { Assign(piBy2); mantissa.Sign = sign; return; } return; } if (reciprocals == null || reciprocals[0].mantissa.Precision.NumBits != numBits || reciprocals.Length < maxTerms) { CalculateReciprocals(numBits, maxTerms); } bool invert = false; BigFloat one = new BigFloat(1, mantissa.Precision); //Invert if outside of convergence if (GreaterThan(one)) { invert = true; Assign(Reciprocal()); } //Reduce using half-angle formula: //arctan(2x) = 2 arctan (x / (1 + sqrt(1 + x))) //First reduction (guarantees 2 bits per iteration) BigFloat temp = new BigFloat(this); temp.Mul(this); temp.Add(one); temp.Sqrt(); temp.Add(one); this.Div(temp); //Second reduction (guarantees 4 bits per iteration) temp.Assign(this); temp.Mul(this); temp.Add(one); temp.Sqrt(); temp.Add(one); this.Div(temp); //Actual series calculation int length = reciprocals.Length; BigFloat term = new BigFloat(this); //pow = x^2 BigFloat pow = new BigFloat(this); pow.Mul(this); BigFloat sum = new BigFloat(this); for (int i = 1; i < length; i++) { //u(n) = u(n-1) * x^2 //t(n) = u(n) / (2n+1) term.Mul(pow); term.Sign = !term.Sign; temp.Assign(term); temp.Mul(reciprocals[i]); if (temp.exponent < -numBits) break; sum.Add(temp); } //Undo the reductions. Assign(sum); exponent += 2; if (invert) { //Assign(Reciprocal()); mantissa.Sign = true; Add(piBy2); } if (sign) { mantissa.Sign = sign; } }
/// <summary> /// Multiplies two numbers and assigns the result to res. /// </summary> /// <param name="res">a pre-existing BigFloat to take the result</param> /// <param name="n1">the first number</param> /// <param name="n2">the second number</param> /// <returns>a handle to res</returns> public static BigFloat Mul(BigFloat res, BigFloat n1, BigFloat n2) { res.Assign(n1); res.Mul(n2); return res; }
/// <summary> /// Divides two numbers and assigns the result to res. /// </summary> /// <param name="res">a pre-existing BigFloat to take the result</param> /// <param name="n1">the first number</param> /// <param name="n2">the second number</param> /// <returns>a handle to res</returns> public static BigFloat Div(BigFloat res, BigFloat n1, BigFloat n2) { res.Assign(n1); res.Div(n2); return res; }
/// <summary> /// Subtracts two numbers and assigns the result to res. /// </summary> /// <param name="res">a pre-existing BigFloat to take the result</param> /// <param name="n1">the first number</param> /// <param name="n2">the second number</param> /// <returns>a handle to res</returns> public static BigFloat Sub(BigFloat res, BigFloat n1, BigFloat n2) { res.Assign(n1); res.Sub(n2); return res; }
//******************* Fast static functions ******************** /// <summary> /// Adds two numbers and assigns the result to res. /// </summary> /// <param name="res">a pre-existing BigFloat to take the result</param> /// <param name="n1">the first number</param> /// <param name="n2">the second number</param> /// <returns>a handle to res</returns> public static BigFloat Add(BigFloat res, BigFloat n1, BigFloat n2) { res.Assign(n1); res.Add(n2); return res; }
/// <summary> /// Two-variable iterative square root, taken from /// http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#A_two-variable_iterative_method /// </summary> public void Sqrt() { if (mantissa.Sign || IsSpecialValue) { if (SpecialValue == SpecialValueType.ZERO) { return; } if (SpecialValue == SpecialValueType.INF_MINUS || mantissa.Sign) { SetNaN(); } return; } BigFloat temp2; BigFloat temp3 = new BigFloat(mantissa.Precision); BigFloat three = new BigFloat(3, mantissa.Precision); int exponentScale = 0; //Rescale to 0.5 <= x < 2 if (exponent < -1) { int diff = -exponent; if ((diff & 1) != 0) { diff--; } exponentScale = -diff; exponent += diff; } else if (exponent > 0) { if ((exponent & 1) != 0) { exponentScale = exponent + 1; exponent = -1; } else { exponentScale = exponent; exponent = 0; } } temp2 = new BigFloat(this); temp2.Sub(new BigFloat(1, mantissa.Precision)); //if (temp2.mantissa.IsZero()) //{ // exponent += exponentScale; // return; //} int numBits = mantissa.Precision.NumBits; while ((exponent - temp2.exponent) < numBits && temp2.SpecialValue != SpecialValueType.ZERO) { //a(n+1) = an - an*cn / 2 temp3.Assign(this); temp3.Mul(temp2); temp3.MulPow2(-1); this.Sub(temp3); //c(n+1) = cn^2 * (cn - 3) / 4 temp3.Assign(temp2); temp2.Sub(three); temp2.Mul(temp3); temp2.Mul(temp3); temp2.MulPow2(-2); } exponent += (exponentScale >> 1); }
//******************** Mathematical Constants ******************* /// <summary> /// Gets pi to the indicated precision /// </summary> /// <param name="precision">The precision to perform the calculation to</param> /// <returns>pi (the ratio of the area of a circle to its diameter)</returns> public static BigFloat GetPi(PrecisionSpec precision) { if (pi == null || precision.NumBits <= pi.mantissa.Precision.NumBits) { CalculatePi(precision.NumBits); } BigFloat ret = new BigFloat (precision); ret.Assign(pi); return ret; }
/// <summary> /// Log(x) implemented as an Arithmetic-Geometric Mean. Fast for high precisions. /// </summary> private void LogAGM1() { if (mantissa.IsZero() || mantissa.Sign) { return; } //Compute ln2. if (ln2cache == null || mantissa.Precision.NumBits > ln2cache.mantissa.Precision.NumBits) { CalculateLog2(mantissa.Precision.NumBits); } //Compute ln(x) using AGM formula //1. Re-write the input as 2^n * (0.5 <= x < 1) int power2 = exponent + 1; exponent = -1; //BigFloat res = new BigFloat(firstAGMcache); BigFloat a0 = new BigFloat(1, mantissa.Precision); BigFloat b0 = new BigFloat(pow10cache); b0.Mul(this); BigFloat r = R(a0, b0); this.Assign(firstAGMcache); this.Sub(r); a0.Assign(ln2cache); a0.Mul(new BigFloat(power2, mantissa.Precision)); this.Add(a0); }
/// <summary> /// Get e to the indicated precision /// </summary> /// <param name="precision">The preicision to perform the calculation to</param> /// <returns>e (the number for which the d/dx(e^x) = e^x)</returns> public static BigFloat GetE(PrecisionSpec precision) { if (eCache == null || eCache.mantissa.Precision.NumBits < precision.NumBits) { CalculateEOnly(precision.NumBits); //CalculateFactorials(precision.NumBits); } BigFloat ret = new BigFloat(precision); ret.Assign(eCache); return ret; }
/// <summary> /// Calculates Sin(x): /// This takes a little longer and is less accurate if the input is out of the range (-pi, pi]. /// </summary> public void Sin() { if (IsSpecialValue) { //Sin(x) has no limit as x->inf if (SpecialValue == SpecialValueType.INF_MINUS || SpecialValue == SpecialValueType.INF_PLUS) { SetNaN(); } return; } //Convert to positive range (0 <= x < inf) bool sign = mantissa.Sign; mantissa.Sign = false; if (pi == null || pi.mantissa.Precision.NumBits != mantissa.Precision.NumBits) { CalculatePi(mantissa.Precision.NumBits); } if (inverseFactorialCache == null || invFactorialCutoff != mantissa.Precision.NumBits) { CalculateFactorials(mantissa.Precision.NumBits); } //Rescale into 0 <= x < 2*pi if (GreaterThan(twoPi)) { //There will be an inherent loss of precision doing this. BigFloat newAngle = new BigFloat(this); newAngle.Mul(twoPiRecip); newAngle.FPart(); newAngle.Mul(twoPi); Assign(newAngle); } //Rescale into range 0 <= x < pi if (GreaterThan(pi)) { //sin(pi + a) = sin(pi)cos(a) + sin(a)cos(pi) = 0 - sin(a) = -sin(a) Sub(pi); sign = !sign; } BigFloat temp = new BigFloat(mantissa.Precision); //Rescale into range 0 <= x < pi/2 if (GreaterThan(piBy2)) { temp.Assign(this); Assign(pi); Sub(temp); } //Rescale into range 0 <= x < pi/6 to accelerate convergence. //This is done using sin(3x) = 3sin(x) - 4sin^3(x) Mul(threeRecip); if (mantissa.IsZero()) { exponent = 0; return; } BigFloat term = new BigFloat(this); BigFloat square = new BigFloat(this); square.Mul(term); BigFloat sum = new BigFloat(this); bool termSign = true; int length = inverseFactorialCache.Length; int numBits = mantissa.Precision.NumBits; for (int i = 3; i < length; i += 2) { term.Mul(square); temp.Assign(inverseFactorialCache[i]); temp.Mul(term); temp.mantissa.Sign = termSign; termSign = !termSign; if (temp.exponent < -numBits) break; sum.Add(temp); } //Restore the triple-angle: sin(3x) = 3sin(x) - 4sin^3(x) Assign(sum); sum.Mul(this); sum.Mul(this); Mul(new BigFloat(3, mantissa.Precision)); sum.exponent += 2; Sub(sum); //Restore the sign mantissa.Sign = sign; }