幂函数给出的答案与 C 中的 math.pow 函数不同

Power function giving different answer than math.pow function in C

我正在尝试编写一个程序来使用 while 循环计算 x^n 的值:

#include <stdio.h>
#include <math.h>
int main()
{
    float x = 3, power = 1, copyx;
    int n = 22, copyn;
    copyx = x;
    copyn = n;

    while (n)
    {
        if ((n % 2) == 1)
        {
            power = power * x;
        }
        n = n / 2;
        x *= x;
    }

    printf("%g^%d = %f\n", copyx, copyn, power);
    printf("%g^%d = %f\n", copyx, copyn, pow(copyx, copyn));

    return 0;
}

直到 n 的值为 15,我创建的函数和 pow 函数(来自 math.h)的答案给出了相同的值;但是,当 n 的值超过 15 时,它就会开始给出不同的答案。

我不明白为什么会有不同的答案。是我函数写错了还是其他原因?

浮点运算不精确(floatdouble 更差,用于存储数据的位数更少;使用 double 可能会延迟不精确的时间更长)。 pow 函数(通常)使用求幂算法来最小化精度损失,and/or 委托给 chip-level 指令,该指令可以更有效、更精确地执行操作,或两者兼而有之。 pow 的实现也可能不止一种,具体取决于您是否告诉编译器使用严格一致的浮点数学、最快的可能、硬件指令等。

您的代码很好(尽管使用 double 会得到更精确的结果),但是与 math.hpow 的改进精度相匹配的是 non-trivial;当你这样做的时候,你就会重新发明它。这就是你使用库函数的原因。

就是说,对于您在此处使用的逻辑整数数学,算法的精度损失可能无关紧要,这纯粹是 floatdouble 问题导致精度损失从类型本身。作为一项规则,默认使用 double,只有当你 100% 确定你不需要精度时才切换到 float负担 double.

的额外 memory/computation 成本

当我 运行 你的代码时,我得到了这个:

3^22 = 31381059584.000000
3^22 = 31381059609.000000

这是因为 pow returns a double 但您的代码使用 float。当我更改为 powf 时,我得到了相同的结果:

3^22 = 31381059584.000000
3^22 = 31381059584.000000

因此,如果您需要高分辨率结果,只需在任何地方使用 double

您混淆了两种不同类型的 floating-point 数据。 pow 函数使用 double 类型,但您的循环使用 float 类型(精度较低)。

您可以通过 或者 为您的 xpowercopyx 使用 double 类型来使结果一致变量, 通过调用 powf 函数(使用 float 类型)而不是 pow.

后面的调整(使用powf)给出了以下输出(clang-cl编译器,Windows10,64位):

3^22 = 31381059584.000000
3^22 = 31381059584.000000

并且,将 main 的第一行更改为 double x = 3, power = 1, copyx; 会得到以下结果:

3^22 = 31381059609.000000
3^22 = 31381059609.000000

请注意,随着 n 的值越来越大,您的循环结果与使用 powpowf 计算的值之间出现差异的可能性越来越大库函数。在我的平台上,double 版本给出了相同的结果,直到值超出范围并变为 Infinity。但是,float 版本开始围绕 n = 55 出现分歧:

3^55 = 174449198498104595772866560.000000
3^55 = 174449216944848669482418176.000000

精度

float x = 3, power = 1; ... power = power * x形成了float个产品。

pow(x, y) 形成 double 结果,良好的实现在内部使用更广泛的数学。

OP 的循环方法在第 15 次迭代后产生四舍五入的结果。这些四舍五入慢慢地加剧了最终结果的不准确性。

316是一个26位的奇数。

float 精确编码所有奇数,直到通常为 224。较大的值都是偶数,只有 24 位有效二进制数字。

double 精确编码所有奇数,直到通常为 253.


要进行公平比较,请使用:

  • double 个对象和 pow()
  • float 个对象和 powf().

对于大幂,pow(f)() 函数肯定会提供比循环更好的答案,因为与循环方法相比,此类函数通常使用内部扩展精度和管理良好的舍入。