将双精度数赋值给C中的int变量的非直观结果

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评估操作和类型的常量floatdouble向范围和精度double类型,评估long double 操作和常数的范围和精度long double 类型;

2评估所有long double类型的范围和精度的操作和常量 .

C11dr§5.2.4.2.29

OP 报道2

通过保存商double a = (Vmax-Vmin)/step;,精确被强制,doubleint 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().最佳解决方案取决于具体情况.

  • 谢谢你的帮助和解释!现在,当我在在线编译器上测试`FTL_EVAL_METHOD`时,我得到了15个变量的"预期"答案,我得到了0的结果.然而,教训是像我这样的菜鸟必须要小心做这样"简单",乍一看,计算:) (2认同)

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)

这正是你得到的结果.

  • 很好,关于浮点数的每个问题都应该有这样的具体例子. (2认同)

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.

另见C FAQ列表中的问题14.4a.


Jea*_*nès 6

分析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.

因此,两个值都不是使用相同的过程获得的,这可能会导致不同的结果,因为浮动类型不提供通常的精确算术.