std::fabs(a*b)与std::fabs(a) * std::fabs(b)之间的区别。

问题描述 投票:7回答:1

我正在处理一些数字代码,我在看编译器的输出。有一种特殊的情况让我觉得很奇怪。

在实数中,它认为 abs(a) * abs(b) = abs(a * b). 我希望在浮点数中也能保持同样的效果,但是优化既不是用clang也不是用g++,我想知道我是否漏掉了一些微妙的区别。然而,优化既不是由clang也不是由g++来执行,我想知道我是否错过了一些微妙的区别。然而这两个编译器都意识到 abs(abs(a) * abs(b)) = abs(a) * abs(b).

这里是相关的代码。

#include<cmath>

double fabsprod1(double a, double b) {
    return std::fabs(a*b);
}
double fabsprod2(double a, double b) {
    return std::fabs(a) * std::fabs(b);
}
double fabsprod3(double a, double b) {
    return std::fabs(std::fabs(a) * std::fabs(b));
}

这是gcc-10.1(写这篇文章时的稳定版)和-O3在godbolt的编译器中的混乱输出: https:/godbolt.orgzZEFPgF

值得注意的是,即使使用-Ofast,据我所知,它对允许的变换更为宽松,也没有进行这种优化。

正如 @Scheff 在评论中指出的,double 和 float 不是实数。但我也看不出浮点数类型的转角情况,比如得到Infinity或NaN作为参数,会产生不同的输出。

c++ g++ compiler-optimization clang++ cmath
1个回答
4
投票

我相信我已经找到了一个反例。我把它作为一个单独的答案发布,因为我认为这与整数的情况完全不类似。

在我考虑的案例中,我忽略了可以改变浮点运算的进位模式。有问题的是,GCC似乎忽略了这一点,当他(我猜)在编译时优化 "已知 "量时。考虑以下代码。

#include <iostream>
#include <cmath>
#include <cfenv>

double fabsprod1(double a, double b) {
    return std::fabs(a*b);
}
double fabsprod2(double a, double b) {
    return std::fabs(a) * std::fabs(b);
}

int main() {
        std::fesetround(FE_DOWNWARD);
        double a  = 0.1;
        double b = -3;
        std::cout << std::hexfloat;
        std::cout << "fabsprod1(" << a << "," << b << "): " << fabsprod1(a,b) << "\n";
        std::cout << "fabsprod2(" << a << "," << b << "): " << fabsprod2(a,b) << "\n";
#ifdef CIN
        std::cin >> b;
#endif
}

输出不同,取决于我是否用以下方式编译

g++ -DCIN -O1 -march=native main2.cpp && ./a.out

g++ -O1 -march=native main2.cpp && ./a.out

值得注意的是,只需要O1(我认为是完全可靠的)就可以改变输出,而这种方式在我看来是不合理的。

使用-DCIN时,输出是

fabsprod1(0x1.999999999999ap-4,-0x1.8p+1): 0x1.3333333333334p-2
fabsprod2(0x1.999999999999ap-4,-0x1.8p+1): 0x1.3333333333333p-2

没有-DCIN,输出为

fabsprod1(0x1.999999999999ap-4,-0x1.8p+1): 0x1.3333333333334p-2
fabsprod2(0x1.999999999999ap-4,-0x1.8p+1): 0x1.3333333333334p-2

编辑:Peter Cordes (感谢你的评论)指出,这个令人惊讶的结果是由于我没有告诉GCC尊重四舍五入模式的改变。通过使用下面的命令进行构建,实现了预期的结果。

g++ -O1 -frounding-math -march=native main2.cpp && ./a.out

(在我的机器上,O2和O3也能工作)。


-1
投票

与intregrals相似的问题是(int) :

#include<cmath>

int fabsprod1(int a, int b) {
    return std::abs(a*b);
}
int fabsprod2(int a, int b) {
    return std::abs(a) * std::abs(b);
}
int fabsprod3(int a, int b) {
    return std::abs(std::abs(a) * std::abs(b));
}

结果是(使用您的选项) -O3 -std=c++2a -march=cannonlake):

fabsprod1(int, int):
        mov     eax, edi
        imul    eax, esi
        cdq
        xor     eax, edx
        sub     eax, edx
        ret
fabsprod2(int, int):
        mov     eax, edi
        cdq
        xor     eax, edx
        sub     eax, edx
        mov     edx, esi
        sar     edx, 31
        xor     esi, edx
        sub     esi, edx
        imul    eax, esi
        ret
fabsprod3(int, int):
        mov     eax, edi
        cdq
        xor     eax, edx
        sub     eax, edx
        mov     edx, esi
        sar     edx, 31
        xor     esi, edx
        sub     esi, edx
        imul    eax, esi
        ret

https:/godbolt.orgztf3nZN

这与你说的 "实浮点 "数字相矛盾。

总的来说,你不应该指望编译器为你快捷的解决数学问题。也就是说,一些优化是可能的。请提供文档或类似的例子来说明你看到优化的地方。

© www.soinside.com 2019 - 2024. All rights reserved.