Geo*_*giD 47 c floating-point type-conversion implicit-conversion
有人可以给我一个解释,为什么我得到两个不同的数字,分别是.在图14和15中,作为以下代码的输出?
#include <stdio.h>
int main()
{
double Vmax = 2.9;
double Vmin = 1.4;
double step = 0.1;
double a =(Vmax-Vmin)/step;
int b = (Vmax-Vmin)/step;
int c = a;
printf("%d %d",b,c); // 14 15, why?
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我希望在两种情况下都能获得15分,但似乎我缺少一些语言的基础知识.
我不确定它是否相关,但我在CodeBlocks中进行测试.但是,如果我在某些在线编译器中键入相同的代码行(例如这个),我会得到两个打印变量的答案为15.
chu*_*ica 42
...为什么我得到两个不同的数字......
除了通常的浮点问题之外,到b和的计算路径以c不同的方式到达. c首先将值保存为double a.
double a =(Vmax-Vmin)/step;
int b = (Vmax-Vmin)/step;
int c = a;
Run Code Online (Sandbox Code Playgroud)
C允许使用更宽的类型计算中间浮点数学.检查FLT_EVAL_METHODfrom 的值<float.h>.
除了赋值和强制转换(删除所有额外的范围和精度),...
-1不确定;
0仅根据类型的范围和精度评估所有操作和常量;
1评估操作和类型的常量
float和double向范围和精度double类型,评估long double操作和常数的范围和精度long double类型;2评估所有
long double类型的范围和精度的操作和常量 .C11dr§5.2.4.2.29
OP 报道2
通过保存商double a = (Vmax-Vmin)/step;,精确被强制,double而int b = (Vmax-Vmin)/step;可以计算为long double.
这种微妙的差异来自(Vmax-Vmin)/step(计算可能为long double)被保存为double与剩余的a 相比long double.一个为15(或刚好在上面),另一个在15以下. int截断将这个差异放大到15和14.
在另一个编译器上,由于FLT_EVAL_METHOD < 2浮点特征或其他浮点特征,结果可能都是相同的.
int从浮点数转换为严重,数字接近整数.往往更好round()或lround().最佳解决方案取决于具体情况.
cma*_*ter 24
这确实是一个有趣的问题,这就是硬件中正好发生的事情.这个答案给出了精确计算IEEE double精度浮点数的精确度,即52位尾数加一个隐含位.有关表示的详细信息,请参阅维基百科文章.
好的,所以你先定义一些变量:
double Vmax = 2.9;
double Vmin = 1.4;
double step = 0.1;
Run Code Online (Sandbox Code Playgroud)
二进制中的相应值将是
Vmax = 10.111001100110011001100110011001100110011001100110011
Vmin = 1.0110011001100110011001100110011001100110011001100110
step = .00011001100110011001100110011001100110011001100110011010
Run Code Online (Sandbox Code Playgroud)
如果对这些位进行计数,您将看到我已将第一位设置为右侧的52位.这正是您的计算机存储的精度double.请注意,值step已经四舍五入.
现在你对这些数字进行一些数学计算.第一个操作,减法,得到精确的结果:
10.111001100110011001100110011001100110011001100110011
- 1.0110011001100110011001100110011001100110011001100110
--------------------------------------------------------
1.1000000000000000000000000000000000000000000000000000
Run Code Online (Sandbox Code Playgroud)
然后除以step,由编译器四舍五入:
1.1000000000000000000000000000000000000000000000000000
/ .00011001100110011001100110011001100110011001100110011010
--------------------------------------------------------
1110.1111111111111111111111111111111111111111111111111100001111111111111
Run Code Online (Sandbox Code Playgroud)
由于四舍五入step,结果略低于此15.与以前不同,我没有立即舍入,因为这恰好是有趣的事情发生的地方:你的CPU确实可以存储比a更精确的浮点数double,因此舍入不会立即发生.
因此,当您将结果(Vmax-Vmin)/step直接转换为a时int,您的CPU只会在小数点之后切断位(这是隐式double -> int转换由语言标准定义的方式):
1110.1111111111111111111111111111111111111111111111111100001111111111111
cutoff to int: 1110
Run Code Online (Sandbox Code Playgroud)
但是,如果首先将结果存储在double类型的变量中,则会进行舍入:
1110.1111111111111111111111111111111111111111111111111100001111111111111
rounded: 1111.0000000000000000000000000000000000000000000000000
cutoff to int: 1111
Run Code Online (Sandbox Code Playgroud)
这正是你得到的结果.
Ste*_*mit 21
"简单"的答案是那些看似简单的数字2.9,1.4和0.1都在内部表示为二进制浮点,而在二进制中,数字1/10表示为无限重复的二进制分数0.00011001100110011 ... [ 2] .(这类似于十进制中的1/3最终为0.333333333 .......)转换回十进制,这些原始数字最终变为2.8999999999,1.3999999999和0.0999999999.当你对它们进行额外的数学计算时,那些.0999999999的数据往往会激增.
然后另外一个问题是你计算某些东西的路径 - 无论是将它存储在特定类型的中间变量中,还是"一次性"计算它,这意味着处理器可能使用内部寄存器,其精度高于类型double - 最终可能会产生重大影响.
最重要的是,当你将一个double背面转换为一个时int,你几乎总是想要圆,而不是截断.这里发生了什么(实际上)一个计算路径给你15.0000000001,截断到15,而另一个给你14.999999999,一直截断到14.
在分析FLT_EVAL_METHOD == 2的C程序时分析了一个等价问题.
如果FLT_EVAL_METHOD==2:
double a =(Vmax-Vmin)/step;
int b = (Vmax-Vmin)/step;
int c = a;
Run Code Online (Sandbox Code Playgroud)
b通过计算long double表达式然后将其截断为a来计算int,而对于c它的评估long double,将其截断为double然后将其截断int.
因此,两个值都不是使用相同的过程获得的,这可能会导致不同的结果,因为浮动类型不提供通常的精确算术.