2015-12-21 35 views
8

在下面的代码:C++标准是否允许这种浮点行为?

#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 

难道这些值允许不同?

我预计,即使编译器使用更高的内部精度比doublexi * xd计算,就应该始终如一地做到这一点。浮点转换中的精度损失为实现定义的,并且此计算的精度为实现定义的 - [c.limits]/3表示FLT_EVAL_METHOD应该从C99导入。 IOW我预计它不应该被允许在一条线上使用不同于另一条线上的xi * xd的精度。

注意:这是故意的C++问题而不是C问题 - 我相信这两种语言在这方面有不同的规则。

+1

请注意,您需要将'xi * xd'作为'long double'并将'long double'中的整数转换为这个错误; 1e9 * 1.18比1.18e9大约1/4 ulp。 – tmyklebu

+0

相关:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323#c127 – Nemo

回答

3

即使编译器使用更高的内部精度比双十一为XD *的计算,它应该这样做始终

无论是否需要(见下文),这显然不发生:Stackoverflow充满了来自同一个程序之内的人看到类似似乎的计算结果没有表面上的原因的问题。

C++标准n3690草案说(重点煤矿):

的浮动操作数的和值浮动表达式的结果可能在更高的精度和范围比由所要求的来表示类型;类型不会因此而改变。62

62)演员和作业操作员仍必须执行5.4,5.2.9和5.17中所述的特定转换。

所以 - 与MM的评论,违反了我先前的编辑协议 - 这是与(double)投版本必须四舍五入到64位double - 这显然恰好是> = 11.8在问题中记录的运行 - 在截断为整数之前。更一般的情况不会使编译器自由地在另一种情况下不提前四舍五入。

[c.limits]/3表示FLT_EVAL_METHOD应该从C99导入。 IOW我期望它不应该被允许在一条线上使用不同于另一条线上的xi * xd的精度。

检查cppreference page

不管FLT_EVAL_METHOD的值,任何浮点表达可以收缩,即,计算为如果所有中间结果具有无穷大的范围和精度(除非的#pragma STDC FP_CONTRACT关闭)

由于tmyklebu意见,继续:

投射和分配剥离任何无关范围和精度:这模拟了将扩展精度FPU寄存器中的值存储到标准大小内存位置的操作。

最后一点与标准的“62”部分一致。

M.M.评论:

STDC FP_CONTRACT似乎并没有出现在C++标准,也很不清楚我到底为C99行为是“进口”的程度

没有出现在选秀我在看。这表明C++并不保证它的可用性,所以上面提到的默认值“任何浮点表达式都可能会收缩”,但我们知道每M.M.评论以及(double)转换之上的标准和cppreference引用是强制舍入到64位的例外。的<cfloat>

上述C++标准草案说:

的内容是一样的标准C库头。 另见:ISO C 7.1.5,5.2.4.2.2,5.2.4.2.1。

如果这些C类标准的一个必需STDC FP_CONTRACT有更多的它是便携式由C++程序中使用的机会,但我还没有调查的支持实现。

+0

该标准的哪一行对应该cppreference行? –

+0

在引用的段落的正下方,cppreference页面显示“投射和分配剥离任何无关的范围和精度:这模拟了将扩展精度FPU寄存器中的值存储到标准大小内存位置的操作。这在你写的东西面前飞翔。 – tmyklebu

+0

'STDC FP_CONTRACT'似乎没有出现在C++标准中,而且我也不清楚C99行为是在什么程度上被“导入”的。 –

2

根据FLT_EVAL_METHOD,xi * xd的计算精度可能高于double。如果xi太大以至于无法完全以双精度表示,那么我甚至不确定是否允许编译器精确地将其转换为long double或许不是 - 因为该转换在之前发生的任何事情都可能发生在之前FLT_EVAL_METHOD。没有要求必须始终如一地使用更高的精度。

有两个地方必须转换为double:在转换点(double)和赋值到double处。如果某个值已经“正式”加倍(如xi * xd这里),即使实际上它的精确度更高,也会有gcc版本将其转换为double值。该“优化”总是一个错误,因为一个铸造必须转换。

所以,你可能遇到了这个错误,其中铸造翻一番未执行(如果bug仍然存在),你可能会遇到使用不一致的更高的精度,这是合法的,如果FLT_EVAL_METHOD允许它,当FLT_EVAL_METHOD根本不允许它时,甚至可能会遇到不一致的更高精度的使用,这又会是一个错误(不是不一致性,而是首先使用更高的精度)。

+0

@MM所以这是编译器中的一个错误,因为确实存在** [expr.static.cast] 5.2.9 \ 4 **:“这种显式转换的效果与执行声明相同并初始化,然后使用临时变量作为转换的结果。“变量'd'就像是一个临时的,但表达式的效果是不同的。 –

+0

噢,[那]有多有趣(https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323#c92)! –

相关问题