【问题标题】:How can I check if std::pow will overflow double如何检查 std::pow 是否会溢出双倍
【发布时间】:2013-09-04 08:37:28
【问题描述】:

我有一个处理任意大网格的函数。由于使用std::pow,我需要计算另一个数字的幂的网格是否适合双精度数。如果不能,我想采用不同的分支并使用 gnu 多精度库而不是正常的。

有没有快速查看的方法:

int a = 1024;
int b = 0-10;

if(checkPowFitsDouble(a, b)) {
    long c = static_cast<long>(std::pow(a, b)); //this will only work if b < 6
} else {
    mpz_t c; //yada yada gmp
}

我完全被 checkPowFitsDouble 难住了;也许有一些我不知道的数学技巧。

【问题讨论】:

  • 如果您仍要使用例如特殊情况下的 GMP,为什么不删除那些特殊情况并使用 GMP?
  • @JoachimPileborg 我需要这个功能非常快。如果我可以在某些情况下避免 gmp,我需要这样做。大多数时候 gmp 不会被使用,所以我想利用它。
  • 您可以尝试最大双打的对数并进行正确的除法,但这可能会减慢速度
  • @doctorlove 谢谢!我现在正在为此开发一个更好的基准。
  • 理论上你可以使用任何对数,以 10 为底、自然或 17。但最合乎逻辑的底数是 2,而且你真的不关心该日志的小数部分。这意味着您拥有非常高效的frexp 函数。

标签: c++ gmp pow


【解决方案1】:

检查幂运算是否会溢出的常用技巧是使用对数。这个想法是基于这些关系:

a^b &lt;= m &lt;=&gt; log(a^b) &lt;= log(m) &lt;=&gt; b * log(a) &lt;= log(m) &lt;=&gt; b &lt;= log(m) / log(a)

例如,

int a = 1024;

for (int b = 0; b < 10; ++b) {
    if (b * std::log(a) < std::log(std::numeric_limits<long>::max())) {
        long c = std::pow(a, b);
        std::cout << c << '\n';
    }
    else
        std::cout << "overflow\n";
}

这给出了这个想法。我希望这会有所帮助。

【讨论】:

  • 这很有趣,感谢顶部的精彩解释。我显然需要更多地查看日志。
【解决方案2】:

除非它对性能特别重要,否则建议尝试一下。如果它溢出双精度,std::pow 将返回HUGE_VAL。因此类似于:

double val = std::pow(a, b);
if(val != HUGE_VAL) {
    ...
} else {
    mpz_t c; 
    //...
}

【讨论】:

  • 不幸的是,它对性能至关重要.. 感谢您的建议。如果一切都失败了,我可能会回到这个。也许我应该研究一下 std::pow 是如何确定这一点的。
  • @jett 毫无疑问,它让硬件确定它(至少在通常的系统上)。在 IEEE 机器上,HUGE_VAL 应该扩展到正无穷大,如果值太大,在计算 pow 时自然会得到。
  • @JamesKanze 感谢您的解释。我查看了标题,并认为深入研究 gcc 内部结构对我来说有点过头了。这是有道理的。
  • @jett 当然,由于标准要求设置errno,因此会有一些额外的代码来检查状态标志(并且可能确保它们在计算之前被重置)。
  • @jett - 尽管它对性能至关重要,但这似乎仍然是要走的路。除非故障非常普遍,否则每次需要调用pow(正如公认的答案所建议的那样)计算一个对数比简单地调用pow 和检查(偶尔的)故障要多得多。
【解决方案3】:

您可以在测试中轻松使用反向函数:

if ( std::log( DBL_MAX ) / std::log( a ) < b ) {
    //  std::pow( a, b ) will not overflow...
} else {
}

做 pow 可能一样好,看看是否 成功:

errno = 0;
double powab = std::pow( a, b );
if ( errno == 0 ) {
    //  std::pow succeeded (without overflow)
} else {
    //  some error (probably overflow) with std::pow.
}

仅计算std::log( a ) 不会节省太多时间。 (std::log( DBL_MAX ) 当然是一个常数,所以只需要 计算一次。)

【讨论】:

  • 这是目前最快的,非常简洁。比目前我的测试中检查 pow 的结果快 2 1/2 倍。
  • 所以实际上.. 经过一番测试,这似乎没有给我正确的答案?我最初尝试了一个大数字和小数字,但这接受 a=1024, b=102 没有下溢(103 它开始说溢出)。
  • pow( 1024., 102. ) 不会溢出,至少对于 IEEE double。 (请注意,这在数学上与 Cassio Neri 使用的测试基本相同。我认为这样写,溢出的风险较小,但我没有详细分析。)
  • @jett - 对于失败的情况,计算日志可能更快,但这并不是分析的结束。如果失败很少见,那么每次计算的额外日志最终会花费更多,而不是像 James 的第二个建议那样简单地事后检查失败。一般来说,浮点专家(我不是)似乎更喜欢完全运行计算,直到最后才检查错误。这就是我们有 NaN 的原因:许多错误会产生 NaN,并且 NaN(在大多数情况下)会在其余计算中持续存在,因此您可以在之后检查失败。
【解决方案4】:

使用以 10 为底的对数,您可以推断出 std:pow(a, b) 具有 log(a^b) = b log a 数字。然后您可以轻松地查看它是否适合双精度数,该双精度数可以容纳高达 DBL_MAX 的值。

但是,此方法执行的计算不仅仅是计算一次a^b。首先使用 GMP 衡量一个版本,看看检查溢出是否真的提供了任何可衡量和可重现的好处。

编辑:忽略这个,std::pow 已经返回一个适当的值,以防发生溢出,所以使用它。

【讨论】:

    猜你喜欢
    • 2020-08-15
    • 1970-01-01
    • 2012-07-16
    • 1970-01-01
    • 2011-12-02
    • 1970-01-01
    • 1970-01-01
    • 2012-11-19
    • 2018-12-09
    相关资源
    最近更新 更多