C中double到int的转换、四舍五入

在一般的PC平台下sizeof(double)=8,即它是8字节的,同时它是双精度浮点型,而float是单精度的。先把它们的基础知识复习一下,有些有点忘了。

1.double类型的数在C中的正确输入、输出怎么表示?

%lf表示双精度,而%f表示单精度。

2.浮点类型数据在内存中的表示

在没有认真学习之前的一种错误认识:认为64位的浮点数,有一部分用于表示整数部分,一部分用于表示小数部分。之所以是错误的是因为没有理解浮点所表示的意思:小数点的位置位置根据需要而浮动。即小数点的位置不是固定的。

该类型数据在内存中表示方式与整型的不同。它采用科学计数法的方式表示,即内存中由符号域、小数域和指数这三部分组成(但这三个域在内存中具体占多少位,并没有统一的规定,与不同平台有关)。如3.424在内存中的表示就类似如下:

+  .34241
符号小数域指数域

这里假设指数是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);

a's parent index is:2
b's parent index is:2
Press any key to continue
对负数的处理,还是按数学上的大小进行的。即floor(-4.6)=-5. ceil(-3.4)=-3;
	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,2147483647
2147483647=(0111 1111 1111 1111 1111 1111 1111 1111)
-2147483648= 1000 0000 0000 0000 0000 0000 0000 0000
	double 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=-2147483648.000000,a2=2147483648.000000
d=-2147483648,d2=-2147483648
Press any key to continue
注意:a 并直接赋值,由于像1,2,344等整数,默认情况下是int类型的,而
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



	
	char c=352;//1 0110 0000
	int dd=c;
	printf("dd=%d\n",dd);
结果为它的低8位,为96
_________________________________________________________________________________________________________________________

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的精度非常高,大约有好几十层楼那么高吧...够用了)



do-and-want


评论 2 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

灵7

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值