C++标准允许这种浮点数行为吗?

8
在以下代码中:
#include <cstdint>
#include <cinttypes>
#include <cstdio>

using namespace std;

int main() {
    double xd = 1.18;
    int64_t xi = 1000000000;

    int64_t res1 = (double)(xi * xd);

    double d = xi * xd;
    int64_t res2 = d;

    printf("%" PRId64"\n", res1);
    printf("%" PRId64"\n", res2);
}

使用v4.9.3版本的g++ -std=c++14,目标为32位Windows,在此情况下我得到以下输出:

1179999999
1180000000

这些值可以不同吗?
我期望即使编译器在计算xi * xd时使用比double更高的内部精度,它也应该始终如一地这样做。浮点转换中的精度损失是“实现定义的”,并且此计算的精度也是“实现定义的” - [c.limits] / 3表示应该从C99导入FLT_EVAL_METHOD。我希望不允许在一行上使用与另一行不同的精度来计算xi * xd。
注意:这是一个有意的C++问题,而不是C问题 - 我相信这两种语言在此领域有不同的规则。

1
请注意,您需要将 xi * xd 计算为 long double 并从 long double 转换为整数才能出错;1e9 * 1.18 最终与 1.18e9 相差约 1/4 ulp。 - tmyklebu
相关链接:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323#c127 - Nemo
2个回答

3
即使编译器在计算xi * xd时使用比double更高的内部精度,它也应该始终如一地这样做。无论是否需要(下面讨论),显然并非如此:Stackoverflow上充斥着人们看到类似计算因没有明显原因而在同一程序中改变的问题。C++标准草案n3690表示(我强调):浮点操作数的值和浮点表达式的结果可以用比类型所需更高的精度和范围来表示;但类型不会因此而改变。62。转换和赋值运算符仍必须执行其描述在5.4、5.2.9和5.17中的特定转换。
所以 - 同意M.M.的评论,与我先前的编辑相反 - 是使用(double)转换的版本必须四舍五入为64位double - 显然在问题中记录的运行中是>= 1180000000 - 然后截断为整数。没有62)的更普遍情况使编译器有自由不在其他情况下提前舍入。

[c.limits]/3说FLT_EVAL_METHOD应该从C99导入。也就是说,我希望不允许在一行上使用与另一行不同的精度来对xi * xd进行计算。

检查cppreference page

无论FLT_EVAL_METHOD的值如何,任何浮点表达式都可以被合并,即,计算时可以假定所有中间结果具有无限的范围和精度(除非#pragma STDC FP_CONTRACT关闭)

正如tmyklebu所评论的那样,它继续说:

Cast和赋值会去除任何多余的范围和精度:这模拟了将扩展精度FPU寄存器中的值存储到标准大小内存位置的操作。
这与标准中的“62)”部分相符。
M.M.评论:
STDC FP_CONTRACT似乎没有出现在C++标准中,而且我不清楚C99的行为到底有多大程度上被“导入”。
在我查看的草案中没有出现。这表明C++不保证其可用性,留下了上述默认值:“任何浮点表达式都可以缩短”,但我们知道根据M.M.的评论、标准和cppreference引用,(double)转换是一个例外,强制舍入到64位。
上述C++标准草案说到< cfloat >:
内容与标准C库头文件相同。另请参见:ISO C 7.1.5、5.2.4.2.2、5.2.4.2.1。
如果其中一个C标准要求使用STDC FP_CONTRACT,那么它更有可能被C++程序使用,但我还没有调查过支持情况的实现。

标准库中的哪一行对应于cppreference网站上的那一行? - M.M
在您引用的段落正下方,cppreference页面上写道:“强制转换和赋值会剥离任何多余的范围和精度:这模拟了将来自扩展精度FPU寄存器的值存储到标准大小内存位置的操作。” 这与您所写的相矛盾。 - tmyklebu
“STDC FP_CONTRACT” 在 C++ 标准中似乎没有出现,而且我也不清楚 C99 行为被“导入”的程度到底是多少。 - M.M
@tmyklebu:感谢您指出这一点-这使得cppreference与标准保持一致,我已经查阅并引用了。干杯。 - Tony Delroy
@M.M.同意根据C++标准,STDC FP_CONTRACT很难理解。你可以尝试并查看编译器是否支持它并解决问题,或者接受结果可能会有所不同。 - Tony Delroy
@M.M. 看起来 FP_CONTRACT 的默认值也是实现定义的 - http://en.cppreference.com/w/c/preprocessor/impl - 因此,除非您明确指定,否则无法可移植地推断出您将获得哪种行为。很痛苦。 - Tony Delroy

2
根据FLT_EVAL_METHOD不同,xi * xd可能会使用比double更高的精度计算。如果xi非常大以至于不能用double精确表示,那么我甚至不确定编译器是否被允许将其精确转换为long double - 可能不行,因为这种转换发生在FLT_EVAL_METHOD覆盖的任何事情之前。没有要求必须始终使用更高的精度。
有两个地方必须转换为double:在强制类型转换(double)处和在分配给double的位置处。曾经有过某些gcc版本,在值已经“正式”成为double(如此处的xi * xd)时,会“优化”掉对double的强制类型转换,即使实际上它是更高的精度;那种“优化”一直都是一个bug,因为强制类型转换必须进行转换。
因此,您可能遇到了这个bug,其中double的强制类型转换未执行(如果该bug仍然存在),您可能遇到了不一致使用更高精度的情况,如果FLT_EVAL_METHOD允许,则是合法的,并且您甚至可能会遇到FLT_EVAL_METHOD根本不允许的情况下,不一致使用更高精度的情况,这又是一个bug(不是不一致性,而是首先使用更高精度)。

@M.M 所以这是编译器的一个错误,因为确实有 [expr.static.cast] 5.2.9\4:“这种显式转换的效果与执行声明和初始化然后使用临时变量作为转换结果相同。” 变量 d 就像一个临时变量,但表达式的效果不同。 - Eugene Zavidovsky
哦,这真是太有趣了!这个 - Eugene Zavidovsky

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