作者Favonia (青峰好帅)
看板b97902HW
标题Re: [计程] 你不可不知道的 double 十件事
时间Thu Oct 2 21:20:43 2008
有一些容易产生误解的地方来解释一下 xD
※ 引述《iForests (森林)》之铭言:
: ‧浮点数误差‧
: 用到 double 时是无论如何都得提到浮点数误差的,不过这里只打算乱讲。简单来说
: 是这样,当你放例如 5 在变数里时,电脑有可能存 4.9999999999999999999999999999。
: 原则上是没有差别,但有时候会造成影响。使徒三是个好例子,很多人写一写可能会发现
: 印出来的东西竟然有 -0.000,其原因正是因为变数值为像 -0.00000000000002147483647
: 这种後面接了一些怪东西的值(只是随便打的)。因此输出时认为它不是 0,而是一个很
: 小很小的负数,所以取 3 位小数时印出 -0.000。
这里可能会误导人。虽然原因应该是有一个很小的负数,
但是真的有 -0 这个东西。浮点数格式有 -0 和 +0 :)
我刚才发现 Wiki 还有介绍 xD
http://en.wikipedia.org/wiki/%E2%88%920_(number)
: 解法方法很简单,我们加一个很小很小又不至於太小的数给它,例如 1e-10。
: -0.00000000000002147483647
: +0.0000000001
: ------------------------------
: 0.00000000009997852516353
: 这样一来,用 %.3lf 印出时,答案就会是 0.000 了。
这地方有一点小问题,请看下面。
: ‧数学函式‧ (使用前需先 #include <math.h>)
: 首先,π = 2.0 * acos(0.0) = 3.1415926535897932384626433832795。
: ↑这是确确实实的 0.0 而不是表情符号
: sin()、cos() 等等函数传入的参数必须使用弪度而非角度,转换方法高中数学课应
: 有提过,总之:弪度 = 角度 * π / 180。
这边似乎有点太理想了 xDDDD
上面这一行丢到编译器去执行
得出来的结果应该是
3.141592653589793
115997963468544185161590576171875... (double)
而不是
3.141592653589793
2384626433832795... (真的圆周率)
对了,也可以直接用 acos (-1) 喔!
: 推 LoganChien:#include <float.h> 之後,你可以用 DBL_MAX 代表极大 09/30 22:40
: → LoganChien:的值(double 可以表示的最大值) 09/30 22:40
: 推 LoganChien:检查二个 double 是否相同,最好用下面的公式,以减少 09/30 22:48
: → LoganChien:意外。﹝误差造成的﹞ 09/30 22:48
: → LoganChien:fabs(a - b) < DOUBLE_PRECISION_EPSILON 09/30 22:49
: → LoganChien:let DOUBLE_PRECISION_EPSILON = 1e-9. 09/30 22:49
以上是用绝对误差来实作,通常也都不会有什麽问题,
不过如果更深入了解 double 的格式的话,
就会知道 double 的误差是
相对的而不是绝对的。
意思是说,对於 1000000000 来说 1e-9 的误差比较容易发生,
但对於 0.000001 来说 1e-9 就比较不容易了。
(可参考
http://en.wikipedia.org/wiki/IEEE_754-2008 或是计概应该会教)
事实上「浮点数」在字面上就讲明小数点是「浮动」的
例如,一般我们写成 5000 和 0.0005 的两个数,
就实际上在记忆体中浮点数的储存方法来看,
应该理解成 5 * 10^3 和 5 * 10^-4 比较恰当
所以误差是相对的而不是绝对的
(附注:电脑里面用的是二进位,所以更接近 x * 2^y)
那到底要怎麽去处理浮点数的误差?
这是个很复杂的问题,端看你的目的而定。
以下这篇文章有讲如何比较两个数字是否一样:
http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
另外我在这边提供一个思考的观点,
到底要允许计算中有多大的误差可以用最後的要求来定,
例如这次的作业可能要求小数点以下三位,
如果你估计最後的答案是 1000 附近(我只是举例)
那麽你可能就要让你整个运算的
相对误差控制在 0.0000005 以内 (0.5e-6)
或是
绝对误差控制在 0.0005 以内 (0.5e-3)
这样子最後印出来时才会和正确(没有误差)的答案比较接近。
运算中的误差传递我相信普物应该有教 xDDD
大致上来说,加法比较不会让误差扩大太快 lol
该讲的讲完了,至於要怎麽写,套一句刘墉的话,「你自己决定吧!」
: → LoganChien:1. 单纯的习惯,我也有看过有人用 1e-8, 1e-10 10/01 00:11
: → LoganChien:2. (印象中) C++ 标准规定 double 最低精确度是 1e-9 10/01 00:12
我相信到这边 1 应该比较清楚了
至於 2 又要花篇幅讲了~
由於大家学的是 C,加上我没有看过最新 C++0x 的标准
所以在这里我只讲 C99 的状况
C99 的标准後面的 Annex F 有讲说
C 里面的 float, double, long double
和 IEC 60559:1989 (也就是 ANSI/IEEE 754-1985) 如何对应
简单来说,要研究 C 的 double 是怎麽运算的,可以查阅上述标准的
倍精浮点数(double-precision floating point number)
先一下历史和题外话 xD
其实除了 IEEE 754-1985 以外
还有一个 IEEE 854 的标准也在规定浮点数的运算
啊不过最近 IEEE 754-2008 (又称 IEEE 754r... 名称超多 - -|||)
把这两个旧标准都吃掉了
ANSI/IEEE 754-xxxx 和 ISO/IEC 9899:1999 (C99 的标准)
都是要$$$$$的
还好在官网上面可以下载定案前的 "draft" (非正式版,但是跟正式版很接近这样)
而且也有很多依照这些标准写的教学网站,好读好懂又免费!
好,那这些标准到底怎麽规定误差的呢?
就像之前讲的,这些标准有订出 double 在记忆体上应该怎麽储存,
格式会接近这样:
(有一些例外的状况请自己看文章:http://en.wikipedia.org/wiki/IEEE_floating-point_standard )
S 1.
XXXXXXXXX * 2^
YYYYYYY
例如
+ 1.
00010100 * 2^(
+001011)
每个亮色的地方都要花记忆体储存,而 double 短短 64 bits 就由这三部份所瓜分
S 是正负号(一个 bit 就够了),
YYYYY 的长度决定後面的指数部份可以多大多小,
而
XXXXX 的长度决定了精确度,一切的罪孽都是来自
XXXXX 不够长 -v-
double 的
YYYY 和
XXXX 都比 float 的
YYYY 和
XXXX 还多位,
所以不仅可以处理比较大的数字,数字也可以存比较多位(误差小)。
double 的
XXXX 只有 52 位~
电脑要把运算结果塞回记忆体中时,很可能就会产生误差!
预设的误差处理方法是二进位的「四舍六入五取双」
大致上是取最接近运算答案的表示方法~
讲到这边,聪明的大家应该就可以知道,
单纯因为 double 储存格式产生的相对误差大约是 2^-5X (有些小细节我不想讲了 orz)
大约是 10^-15 ~ 10^-16 左右
(也就是十进位下大约有 15 位 xD 大家可以看前面的圆周率大约第几位开始爆炸)
至於 C++ 我猜应该也是用同样的标准玩的
有兴趣欢迎补充 xDDDD
--
我在 P2 也有版喔 xD 同 ID 喵~
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 140.112.30.39
※ 编辑: Favonia 来自: 140.112.30.39 (10/02 21:31)
1F:推 godgunman:是大彼得耶!!!! 10/02 21:41
2F:推 godgunman:话说, 今天助教时间有讲IEEE-754 10/02 21:47
※ 编辑: Favonia 来自: 140.112.30.39 (10/02 21:52)
3F:推 wolfdigit: 大彼得好威!!! 10/02 21:54
4F:推 ming1053: 大彼得好威!!! 这篇讲的好清楚 10/02 22:04
5F:推 ckclark:-0今天忘了讲 10/02 22:17
6F:推 iForests:有神快拜!!!!!(拜) 10/02 22:17
7F:推 scan33scan33: 大彼得好威!!!(拜) 10/02 22:46
8F:推 anfranion:传说中的大彼得(仰望) 10/02 22:53
9F:推 rewqrewwq:感谢学长指教!(拜) 10/02 23:03
10F:推 Bakaking:娘子,快跟牛魔王出来见上帝! 10/02 23:53
11F:→ benck:推一个 字好多喔 要慢慢消化 10/03 00:07
12F:推 sa072686:有神快拜!!!!!(拜) 10/03 00:22
13F:推 LoganChien:拿香跟拜!(拜) 10/03 00:23
14F:→ LoganChien:获益良多! 10/03 00:25
※ 编辑: Favonia 来自: 140.112.30.39 (10/03 02:07)
15F:推 pigalan: 大彼得好威!!!(拜) 10/03 22:24