将浮点值舍入为例如单精度
Round floating-point value to e.g. single precision
C 和 C++ 提供多种宽度的浮点数据类型,但未指定精度。编译器可以自由使用理想化算术来简化表达式,在计算 float
值的表达式时使用双精度,或者使用双精度寄存器来保存 float
变量或公共变量的值子表达式。
错了请指正错了,见编辑,但是把内存中的float
提升到双精度寄存器中也是合法的,所以存储一个值然后将其加载回不一定会截断位。
将数字转换为较低精度的最安全、最便携的方法是什么?理想情况下,它也应该是高效的,在 SSE2 上编译为 cvtsd2ss
。 (因此,虽然 volatile
可能是一个答案,但我更喜欢更好的答案。)
编辑: 总结一些评论和发现…
- 更广泛的中间结果精度始终是公平的游戏。
- 在 C++ 中允许表达式简化,在 C 中给定
FP_CONTRACT on
。
- 将双精度用于单精度
float
不允许(在 C 或 C++ 中)。
但是,某些编译器(尤其是 x86-32 上的 GCC)会非法忘记某些精度转换。
编辑 2: 有些人对未能缩小中间结果的一致性表示怀疑。
C11 §5.2.4.2.2/9(与答案中引用的 C99 参考文献相同)具体说明了“删除所有额外范围和精度”,因为它指定了如何进行其他计算在更广泛的精度。在几个符合要求的替代精度中,有一个是“不确定的”,对我来说这意味着没有任何限制。
C11 §7.12.2 和 §6.5/8 定义了 #pragma STDC FP_CONTRACT on
,它使编译器能够尽可能使用无限精度。
The intermediate operations in the contracted expression are evaluated as if to infinite range and precision, while the final operation is rounded to the format determined by the expression evaluation method. A contracted expression might also omit the raising of floating-point exceptions.
C++14同样明确放弃了有限精度和范围对中间结果的约束。 N4567 §5/12:
The values of the floating operands and the results of floating expressions may be represented in greater precision and range than that required by the type; the types are not changed thereby.
请注意,允许恒等式 x - x = 0
将 a + b - b + c
简化为 a + c
与使加法可交换或结合不同。 a + b + c
仍然与 a + c + b
或 a + (b + c)
不同,因为 CPU 仅提供两个加数和一个舍入结果的加法。
我不太确定我是否在这里分享你的恐惧......我尝试了这个美化的 cast-as-a-function:
float to_float(double x)
{
return (float) x;
}
当输入 Compiler Explorer 时,我得到:
to_float(double):
push rbp
mov rbp, rsp
movsd QWORD PTR [rbp-8], xmm0
cvtsd2ss xmm0, QWORD PTR [rbp-8]
pop rbp
ret
这似乎立即生成了请求的操作码 (cvtsd2ss
),我什至没有输入任何编译器选项来强制 SSE2 或任何东西。
我会说强制转换必须转换为目标类型,据我所知,编译器不能随意忽略强制转换。
您能否提供一些您认为编译器可以忽略强制转换的案例?也许代码中潜伏着某种未定义的行为,这使得编译器采取了意想不到的捷径。
C99 5.2.4.2.2p8 明确表示
assignment and cast [..] remove all extra range and precision
因此,如果您想将范围和精度限制为浮点数,只需转换为 float
,或分配给 float
变量。
您甚至可以做类似 (double)((float)d)
的事情(使用额外的括号以确保人们正确阅读它),将变量 d
限制为 float
精度和范围,然后转换它回到 double
。 (即使 d
是 double
,标准 C 编译器也不允许对其进行优化;它必须将精度和范围限制为 float
。)
我已经在实际实现中使用了它,例如Kahan summation algorithm,它可用于允许 C 编译器进行非常积极的优化,但没有失效的风险。
C 和 C++ 提供多种宽度的浮点数据类型,但未指定精度。编译器可以自由使用理想化算术来简化表达式,在计算 float
值的表达式时使用双精度,或者使用双精度寄存器来保存 float
变量或公共变量的值子表达式。
错了请指正错了,见编辑,但是把内存中的float
提升到双精度寄存器中也是合法的,所以存储一个值然后将其加载回不一定会截断位。
将数字转换为较低精度的最安全、最便携的方法是什么?理想情况下,它也应该是高效的,在 SSE2 上编译为 cvtsd2ss
。 (因此,虽然 volatile
可能是一个答案,但我更喜欢更好的答案。)
编辑: 总结一些评论和发现…
- 更广泛的中间结果精度始终是公平的游戏。
- 在 C++ 中允许表达式简化,在 C 中给定
FP_CONTRACT on
。 - 将双精度用于单精度
float
不允许(在 C 或 C++ 中)。
但是,某些编译器(尤其是 x86-32 上的 GCC)会非法忘记某些精度转换。
编辑 2: 有些人对未能缩小中间结果的一致性表示怀疑。
C11 §5.2.4.2.2/9(与答案中引用的 C99 参考文献相同)具体说明了“删除所有额外范围和精度”,因为它指定了如何进行其他计算在更广泛的精度。在几个符合要求的替代精度中,有一个是“不确定的”,对我来说这意味着没有任何限制。
C11 §7.12.2 和 §6.5/8 定义了
#pragma STDC FP_CONTRACT on
,它使编译器能够尽可能使用无限精度。The intermediate operations in the contracted expression are evaluated as if to infinite range and precision, while the final operation is rounded to the format determined by the expression evaluation method. A contracted expression might also omit the raising of floating-point exceptions.
C++14同样明确放弃了有限精度和范围对中间结果的约束。 N4567 §5/12:
The values of the floating operands and the results of floating expressions may be represented in greater precision and range than that required by the type; the types are not changed thereby.
请注意,允许恒等式 x - x = 0
将 a + b - b + c
简化为 a + c
与使加法可交换或结合不同。 a + b + c
仍然与 a + c + b
或 a + (b + c)
不同,因为 CPU 仅提供两个加数和一个舍入结果的加法。
我不太确定我是否在这里分享你的恐惧......我尝试了这个美化的 cast-as-a-function:
float to_float(double x)
{
return (float) x;
}
当输入 Compiler Explorer 时,我得到:
to_float(double):
push rbp
mov rbp, rsp
movsd QWORD PTR [rbp-8], xmm0
cvtsd2ss xmm0, QWORD PTR [rbp-8]
pop rbp
ret
这似乎立即生成了请求的操作码 (cvtsd2ss
),我什至没有输入任何编译器选项来强制 SSE2 或任何东西。
我会说强制转换必须转换为目标类型,据我所知,编译器不能随意忽略强制转换。
您能否提供一些您认为编译器可以忽略强制转换的案例?也许代码中潜伏着某种未定义的行为,这使得编译器采取了意想不到的捷径。
C99 5.2.4.2.2p8 明确表示
assignment and cast [..] remove all extra range and precision
因此,如果您想将范围和精度限制为浮点数,只需转换为 float
,或分配给 float
变量。
您甚至可以做类似 (double)((float)d)
的事情(使用额外的括号以确保人们正确阅读它),将变量 d
限制为 float
精度和范围,然后转换它回到 double
。 (即使 d
是 double
,标准 C 编译器也不允许对其进行优化;它必须将精度和范围限制为 float
。)
我已经在实际实现中使用了它,例如Kahan summation algorithm,它可用于允许 C 编译器进行非常积极的优化,但没有失效的风险。