何时使用`std::hypot(x,y)`而不是`std::sqrt(x*x + y*y)`?

39

std::hypot函数文档中指出:

计算x和y的平方和的平方根,在计算过程中不会出现过度溢出或下溢。

我难以想象什么情况下应该使用std::hypot而不是简单的sqrt(x*x + y*y)

下面的测试表明,std::hypot比朴素计算大约慢20倍。

#include <iostream>
#include <chrono>
#include <random>
#include <algorithm>

int main(int, char**) {
    std::mt19937_64 mt;
    const auto samples = 10000000;
    std::vector<double> values(2 * samples);
    std::uniform_real_distribution<double> urd(-100.0, 100.0);
    std::generate_n(values.begin(), 2 * samples, [&]() {return urd(mt); });
    std::cout.precision(15);

    {
        double sum = 0;
        auto s = std::chrono::steady_clock::now();
        for (auto i = 0; i < 2 * samples; i += 2) {
            sum += std::hypot(values[i], values[i + 1]);
        }
        auto e = std::chrono::steady_clock::now();
        std::cout << std::fixed <<std::chrono::duration_cast<std::chrono::microseconds>(e - s).count() << "us --- s:" << sum << std::endl;
    }
    {
        double sum = 0;
        auto s = std::chrono::steady_clock::now();
        for (auto i = 0; i < 2 * samples; i += 2) {
            sum += std::sqrt(values[i]* values[i] + values[i + 1]* values[i + 1]);
        }
        auto e = std::chrono::steady_clock::now();
        std::cout << std::fixed << std::chrono::duration_cast<std::chrono::microseconds>(e - s).count() << "us --- s:" << sum << std::endl;
    }
}

所以我正在寻求指导,什么情况下必须使用std::hypot(x,y)才能获得正确的结果,而不是更快的std::sqrt(x*x + y*y)

澄清: 我正在寻找适用于xy为浮点数的答案。例如比较:

double h = std::hypot(static_cast<double>(x),static_cast<double>(y));

致:

double xx = static_cast<double>(x);
double yy = static_cast<double>(y);
double h = std::sqrt(xx*xx + yy*yy);

1
我认为你也应该将其与std::abs(std::complex<double>(x,y))进行比较,就像std::hypot页面中所示。 - phuclv
4
虽然有些晚,但是 cppreference 的文档也作为一个注释(因此不能保证符合标准)提到:“实现通常保证精度小于 1 ulp(最后一位单位)。如果使用 round to nearest 设置,则 x*x + y*y 可能会失去一些精度。这意味着 std::sqrt(x*x+y*y) 可能会有一两个误差。需要使用比 std::sqrt(x*x+y*y) 更好的算法才能获得该保证。(续) - David Hammen
3
更糟的是,假设你已经对四舍五入进行了修改?那肯定会妨碍实现次位以下的精度。hypot必须设置舍入以达到这种精度,然后将舍入恢复回您的设置。这种设置和重置舍入行为是std:hypot(x,y)std::sqrt(x*x+y*y)慢得多的原因。 - David Hammen
1
我很喜欢这个问题,但我仍然想知道性能差异的原因。https://dev59.com/LW865IYBdhLWcg3wivKj讨论了这个问题。具体来说,https://dev59.com/LW865IYBdhLWcg3wivKj#3764993为我解释了它。 - stringsn88keys
2
sqrt函数具有这样的特性,即输入中存在的任何相对误差在平方根结果中减半 - 即sqrt(x*(1+e)) = sqrt(x)*(1+e/2) - (而平方会使其加倍),因此从上面看起来平方根方法并不像它看起来那么糟糕。 hypot的额外运行时间部分是由于选择不同的方法以获得额外的精度和避免溢出/下溢的步骤,但也包括inf的特殊测试(例如,hypot(inf,NaN) -> inf,而另一种方法会给你NaN)。 - greggo
1个回答

42

你引用的文档已经给出了答案。

计算x和y的平方和的平方根,在计算的中间阶段没有不必要的溢出或下溢

如果x * x + y * y溢出,那么如果你手动进行计算,你将得到错误的答案。但是,如果您使用std::hypot,它保证中间计算不会溢出。

你可以在这里看到这种差异的例子。

如果您使用的数字不会溢出您平台上相关的表示法,请放心使用原始版本。


3
std::hypot函数的参数必须是浮点类型,如果你在这里使用整型参数,那么它也会被提升为浮点类型。 - phuclv
我在我的问题中可能没有表达清楚。我只关心当使用double精度参数时两者之间的区别。因为整数溢出是由类型提升处理的,而不是由hypot函数处理的。但是,即使进行了类型提升,hypot中的某些东西使其比朴素实现慢20倍。为什么会这样,它何时能够拯救我的后背? - Emily L.
正如 这个例子 所示,在使用双精度参数时没有差异。具体而言,我正在寻找会出现差异的 double x,y - Emily L.
@TartanLlama 看来我们有所进展 :) 所以当中间结果超过 1.7e308 时,就会出现差异。如果你编辑你的答案包括这一点,并跳过整数溢出的讨论,我将很乐意接受它。 - Emily L.
2
对于给定的平台,当然可以。更一般地说,当它超过 std::numeric_limits<double>::max() 时。 - TartanLlama
显示剩余2条评论

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