F# PowerPack's BigRational
类型转换为double
来评估该值。但是,在分子和分母达到一定大小后,返回的值为double.NaN
。
由于BigRational
将分子和分母都跟踪为System.Numerics.BigInteger
s,因此您可以使用对数属性的变通方法:
a / b = e^(ln(a) - ln(b))
使用我们的BigInteger
分子和分母,我们可以调用
BigInteger num = myBigRational.Numerator;
BigInteger den = myBigRational.Denominator;
double value = Math.Exp(BigInteger.Log(num) - BigInteger.Log(den));
由于double
类型的结构对于接近0的值的限制,我宁愿使用decimal
。我只是没弄明白。
为了好玩,我正在编写一个使用arctan
的{{3}}计算pi的程序。
arctan(x) = x - x^3/3 + x^5/5 - x^7/7 + x^9/9 - ...
如果我们将系列评估为1,我们得到
arctan(1) = 1 - 1/3 + 1/5 - 1/7 + 1/9 - ...
从arctan(1) = pi/4
开始,我们可以将系列乘以4来计算pi。
我的程序的目标是计算收敛到pi accurate to n digits
所需的系列术语的数量。例如,为了使系列精确到一位数(3),它需要前三个术语:
1 term: 4 * (1) = 4
2 terms: 4 * (1 - 1/3) = 2.666666666666667
3 terms: 4 * (1 - 1/3 + 1/5) = 3.466666666666667
要准确到2位数(3.1),它需要前19个术语。准确到3位数(3.14)需要前119个术语,依此类推。
我最初使用C#的decimal
类型编写了我的程序:
const int MaxDigits = 20;
private static void RunDecimalCalculation()
{
decimal pi = 0m; // our current approximation of pi
decimal denominator = 1m;
decimal addSubtract = 1m;
ulong terms = 0;
for (int digits = 0; digits < MaxDigits; digits++)
{
decimal piToDigits, upperBound;
GetBounds(digits, out piToDigits, out upperBound);
while (pi >= upperBound | pi < piToDigits)
{
pi += addSubtract * 4m / denominator;
denominator += 2m;
addSubtract *= -1m;
terms++;
}
PrintUpdate(terms, digits, pi);
}
}
/// <summary>
/// Returns the convergence bounds for <paramref name="digits"/> digits of pi.
/// </summary>
/// <param name="digits">Number of accurate digits of pi.</param>
/// <param name="piToDigits">Pi to the first <paramref name="digits"/> digits of pi.</param>
/// <param name="upperBound">same as <paramref name="piToDigits"/>, but with the last digit + 1</param>
/// <example>
/// <code>GetBounds(1)</code>:
/// piToDigits = 3
/// upperBound = 4
///
/// <code>GetBounds(2)</code>:
/// piToDigits = 3.1
/// upperbound = 3.2
/// </example>
private static void GetBounds(int digits, out decimal piToDigits, out decimal upperBound)
{
int pow = (int)Math.Pow(10, digits);
piToDigits = (decimal)Math.Floor(Math.PI * pow) / pow;
upperBound = piToDigits + 1m / pow;
}
但我意识到,由于每次迭代中的舍入错误,在足够的条件之后,所需的术语数量可能会被关闭。所以,我开始研究F#PowerPack的BigRational
并重新编写代码:
// very minor optimization by caching common values
static readonly BigRational Minus1 = BigRational.FromInt(-1);
static readonly BigRational One = BigRational.FromInt(1);
static readonly BigRational Two = BigRational.FromInt(2);
static readonly BigRational Four = BigRational.FromInt(4);
private static void RunBigRationalCalculation()
{
BigRational pi = BigRational.Zero;
ulong terms = 0;
var series = TaylorSeries().GetEnumerator();
for (int digits = 0; digits < MaxDigits; digits++)
{
BigRational piToDigits, upperBound;
GetBounds(digits, out piToDigits, out upperBound);
while (pi >= upperBound | pi < piToDigits)
{
series.MoveNext();
pi += series.Current;
terms++;
}
double piDouble = Math.Exp(BigInteger.Log(pi.Numerator) - BigInteger.Log(pi.Denominator));
PrintUpdate(terms, digits, (decimal)piDouble);
}
}
// code adapted from http://tomasp.net/blog/powerpack-numeric.aspx
private static IEnumerable<BigRational> TaylorSeries()
{
BigRational n = One;
BigRational q = One;
while (true)
{
yield return q * Four / n;
n += Two;
q *= Minus1;
}
}
不出所料,这个版本慢慢地运行 ,这很好。 (十进制版本需要34秒才能达到9个准确的数字; BigRational版本需要17秒才能达到5个准确的数字,并且已经运行了大约半个小时并且没有达到6个准确的数字)。但令我感到沮丧的是,double
不如decimal
准确,所以虽然术语的数量总是正确的,但显示的值是
double piDouble = Math.Exp(BigInteger.Log(pi.Numerator) - BigInteger.Log(pi.Denominator));
不准确。有没有办法解决这个问题,无论是通过数学魔法还是某些具有decimal
版Math.Exp()
和BigInteger.Log()
的库?
答案 0 :(得分:3)
如何在C#中将BigIntegers的划分评估为十进制而不是两倍?
你有BigIntegers N和D,并希望将精确值N / D近似为小数。 WOLOG认为两者都是正面的。
这很容易。首先解决这个问题:
第二,解决这个问题:
第三,解决这个问题:
将I0,I1和I2转换为无符号整数然后转换为有符号整数。
现在您拥有了调用所需的一切
https://msdn.microsoft.com/en-us/library/bb1c1a6x(v=vs.110).aspx
嘿,你手边有一个小数。
那说:首先你不应该这样做。你正在BigRationals做数学;你为什么要把它们放到小数或双打?只需将pi大概逼近你想要的大概率,并将慢转换系列与之比较。
仅供参考,这个系列慢慢收敛非常。一旦你凭经验确定收敛速度有多慢,你能否提供收敛率的任何界限证明?