pow(a / b,x)与pow(b / a,-x)的数值精度

时间:2019-04-09 06:28:52

标签: c++ c pow

pow(a/b,x)pow(b/a,-x)之间的准确性是否存在差异? 如果存在,将小于1的数字提高为正数或将大于1的数字提高为负数会产生更准确的结果吗?

编辑:假设使用x86_64处理器和gcc编译器。

编辑:我尝试使用一些随机数进行比较。例如:

printf("%.20f",pow(8.72138221/1.761329479,-1.51231)) // 0.08898783049228660424
printf("%.20f",pow(1.761329479/8.72138221, 1.51231)) // 0.08898783049228659037

因此,看起来似乎存在差异(尽管在这种情况下很小),但是也许知道算法实现的人可以评论最大差异是什么,在什么条件下。

4 个答案:

答案 0 :(得分:2)

这是回答此类问题的一种方法,以查看浮点的行为方式。这不是分析此类问题的100%正确方法,但它给出了一个总体思路。

让我们生成随机数。计算浮点精度的v0=pow(a/b, n)v1=pow(b/a, -n)。并以双精度计算ref=pow(a/b, n),并将其舍入为浮点数。我们使用ref作为参考值(我们假设double的精度比float精度高得多,因此我们可以相信ref被认为是可能的最佳值。对于大多数IEEE-754而言,这都是正确的的时间)。然后求和v0-refv1-ref之间的差。差异应使用“ v和ref之间的浮点数的数量”来计算。

请注意,结果可能取决于abn的范围(以及随机生成器的质量。如果确实很差,则可能会产生偏差)结果)。在这里,我使用了a=[0..1]b=[0..1]n=[-2..2]。此外,该答案假设浮点/双除法/功率的算法是相同的,具有相同的特征。

对于我的计算机,总和的差异为:2604828 2603684,这意味着两者之间没有显着的精度差异。

这是代码(请注意,此代码假设IEEE-754算术):

#include <cmath>
#include <stdio.h>
#include <string.h>

long long int diff(float a, float b) {
    unsigned int ai, bi;
    memcpy(&ai, &a, 4);
    memcpy(&bi, &b, 4);
    long long int diff = (long long int)ai - bi;
    if (diff<0) diff = -diff;
    return diff;
}

int main() {
    long long int e0 = 0;
    long long int e1 = 0;
    for (int i=0; i<10000000; i++) {
        float a = 1.0f*rand()/RAND_MAX;
        float b = 1.0f*rand()/RAND_MAX;
        float n = 4.0f*rand()/RAND_MAX - 2.0f;

        if (a==0||b==0) continue;

        float v0 = std::pow(a/b, n);
        float v1 = std::pow(b/a, -n);
        float ref = std::pow((double)a/b, n);

        e0 += diff(ref, v0);
        e1 += diff(ref, v1);
    }

    printf("%lld %lld\n", e0, e1);
}

答案 1 :(得分:2)

通常,具有正幂的形式稍好一些,尽管过少可能不会产生实际效果。具体情况可以区分。例如,如果 a b 是2的幂,则应将其用作分母,因为除法将没有舍入误差。

在这个答案中,我假设IEEE-754二进制浮点的舍入舍入为偶数,并且涉及的值在浮点格式的正常范围内。

abx赋值 a b x ,以及pow的一种实现,它计算出最接近理想数学值的可表示值(实际实现通常不是那么好),pow(a/b, x)会计算( a / b •(1+ e 0 )) x •(1+ e 1 ),其中 e 0 是除法中出现的舍入误差,而 e < sub> 1 是pow中发生的舍入误差,pow(b/a, -x)计算( b / a •(1+ e 2 )))- x •(1+ e 3 ),其中 e 2 e 3 是该除法的舍入误差,而{ {1}}。

每个错误 e 0 ... e 3 位于区间[− u / 2, u / 2],其中 u 是浮点格式的最低精度(ULP)单位1。 (符号[ p q ]是包含从 p q 的所有值(包括 > p q 。)如果结果位于binade的边缘附近(浮点指数发生变化,并且有效数接近于1),则下界可能是- u / 4。目前,我不会分析这种情况。

重写,它们是( a / b x •(1+ e < / em> 0 x •(1+ e 1 )和( a / b x •(1+ e 2 < / sub>)- x •(1+ e 3 )。这表明(1+ e 0 x 与(1+ e 2 - x 。 1+ e 1 与1+ e 3 也是不同的,但这只是最后的舍入。 [我可能会在稍后考虑对此进行进一步分析,但现在忽略它。]

考虑(1+ e 0 x 和(1+ e 2 - x 。第一个表达式的电位值跨度[[1- u / 2) x ,(1+ u / 2) x ],而第二个跨度[(1+ u / 2) x ,(1− u / 2) - x ]。当 x > 0时,第二个间隔比第一个间隔长:

  • 第一个的长度为(1+ u / 2) x −(1+ u / 2) x
  • 秒的长度是(1 /(1− u / 2)) x −(1 /(1+ < em> u / 2)) x
  • 将后者乘以(1-−em> u 2 / 2 2 x 产生((1− u 2 / 2 2 )/(1− u / 2)) x -((1− u 2 / 2 2 )/(1 + u / 2)) x =(1+ u / 2) x -(1+ u / 2) x ,即第一个间隔的长度。
  • 1− u 2 / 2 2 <1,因此(1− u 2 / 2 2 x <1表示正 x
  • 由于第一长度等于第二长度乘以小于1的数字,因此第一间隔较短。

因此,在指数形式为正的形式上,其潜在结果的时间间隔较短的意义更好。

尽管如此,这种差异很小。如果在实践中无法观察到我,我不会感到惊讶。同样,人们可能会关注错误的概率分布,而不是潜在错误的范围。我怀疑这也会有利于正指数。

答案 2 :(得分:2)

  

...在pow(a/b,x)pow(b/a,-x)之间...将小于1的数字提高为正幂或将大于1的数字提高为负幂会产生更准确的结果吗?

以哪种划分更为弓形。


考虑z = x y = 2 y * log2(x)

大约:y * log2(x)中的错误z的值放大,从而形成z中的错误。 x y x中的错误非常敏感。 |log2(x)|越大,关注越大。

在OP的情况下,pow(a/b,p)pow(b/a,-p)通常都具有相同的y * log2(x)和相同的z,并且在z中具有相似的错误。这是关于x, y的形成方式的问题:


a/bb/a通常都具有+/- 0.5 * unit in the last place的相同误差,因此两种方法类似的错误

然而,选择值分别为a/bb/a的情况下,一个商将更为精确,并且该方法的误差较小,pow()

pow(7777777/4,-p)pow(4/7777777,p)更准确。

由于缺乏对分割错误的保证,因此一般规则适用:无重大差异。

答案 3 :(得分:0)

对于像您这样的舍入错误评估,使用一些多精度库(例如Boost.Multiprecision)可能会很有用。然后,您可以比较各种精度的结果,例如,使用以下程序:

#include <iomanip>
#include <iostream>
#include <boost/multiprecision/cpp_bin_float.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>

namespace mp = boost::multiprecision;

template <typename FLOAT>
void comp() {
  FLOAT a = 8.72138221;
  FLOAT b = 1.761329479;
  FLOAT c = 1.51231;

  FLOAT e = mp::pow(a / b, -c);
  FLOAT f = mp::pow(b / a, c);

  std::cout << std::fixed << std::setw(40) << std::setprecision(40) << e << std::endl;
  std::cout << std::fixed << std::setw(40) << std::setprecision(40) << f << std::endl;
}

int main() {
  std::cout << "Double: " << std::endl;
  comp<mp::cpp_bin_float_double>();
  td::cout << std::endl;

  std::cout << "Double extended: " << std::endl;
  comp<mp::cpp_bin_float_double_extended>();
  std::cout << std::endl;

  std::cout << "Quad: " << std::endl;
  comp<mp::cpp_bin_float_quad>();
  std::cout << std::endl;

  std::cout << "Dec-100: " << std::endl;
  comp<mp::cpp_dec_float_100>();
  std::cout << std::endl;
}

在我的平台上,其输出为:

Double: 
0.0889878304922865903670015086390776559711
0.0889878304922866181225771242679911665618

Double extended: 
0.0889878304922865999079806265115166752366
0.0889878304922865999012043629334822725241

Quad: 
0.0889878304922865999004910375213273866639
0.0889878304922865999004910375213273505527

Dec-100: 
0.0889878304922865999004910375213273881004
0.0889878304922865999004910375213273881004

实时演示:https://wandbox.org/permlink/tAm4sBIoIuUy2lO6

对于double,第一个计算更准确,但是,我猜这里不能得出任何一般性结论。


此外,请注意,您的输入数字不能使用IEEE 754双精度浮点类型(它们都不是)精确地表示。问题是,您是否关心使用最接近表示的确切数字进行计算的准确性。