如何在C#中将BigInteger提高到Double次幂?

4

我尝试使用 BigInteger.Pow 方法来计算类似于10^12345.987654321的数,但是这个方法只接受像这样的整数作为指数:

BigInteger.Pow(BigInteger x, int y)

那么我该如何在上述方法中使用双精度浮点数作为指数呢?

3个回答

5

C#中没有任意精度的大数支持,因此无法直接完成此操作。有一些替代方案(例如寻找第三方库),或者您可以尝试像下面的代码一样做 - 如果基数足够小,就像在您的情况下。

public class StackOverflow_11179289
{
    public static void Test()
    {
        int @base = 10;
        double exp = 12345.123;
        int intExp = (int)Math.Floor(exp);
        double fracExp = exp - intExp;
        BigInteger temp = BigInteger.Pow(@base, intExp);
        double temp2 = Math.Pow(@base, fracExp);
        int fractionBitsForDouble = 52;
        for (int i = 0; i < fractionBitsForDouble; i++)
        {
            temp = BigInteger.Divide(temp, 2);
            temp2 *= 2;
        }

        BigInteger result = BigInteger.Multiply(temp, (BigInteger)temp2);

        Console.WriteLine(result);
    }
}

使用大整数算法计算指数的整数部分的幂,然后使用双精度(64位浮点数)算法计算分数部分的幂。利用以下事实:
a ^ (int + frac) = a ^ int * a ^ frac

我们可以将这两个值合并为一个大整数。但是简单地将双精度值转换为BigInteger会失去许多精度,因此我们首先将精度“移位”到bigInteger上(使用上面的循环以及double类型使用52位精度这一事实),然后再乘以结果。
请注意,结果是一个近似值,如果您需要更精确的数字,则需要使用执行任意精度浮点数运算的库。
更新:如果底数/指数足够小,使得幂在double范围内,我们可以简单地按照Sebastian Piu建议的方式操作(new BigInteger(Math.Pow((double)base, exp)))。

我只是出于好奇问一下,为什么使用new BigInteger(Math.Pow(10, 123.123));不正确? - Sebastian Piu
2
他想要计算10 ^ 12345.123,但这已经超出了double类型的范围(Math.Pow的结果)。我将其缩小以便在我的控制台应用程序中查看结果,但我会再次增加它以使其更清晰。 - carlosfigueira
1
那么你需要一个第三方库。这在核心的.NET Framework中不存在。 - carlosfigueira
@carlosfigueira 你知道有没有任何第三方库可以解决这个问题? - Siyamak Shahpasand
不,你可以让这个问题保持开放状态等待更多答案,但是这里已经有其他人提出过类似的问题(例如https://dev59.com/N0bRa4cB1Zd3GeqP3s3Y,https://dev59.com/eHE85IYBdhLWcg3wQxPG),而指向一个库的唯一答案链接已经失效。 - carlosfigueira
1
有趣!但你不必在循环中执行“精确转换”。你可以使用一个常量“2的52次方”(或53次方,甚至更好),它可以被表示为DoubleBigInteger。然后将temp除以这个常量一次,将temp2乘以这个常量一次即可。 - Jeppe Stig Nielsen

2
我喜欢carlosfigueira的答案,但他的方法仅在前15-17位(最显著)数字上可以得到正确的结果,因为最终使用了System.Double作为乘数。
有趣的是,确实存在一种方法BigInteger.Log可以执行“反”操作。因此,如果要计算Pow(7, 123456.78),理论上可以搜索所有BigInteger类型的数字x,以找到一个数字,使得BigInteger.Log(x, 7)等于123456.78或比任何其他类型的BigInteger的x更接近123456.78。
当然,对数函数是增加的,因此您的搜索可以使用某种“二进制搜索”(二分搜索)。我们的答案在Pow(7, 123456)和Pow(7, 123457)之间,这两个值都可以精确计算。
现在,我们如何预测是否有多个整数的对数是123456.78,直到System.Double的精度,或者是否实际上不存在任何整数的对数达到该特定Double(理想Pow函数的精确结果为无理数)? 在我们的示例中,将有非常多的整数给出相同的Double 123456.78,因为因子m = Pow(7, epsilon)(其中epsilon是最小正数,使得123456.78 + epsilon的表示形式作为Double与123456.78的表示形式不同)足够大,以至于在真实答案和真实答案乘以m之间将有非常多的整数。
从微积分课程中记得,数学函数x→Pow(7, x)的导数是x→Log(7)*Pow(7, x),因此所讨论的指数函数的斜率为Log(7)*Pow(7, 123456.78)。这个数字乘以上面的epsilon仍然比一大得多,因此有许多满足我们需求的整数。
实际上,我认为carlosfigueira的方法将给出一个“正确”的答案x,意思是Log(x, 7)具有与123456.78相同的Double表示形式。但有人试过吗? :-)

1
我会提供另一个更清晰的答案。关键是:由于System.Double的精度仅限于大约15-17个小数位,任何Pow(BigInteger, Double)计算的结果将具有更有限的精度。因此,没有比carlosfigueira的答案更好的希望。
让我用一个例子来说明这一点。假设我们想要计算
Pow(10, exponent)

在这个例子中,我选择双精度数字作为指数。
const double exponent = 100.0 * Math.PI;

这当然只是一个例子。 exponent 的值,以十进制表示,可以给定为以下之一

314.159265358979
314.15926535897933
314.1592653589793258106510620564222335815429687500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000...

这些数字中的第一个是你通常看到的(15位数)。第二个版本是使用exponent.ToString("R")生成的,包含17位数。请注意,Double的精度小于17位数。上面的第三种表示是exponent的理论“精确”值。当然,这与数学上的100π在第17位数上有所不同。

为了弄清楚Pow(10, exponent)应该是什么,我只需对许多数字x执行BigInteger.Log10(x),以查看如何重现exponent。因此,这里呈现的结果仅反映了.NET Framework对BigInteger.Log10的实现。

事实证明,任何BigInteger x都可以从

0x0C3F859904635FC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
through
0x0C3F85990481FE7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

Log10(x)函数将以15位精度使其等于exponent。同样,任何数字从

0x0C3F8599047BDEC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
through
0x0C3F8599047D667FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

满足 Log10(x) == exponent 的精度为 Double。换句话说,后者范围内的任何数字都同样“正确”作为 Pow(10, exponent) 的结果,仅因为 exponent 的精度非常有限。

(插曲:一堆 0F 表明 .NET 的实现只考虑了 x 的最高有效字节。他们不关心做得更好,正是因为 Double 类型具有这种有限的精度。)

现在,引入第三方软件的唯一原因是,如果您坚持认为exponent应该被解释为上述十进制数的第三个数字。(真是一个奇迹,Double 类型允许您精确地指定您想要的数字,对吧?)在这种情况下,Pow(10, exponent)的结果将是一个带有永不重复小数尾巴的无理(但代数)数字。它不能在没有四舍五入/截断的情况下适合一个整数。另外,如果我们将指数取为实数 100π,则数学上的结果将有所不同:我猜会得到某些超越数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接