注册 登录
编程论坛 C++教室

简单的问题,但自己不会

gukai1991 发布于 2011-03-24 23:52, 2158 次点击
#include<iostream>
#include<conio.h>
using namespace std;

int main()
{
    double a=3.3,b=1.1;
    int i=a/b;
    cout<<i<<endl;
    getch();
    return 0;
}

为什么最后i=2呢
29 回复
#2
rjsp2011-03-25 08:19
你应该把无关的代码移除掉

#3
rjsp2011-03-25 08:23
程序代码:
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    double a=3.3,b=1.1;

    double c = a/b;
    cout << setprecision(18) << c << endl;

    int i = c;
    cout << i << endl;

    return 0;
}
输出为
2.9999999999999996
2

如果你想问 3.3/1.1 为什么不等于 3,那你把 3.3/1.1的结果转化为int干吗?
如果你想问 2.9999999999999996 转化为int为什么不是3,那你应该这么问:
    int i = 2.9;
    结果i为什么是2,而不是3?
#4
剑池2011-03-25 11:19
我运行的结果是2,没错呀
#5
pangding2011-03-25 14:27
3楼 说的比较犀利。

我也经常发现现在问问题的人不是很虚心。提的问题往往也不在点上。明明只有一点不会的地方,非用一大堆障眼法虎住。
#6
lisanhu12011-03-25 14:54
double型在计算的时候很容易出现微小的误差的,所以一般不要把double型的计算结果进行强制类型转换
还有在C++中我尝试了下将int换成double没设置精度的情况下是会输出3的我也没弄太明白,呵呵,可能是输出的时候进行了优化吧
#7
lisanhu12011-03-25 15:01
回复 5楼 pangding
我不赞同你的说法,这是个小问题,可是你能讲下为什么吗,如果不进行结果的精度限制用g++编译的结果也会出现3啊
这是对于C++中问题的研究,作为一个新人,提出这个问题也无可厚非,你觉得这个没用,说用障眼法,他的问题还不明确吗,两个double型运算正常输出结果也会是3的,可是换在这里却出了问题,那么这是否告诉我们尽量少用double型运算呢,在C中尽管是两个double型运算也会出现精度的问题,这不是个很好的问题吗,你现在对于C++学的很好了,难道就忘记了初学C++时的艰难吗,对于每个问题都能够坚持不懈地研究这是对于C++的热爱难道不值得提倡吗,你是否从新人的角度想过问题啊
#8
pangding2011-03-25 18:09
我说的障眼法是通病,不是针对楼主。
往往很多人一问问题就是一堆代码,然后让我们从中几十行甚至几百行中找出一行错误。
还有人明明知道可能是某些語句的问题,但非要再写些不太相关的话,让我们费解。有的人问的问题甚至就是在考验我们的眼力。
当然我承认,有经验的人调试語句的的速度会比一般的人快。但问问题的人也应该尽量为帮助他的人节约时间。


比如这个,我觉得再怎么新手也应该知道是 double 转 int 出的问题。为什么他不指出?
如果是我问同样的问题,至少会问成 3 楼那个样子。

这个问题不是我不知道,只是我觉得解释着比较麻烦,而且新手不一定能听懂。
如果楼主会浮点数的表示,和10进制到2进制的转化,这个问题应该会迎忍而解。
不过对于新人来说,我觉得某清楚这个问题没什么太大帮助。只要知道浮点数表示在精度上有誤差就够了。
要想解释就得用我提的那两个知识。大部分事情都是这样:原理很简单,但具体问题比较麻烦(不是难)。比如解 N 元线性方程组,上过中学的人都会解,但你肯定不愿意解超过三元的某个方程

答这个问题要花好多时间,等我晚上有空答答吧。
我答问题一般比较有耐心,而且都会很细致,熟悉我的人大约多少会有一点了解。只是看了2楼的帖子比较有同感而已。


请楼主和其他人不要誤解,我并不是针对某一个说的楼上那些话。

#9
gukai19912011-03-25 18:40
回复 3楼 rjsp
sorry,这个是课本上的题目,我只是照抄,而且。。。您解释一下也不是很复杂吧
#10
gukai19912011-03-25 18:44
回复 7楼 lisanhu1
感谢,太简单的问题,我都会自己想的,不会那么无聊为了发帖而发帖
但就是这个不明白,究竟是怎样产生的误差
感谢这位朋友
#11
gukai19912011-03-25 18:50
回复 7楼 lisanhu1
对于这个问题以后注意,以后直接指出问题所在
#12
pangding2011-03-25 22:36
说了今天晚上来答,不能食言~

首先你得知道怎么把十进制转成二进制。

手算时要分别转整数和小数。

整数是不断除二,反向取余数:
比如 13 = 8 + 4 + 1,即 2^3 + 2^2 + 2^0。所以二进制是 1101
可用竖式算得,如下:
    1 | 13 (13 除以2 商6 余1)
    0 | 6
    1 | 3
    1 | 1        // 修正修正,原先写的是 2,挺明显的一个错误。谢谢 shamoor 指出。
      | 0
算法是,不断除二,把商写在下面,余数写在左边。(这个不太好打,你就凑合着看把,反正会算就行)
然后从下往上看就是 1101

小数部分是不断乘二,取整数部分。
比如 0.8125 = 0.5 + 0.25 + 0.0625 = 1/2 + 1/4 + 1/16,所以写成二进制是 0.1101
用竖式算是:
    1 | 0.8125 (0.8125 乘以2得1.625,整数部分是1,小数部分是0.625)
    1 | 0.625
    0 | 0.25
    1 | 0.5
      | 0
算法是,不断乘二,把整数部分写在左边,小数部分写在下面。到0为止。
这个是从上往下看的。所以就是 0.1101

此时就有 13.8125 写成二进制就是 1101.1101。


先会这个,再看你那个题。

咱们先算 3.3
3 = 2+1,即是二进制 11,没得说。
0.3:
    0 | 0.3
    1 | 0.6
    0 | 0.2
    0 | 0.4
    1 | 0.8
    1 | 0.6
    ... ...
0.6 又出现了。

这就比较有意思了,虽然 0.3 在十进制里很平常,但在二进制里是无限循环小数。
所以 3.3 是 11.0100110011001....
同理可以算得 1.1 是 1.0001100110011001....

这两个都是无限小数,在计算机这种二进制的世界里显然无法精确表示。


IEEE 标准里(好像是754标准吧,是个规定浮点运算相关的标准)规定了浮点数的表示方法。
统一用类似科学计数法的方法表示:m * 2^n。
m 称为尾数(Fraction),n 称为阶数或者指数(exponent),还有一个符号位来表示正负。
并要求要调整指数,比如 1101.1101 * 2^0 要写成 1.1011101 * 2^3。此时指数就是 3,当然要用二进制的11;尾数是 1.1011101。由于这么一移,整数部分必然是用1开头的,没必要多浪费一位。所以尾数部分不储存首1。即 m 是 1011101。

当然情况没这么简单,还可能有的数是 0.001,就得写成 1 * 2^-3。这么一来,指数又得有正负,为了避免这个,指数还要用偏移码的方法表示。
标准里规定的比较详细。我这说的叫正规浮点数,还有非正规的。还有用来表示正负无穷大的(INF),表示无意义值的(NaN),无意义值又有很多种类,用于表示不同的错误,等等。我不详细介绍了。


具体来说:
32位(4字节的,相当于 float)的浮点数,有1位符号位,8位指数位,23位尾数位。
64位(8字节的,如double),有1位符号,11位指数,52位尾数。
好像标准里还有16位的,我记不得了。有兴趣的自己查。

既然尾数有限,就存不下无限小数,存不下的值就抛弃了。
至于你问为什么除出来是 2.99999 这个其实可以算,但我实在不想算了。
乘除法的机器算法用的是位移加减,你可以自己手算。查下资料,把那52位数写出来,先指数对齐,再位移最多不超过52次,做减法,就能得到。


我觉得楼主大概知道这么多,也不是那么疑惑了。这些知识在计算机组成原理里都有讲。
会不会这些,对 C 这种高级语言来说不是那么太重要了。同样的算法在不同的编译器上,不同的系统上,不同的硬件上,跑下来的值在小数点很后面的几位有点微小的偏差是很正常的事情。(虽然 GNU 的有些文档,也指出这种行为是它们软件的BUG,不过他们说了这种 BUG 改善不了)

不要太纠结。一般来说把低精度的往高数度的转,如 int 转 double 是没问题的,相反的转法一般是不可取,精度损失太大。
像这个例子,根据 2楼 提供的数据,本来 double 算下来,誤差也就是几百亿分之一,结果一转变成1了。好的编译器,应该在这种情况给中等级别以上的警告提示。

硬件方面我也只是会 intel 的 80x86 的架构。别的不是很清楚,不过这个工程标准规定的东西,肯定什么设备都一样。


[ 本帖最后由 pangding 于 2011-4-4 20:26 编辑 ]
#13
寒风中的细雨2011-03-25 22:58
回复 12楼 pangding
觉得这个帖子可以  置顶
单精度:32bit
双精度:64bit
扩展精度:80bit 1  16 63
   
#14
BlueGuy2011-03-26 09:40

会不会这些,对 C 这种高级语言来说不是那么太重要了。同样的算法在不同的编译器上,不同的系统上,不同的硬件上,跑下来的值在小数点很后面的几位有点微小的偏差是很正常的事情。(虽然 GNU 的有些文档,也指出这种行为是它们软件的BUG,不过他们说了这种 BUG 改善不了)
//
你答的实在是太精彩了,

前几天我还想买本IEEE 浮点数标准学习一下, 翻开一看觉得没什么必要, 还是买本  photoshop比较实用,
 
#15
wskcb2011-03-26 11:08
变量定义有问题。。
#16
wskcb2011-03-26 11:13
回复 12楼 pangding
很给力啊
#17
lucky5635912011-03-27 07:54
计算机运算又不像人那样,它把除法变成乘法,再转化为加法,最后就是2点多,舍余后等于2
#18
xyz3265474452011-03-28 13:43
我也是编程新人,看了后很受启发。
#19
白苏2011-03-28 22:14
double的运算中,3.3读出的是3.2999999999...1.1读出的是1.0999999999........
得到的double型是2.9999999999......
强转成int型便得到2喽!
#20
loverson2011-04-03 18:39
刚进入论坛就收益匪浅呐,12楼的解释真是详尽啊,相信一般的新手看了都会明白的
#21
shamoor2011-04-04 19:31
以下是引用pangding在2011-3-25 22:36:02的发言:

说了今天晚上来答,不能食言~

首先你得知道怎么把十进制转成二进制。

手算时要分别转整数和小数。

整数是不断除二,反向取余数:
比如 13 = 8 + 4 + 1,即 2^3 + 2^2 + 2^0。所以二进制是 1101
可用竖式算得,如下:
    1 | 13 (13 除以2 商6 余1)
    0 | 6
    1 | 3
    1 | 2
      | 0
算法是,不断除二,把商写在下面,余数写在左边。(这个不太好打,你就凑合着看把,反正会算就行)
然后从下往上看就是 1101

小数部分是不断乘二,取整数部分。
比如 0.8125 = 0.5 + 0.25 + 0.0625 = 1/2 + 1/4 + 1/16,所以写成二进制是 0.1101
用竖式算是:
    1 | 0.8125 (0.8125 乘以2得1.625,整数部分是1,小数部分是0.625)
    1 | 0.625
    0 | 0.25
    1 | 0.5
      | 0
算法是,不断乘二,把整数部分写在左边,小数部分写在下面。到0为止。
这个是从上往下看的。所以就是 0.1101

此时就有 13.8125 写成二进制就是 1101.1101。


先会这个,再看你那个题。

咱们先算 3.3
3 = 2+1,即是二进制 11,没得说。
0.3:
    0 | 0.3
    1 | 0.6
    0 | 0.2
    0 | 0.4
    1 | 0.8
    1 | 0.6
    ... ...
0.6 又出现了。

这就比较有意思了,虽然 0.3 在十进制里很平常,但在二进制里是无限循环小数。
所以 3.3 是 11.0100110011001....
同理可以算得 1.1 是 1.0001100110011001....

这两个都是无限小数,在计算机这种二进制的世界里显然无法精确表示。


IEEE 标准里(好像是754标准吧,是个规定浮点运算相关的标准)规定了浮点数的表示方法。
统一用类似科学计数法的方法表示:m * 2^n。
m 称为尾数(Fraction),n 称为阶数或者指数(exponent),还有一个符号位来表示正负。
并要求要调整指数,比如 1101.1101 * 2^0 要写成 1.1011101 * 2^3。此时指数就是 3,当然要用二进制的11;尾数是 1.1011101。由于这么一移,整数部分必然是用1开头的,没必要多浪费一位。所以尾数部分不储存首1。即 m 是 1011101。

当然情况没这么简单,还可能有的数是 0.001,就得写成 1 * 2^-3。这么一来,指数又得有正负,为了避免这个,指数还要用偏移码的方法表示。
标准里规定的比较详细。我这说的叫正规浮点数,还有非正规的。还有用来表示正负无穷大的(INF),表示无意义值的(NaN),无意义值又有很多种类,用于表示不同的错误,等等。我不详细介绍了。


具体来说:
32位(4字节的,相当于 float)的浮点数,有1位符号位,8位指数位,23位尾数位。
64位(8字节的,如double),有1位符号,11位指数,52位尾数。
好像标准里还有16位的,我记不得了。有兴趣的自己查。

既然尾数有限,就存不下无限小数,存不下的值就抛弃了。
至于你问为什么除出来是 2.99999 这个其实可以算,但我实在不想算了。
乘除法的机器算法用的是位移加减,你可以自己手算。查下资料,把那52位数写出来,先指数对齐,再位移最多不超过52次,做减法,就能得到。


我觉得楼主大概知道这么多,也不是那么疑惑了。这些知识在计算机组成原理里都有讲。
会不会这些,对 C 这种高级语言来说不是那么太重要了。同样的算法在不同的编译器上,不同的系统上,不同的硬件上,跑下来的值在小数点很后面的几位有点微小的偏差是很正常的事情。(虽然 GNU 的有些文档,也指出这种行为是它们软件的BUG,不过他们说了这种 BUG 改善不了)

不要太纠结。一般来说把低精度的往高数度的转,如 int 转 double 是没问题的,相反的转法一般是不可取,精度损失太大。
像这个例子,根据 2楼 提供的数据,本来 double 算下来,誤差也就是几百亿分之一,结果一转变成1了。好的编译器,应该在这种情况给中等级别以上的警告提示。

硬件方面我也只是会 intel 的 80x86 的架构。别的不是很清楚,不过这个工程标准规定的东西,肯定什么设备都一样。
太牛了!今天值了!
#22
yangfanconan2011-04-07 12:58
犀利点好。
#23
肖付2011-04-09 11:52
回复 21楼 shamoor
兄台犀利啊。。
膜拜,膜拜!
#24
小鱼o号2011-04-09 22:37
今天看到了,但还是说,经典啊
#25
Crystal_CL2011-04-10 10:32
俺也是新手,俺也没看懂。。。。.......
到底是咋回事啊?
#26
wangjunit2011-04-12 17:20
我运行楼主代码 结果是3
#27
守墨2011-04-15 22:31
,凑下热闹,新手···
#28
proluo2011-04-17 11:32
12楼很强大啊!
#29
xjzxylj2011-04-20 15:48
我想是数据问题吧,上面都是double,商是int。
#30
棉雨2011-04-23 00:08
我又学到一点东西。
1