为什么浮动这样做?

时间:2013-04-08 02:41:28

标签: c floating-point

我正在玩普通的老C.我只是计算一些便士,然后在一定次数的迭代中以指数方式增加数量。在这个过程的最后,我想要在屏幕上以美元和美分显示大量的美分/便士。

我的输出电流如下:

"You have 805306365 pennies"

我试图通过将新变量定义为float来将此数字转换为美元:

float totalD = total / 100.00;

输出显示8053063.50。为什么这个int的划分造成了15美分的损失? 我试图通过“printf”显示它,如果这有帮助:

printf("You have %.2f dollars", totalD);

我知道我可以将我的美分转换为字符串并尝试格式化它,但我很喜欢,但我很困惑为什么浮点数会这样做。任何人都可以告诉我为什么会发生这种情况以及如何处理它?<​​/ p>

3 个答案:

答案 0 :(得分:5)

更新了准确性和完整性

您遇到的是浮点数中 precision 的(相当常见)问题。在许多编译器中,浮点数只有32位长,并且它们使用一定数量的这些位(23)作为其“有效数”(有时称为“尾数”),1符号位和8位用于“指数“(在1.23E45之类的数字中,”1.23“是有效数字,”45“是指数。在二进制中,你实际上有相对较少的(24)个零和零可用,所以你的精度不足十进制表示法中的数字6或7)。

为了说明这种精度损失,我写了几行代码:

#include <stdio.h>

int main(){
  float fpennies;
  long lpennies, ii;

  lpennies = 805306360;
  for(ii = 0; ii< 100; ii++) {
      fpennies = lpennies + ii;
      printf("%ld pennies converted to float: %.0f fpennies\n",ii+ lpennies, fpennies);
  }
}

这产生了许多类型

的行
805306360 pennies converted to float: 805306368 fpennies 
805306361 pennies converted to float: 805306368 fpennies 
805306362 pennies converted to float: 805306368 fpennies 
... 
805306400 pennies converted to float: 805306368 fpennies 
805306401 pennies converted to float: 805306432 fpennies

正如您所看到的那样,在805306400附近,只需将long递增一位,float代表64代表printf("%.0f %08x", fpennies, *(unsigned int*)(&fpennies)); !最好通过稍微查看浮点数的二进制表示来解释这一点。

首先,这是32位浮点数的组织(来自http://upload.wikimedia.org/wikipedia/commons/d/d2/Float_example.svg):

enter image description here

我们可以使用一些显式的转换来获取数字的十六进制表示:

805306368 4e400000
805306432 4e400001

对于我们之前看到的跨越跳跃的两个值,结果为

1

正如您所看到的,有效数字的“最低有效位”增加了0x4e40 = 0100 1110 0100 0000 in binary ,但是指数意味着乘数为64.为什么64?好吧,让我们扩大前几位:

1001 1100 = 0x9c = 156

由于最高位是符号位(0 =正),接下来的8位是指数,这就是指数

value = (-1)^(sign bit) * (1 + sum(i=1 to 23, bit(23-i)*2^(-i))) * 2^(exponent - 127)

现在,从浮点中的位到其值(参见http://en.wikipedia.org/wiki/Single-precision_floating-point_format)的规则是

1

在这种情况下,最低有效位(位0)中2^(-23) * 2^( 156 - 127 ) = 2^6 = 64的更改会增加64

因此,对于这个数量的数字,可以表示的最小步长为long int dollars, cents, pennies; ... dollars = pennies / 100; cents = pennies % 100; ,如输出中所示。

如果你想解决这个问题,你可以做一些在Vaughn的答案中建议的事情 - 使用代表便士的长整数,并使用整数数学(除法,模数)来获得“全部美元,全部美分”金额。

float pennies = 805306365;
printf("you have %f pennies\n", pennies);

通过这种方式,您可以在不损失精确度的情况下代表一些相当大的金钱。

在练习中,当你写作

You have 805306368 pennies

你得到了

double

如果你使用{{1}}类型,你会有更好的运气(在这种情况下)。

答案 1 :(得分:2)

我认为你会以这种方式做得更好:

printf("You have %d.%02d dollars", total/100,total%100);

答案 2 :(得分:-1)

考虑使用long int或long long来表示便士。浮动或双打不适合你想要做的事情。我建议你阅读discussion on StackOverflow