在一般的PC平台下sizeof(double)=8,即它是8字节的,同时它是双精度浮点型,而float是单精度的。先把它们的基础知识复习一下,有些有点忘了。
1.double类型的数在C中的正确输入、输出怎么表示?
%lf表示双精度,而%f表示单精度。
2.浮点类型数据在内存中的表示
在没有认真学习之前的一种错误认识:认为64位的浮点数,有一部分用于表示整数部分,一部分用于表示小数部分。之所以是错误的是因为没有理解浮点所表示的意思:小数点的位置位置根据需要而浮动。即小数点的位置不是固定的。
该类型数据在内存中表示方式与整型的不同。它采用科学计数法的方式表示,即内存中由符号域、小数域和指数这三部分组成(但这三个域在内存中具体占多少位,并没有统一的规定,与不同平台有关)。如3.424在内存中的表示就类似如下:
+ | .3424 | 1 |
符号 | 小数域 | 指数域 |
这里假设指数是10的幂,从而3.424=0.3424*10^1
但实际PC中小数域和指数域都是用二进制表示的,一般也以2的幂表示。
在Torbo C中float为32bit,而有效的
数字只有6-7个。而double的有效数字有15-16个
/* Note:Your choice is C IDE */
#include "stdio.h"
void main()
{
float a,b;
int d=123456789;
int d1;
a=123.456781e5;//12345678.9
d1=(int)a;
b=a+0.4;
printf("a=%f\n",a);
printf("b=%f\n",b);
printf("d=%d\n",d);
printf("d1=%d\n",d1);
}
注意:
float为32bit,而有效的
数字只有6-7个,因此
a=123.456789e5;//12345678.9,从7之后就是不确定的了。
此外编译器会提示: e: warning C4305: '=' : truncation from 'const double' to 'float'对应红色部分。
在默认情况下,4.554等小数表示为double类型的。
b=a+0.4,也是先用double运算,再把double结果给float赋值。
double类型给float\int等类型赋值时可能发生精度损失问题。
a=12345678.000000
b=12345678.000000
d=123456789
d1=12345678
Press any key to continue
3. 四舍五入的情况
3.1把float\double按格式,限制小数点个数时会发生四舍五入。
3.2把float\double强转化为int类型时,只取整数部分不会四舍五入。
#include "stdio.h"
void main()
{
float a,b;
int d,d1;
a=3.56;// warning const double to float
b=(float)3.34;//这样就不警告了。
printf("a=%f,b=%f\n",a,b);//默认情况下,小数点6位
d=(int)a;//只取整数部分而不会四舍五入。
d1=(int)(b);//只取整数部分而不会四舍五入
printf("d=%d,d1=%d\n",d,d1);
printf("m.n的格式会四舍五入:%.1f,%.1f",a,b);
}
a=3.560000,b=3.340000
d=3,d1=3
m.n的格式会四舍五入:3.6,3.3Press any key to continue
a=1234567.45443;printf("a=%f,b=%f\n",a,b);//默认情况下,小数点6位,a为 1234567. 500000,即后面的精度都是不可靠的。
3.3 如何把float或double给int赋值时,也按四舍五入的方式。即:3.4--〈3〉 4.5--〈5〉
可以采用sprintf,(奇怪在VC下没有snprinf))
float a,b;
int d,d1;
char buf[14];
a=(float)3.56;
b=(float)3.34;//这样就不警告了。
sprintf(buf,"%.0f",a);
d=atoi(buf);
sprintf(buf,"%.0f",b);
d1=atoi(buf);
printf("d=%d,d1=%d\n",d,d1);
return 0;
d=4,d1=3
Press any key to continue
3.4 如何提取小数点部分
利用到int的强制转换就只提取整数部分,若利用floor函数,看3.5
double df=3.4564233;
double df1=3.6564235;
int d;
printf("%lf\n",df);//double也默认只有6个小数
printf("%.7lf\n",df1);//可以完整输出。
d=(int)df1;//只取整数部分
df1=df1-d;
printf("%.7lf\n",df1);
3.456423
3.6564235
0.6564235
Press any key to continue
3.5 ceil \floor
在一些算法或运算中可能要用到四舍五入、向上取整┌X┐、向下取整等操作.└X┘
其中ceil()函数是天花板的意思,即向上取整。floor为地板,即向下取整。
#include <math.h> double floor( double arg ); 功能: 函数返回参数不大于arg的最大整数。 ___________________________________ double ceil(double x); 功 能: 返回大于或者等于指定表达式的最小整数
它们的参数都为double类型的,但我们经常会用int类型的数据进行操作。
如下完全二叉树中,若以1为根结点,则4,5这两个节点的双亲为[4/2] [5/2]都为向下取整。
int a=4; int b=5; int par; par=(int)floor(a/2); printf("a's parent index is:%d\n",par); par=(int)floor(b/2); printf("b's parent index is:%d\n",par);
对负数的处理,还是按数学上的大小进行的。即floor(-4.6)=-5. ceil(-3.4)=-3;a's parent index is:2 b's parent index is:2 Press any key to continue
double a=4.67; double b=-4.67; double c=4.23; double d=-4.23; double a_floor=floor(a); double b_floor=floor(b); double c_floor=floor(c); double d_floor=floor(d); double a_ceil=ceil(a); double b_ceil=ceil(b); double c_ceil=ceil(c); double d_ceil=ceil(d); printf("a_floor=%lf,b_floor=%lf\n",a_floor,b_floor); printf("c_floor=%lf,d_floor=%lf\n",c_floor,d_floor); printf("a_ceil=%lf,b_ceil=%lf\n",a_ceil,b_ceil); printf("c_ceil=%lf,d_ceil=%lf\n",c_ceil,d_ceil);
a_floor=4.000000,b_floor=-5.000000 c_floor=4.000000,d_floor=-5.000000 a_ceil=5.000000,b_ceil=-4.000000 c_ceil=5.000000,d_ceil=-4.000000 Press any key to continue
4.若double变量大于int所存储的范围时,强制转换的结果是不确定的
double a=179723554568; int d=(int)a; printf("a=%lf\n",a); printf("d=%d\n",d); return 0;
a=179723554568.000000 d=-665071864 Press any key to continue
int 32位时表示的范围为(-2^31--(2^31-1))即-2147483648,21474836472147483647=(0111 1111 1111 1111 1111 1111 1111 1111)
-2147483648= 1000 0000 0000 0000 0000 0000 0000 0000double a=2147483647+1; double a2=2147483648;//注意a2!=a int d=(int)a; int d2=(int)a2; printf("a=%lf,a2=%lf\n",a,a2); printf("d=%d,d2=%d\n",d,d2); return 0;
注意:a 并直接赋值,由于像1,2,344等整数,默认情况下是int类型的,而a=-2147483648.000000,a2=2147483648.000000 d=-2147483648,d2=-2147483648 Press any key to continue
2147483647能够用int类型存储,因此它还是按int类型计算的,但此时发生了溢出,从而它符号位变为1了,溢出后为最大的负数,再把这个负数赋值给double.
而2147483648,则超出了int的范围,则上升为long int。1000 0000 0000 0000 0000 0000 0000 0000
注意发生溢出后,是变为负的最大,而不是变为0.
0的二进制全为0,
-1的二进制全为1.
char c=128; int dd=c; printf("dd=%d\n",dd);
dd=-128 Press any key to continue
_________________________________________________________________________________________________________________________结果为它的低8位,为96char c=352;//1 0110 0000 int dd=c; printf("dd=%d\n",dd);
5.19.9变成了19.89999
在十进制中小数有些是无法完整用二进制表示的。它们只能用有限位来表示,从而在存储时可能就会有误差。十进制的小数采用乘2取整法进行计算,取掉整数部分后,剩下的小数继续乘以2,直到小数部分全为0.
如0.125变成二进制为
0.125*2=0.25 .....取整0
0.25*2=0.5 ........取整0
0.5*2= 1.0 ………取整1
0.0*2=0
所以0.125的二进制为0.001
如我们有
而0.9*2=1.8.....取整1
0.8*2=1.6…....取整1
0.6*2=1.2.......取整1
0.2*2=0.4........取整0
0.4*2=0.8...取整0
0.8*2=1.6....取整1
………………………………
从而它是一个循环,不可能出现小数部分为0的情况。从而在内存中表示时就会小于0.9
double a=19.9;
int b=(int)(19.9*100);
printf("b=%d\n",b);
printf("a=%lf\n",a);
b=1989
a=19.900000
Press any key to continue
采用
采用VC6.0可以观察到a的实际并不是19.9 (F9设置断点,F5执行)
a 19.899999999999999
b 1989
printf("%lf\n",a)中由于发生了四舍五入才会变成19.9。
但19.9*100是按二进制乘法进行运算(而不是我们十进制)的,而(int)的强制又只取整数部分,它不会四舍五入。
但19.9*10=199
double a=19.9;
int b=(int)(19.9*10);
printf("b=%d\n",b);
printf("a=%.6lf\n",a);
return 0;
b=199
a=19.900000
Press any key to continue
VCwatch中观察到的为
a
19.899999999999999
b
199
个人觉得所得的结果与乘以19.9的那个数是有关的,在二进制中,用不同的数进制运算,所得的误差可能会变放大,如
(a+b)*c=ac+bc,这里假设b为误差,则它被放大了c倍,而bc与它相加则可能在某个特殊的地方就产生了致使的错误。
如果认真去算的话,要以二进制去推导。
这个问题的发现是在do_and_want中的博客看到的,背景是这样的:
某商品的定价为19.9元,由于在数据处理过程中把所有的端口转化成整数处理(商品价格只有到分),所以把它乘以100后再进行处理,从而意思地发现商品少了一分钱。若数量大的话,损失也是可观的。
下面是来看网络中的分析:
19.9 作为 Double 类型表示,二进制形式是:
1 00000000110 011111001100110011001100110011001100110011001100110
(注意中间的两个空格,如果你不知道啥意思,就去查查double的内存表示形式吧)
但是19.9 * 100 由于是二进制运算的结果是
1 00000010011 111000101111111111111111111111111111111111111111111
由于后面有n个11111所以我猜测可能发生了溢出被计算机舍去了.
于是这个数字比 1990少那么一点点(可能是 1989.99999999...)
但是你的取整操作却直接截断了后面的数字,于是成了1989
至于你说9.9 29.9为什么不那样,那就是可能没有发生溢出了(不要以10进制的思维来猜测二进制)
别的语言你只能通过保留更高的精度并且四舍五入来实现,而C#为了支持金融运算,独家引入变态的Decimal类型,于是你的问题现在可以通过decimal解决了(decimal的精度非常高,大约有好几十层楼那么高吧...够用了)
(注意中间的两个空格,如果你不知道啥意思,就去查查double的内存表示形式吧)
但是19.9 * 100 由于是二进制运算的结果是
1 00000010011 111000101111111111111111111111111111111111111111111
由于后面有n个11111所以我猜测可能发生了溢出被计算机舍去了.
于是这个数字比 1990少那么一点点(可能是 1989.99999999...)
但是你的取整操作却直接截断了后面的数字,于是成了1989
至于你说9.9 29.9为什么不那样,那就是可能没有发生溢出了(不要以10进制的思维来猜测二进制)
别的语言你只能通过保留更高的精度并且四舍五入来实现,而C#为了支持金融运算,独家引入变态的Decimal类型,于是你的问题现在可以通过decimal解决了(decimal的精度非常高,大约有好几十层楼那么高吧...够用了)