MySQL float,double,decimal數據類型區別

1. 浮點數 (SINGLE,DOUBLE,FLOAT,REAL) 在計算機中是純數字,即由二進制來表示的數字。由於規定了長度,所有是離散形的,也就是說無法準確表示定義區間內的所有實數。 如果想了解詳細,則可以參考自己的《計算機原理》教材,或者搜索 IEEE 754。

2. DECIMAL、NUMBER, 這個從計算機角度來看,它不是數字,是一個結構。是由字符串或者DCB編碼來表示的數字。和浮點數不同,它無法直接在CPU的ALU上直接進行計算,而是需要程序來處理的。 但由於它是用字符串來表示,所以不會出現精度損失。但計算速度慢。並且一旦出現 DECIMAL*DECIMAL則同樣會產生計算誤差。

mysql中float數據類型的問題:
一、浮點數的概念及誤差問題: 浮點數是用來表示實數的一種方法,它用 M(尾數) * B( 基數)的E(指數)次方來表示實數,相對於定點數來說,在長度一定的情況下,具有表示數據範圍大的特點。但同時也存在誤差問題,這就是著名的浮點數精度問題! 浮點數有多種實現方法,計算機中浮點數的實現大都遵從 IEEE754 標準,IEEE754 規定了單精度浮點數和雙精度浮點數兩種規格,單精度浮點數用4字節(32bit)表示浮點數,格式是:1位符號位 8位表示指數 23位表示尾數 雙精度浮點數8字節(64bit)表示實數,格式是:1位符號位 11位表示指數 52位表示尾數 同時,IEEE754標準還對尾數的格式做了規範:d.dddddd...,小數點左面只有1位且不能爲零,計算機內部是二進制,因此,尾數小數點左面部分總是1。顯然,這個1可以省去,以提高尾數的精度。由上可知,單精度浮點數的尾數是用24bit表示的,雙精度浮點數的尾數是用53bit表示的,轉換成十進制: 2^24 - 1 = 16777215 2^53 - 1 = 9007199254740991 由上可見,IEEE754單精度浮點數的有效數字二進制是24位,按十進制來說,是8位;雙精度浮點數的有效數字二進制是53位,按十進制來說,是16 位。顯然,如果一個實數的有效數字超過8位,用單精度浮點數來表示的話,就會產生誤差!同樣,如果一個實數的有效數字超過16位,用雙精度浮點數來表示,也會產生誤差!對於 1310720000000000000000.66 這個數,有效數字是24位,用單精度或雙精度浮點數表示都會產生誤差,只是程度不同: 單精度浮點數: 1310720040000000000000.00 雙精度浮點數: 1310720000000000000000.00 雙精度差了 0.66 ,單精度差了近4萬億!這個結果爲什麼與例子中的差很多呢?原因是的測試用表中對字段進行了限制,實際上顯示的是mysql溢出後的值,而我這裏給出的是計算機中實際的值,如果把測試表字段精度提高到24位或以上,得到的結果就相同了。 以上說明了因長度限制而造成的誤差,但這還不是全部!採用IEEE754標準的計算機浮點數,在內部是用二進制表示的,但在將一個十進制數轉換爲二進制浮點數時,也會造成誤差,原因是不是所有的數都能轉換成有限長度的二進制數。對於測試中用到的 131072.32 這個數,其有效數字是8位,按理應該能用單精度浮點數準確表示,爲什麼會出現偏差呢?看一下這個數據二進制尾數就明白了 10000000000000000001010001...... 顯然,其尾數超過了24bit,根據舍入規則,尾數只取 100000000000000000010100,結果就造成測試中遇到的“奇怪”現象!131072.68 用單精度浮點數表示變成 131072.69 ,原因與此類似。實際上有效數字小於8位的數,浮點數也不一定能精確表示,7.22這個數的尾數就無法用24bit二進制表示,當然在數據庫中測試不會有問題(舍入以後還是7.22),但如果參與一些計算,誤差積累後,就可能產生較大的偏差。二、mysql 和 oracle中的數值類型: 發現的問題是不是隻有 mysql 存在呢?顯然不是,只要是符合IEEE754標準的浮點數實現,都存在相同的問題。 mysql中的數值類型(不包括整型): IEEE754浮點數: float (單精度) , double 或 real (雙精度) 定點數: decimal 或 numeric oracle中的數值類型: oracle 浮點數 : number (注意不指定精度) IEEE754浮點數: BINARY_FLOAT (單精度) , BINARY_DOUBLE (雙精度) FLOAT,FLOAT(n) (ansi要求的數據類型) 定點數: number(p,s) 如果在oracle中,用BINARY_FLOAT等來做測試,結果是一樣的。 因此,在數據庫中,對於涉及貨幣或其他精度敏感的數據,應使用定點數來存儲,對mysql來說是 decimal,對oracle來說就是number(p,s)。雙精度浮點數,對於比較大的數據同樣存在問題!三、編程中也存在浮點數問題: 不光數據庫中存在浮點數問題,編程中也同樣存在,甚至可以說更值得引起注意! 通過上面的介紹,浮點數的誤差問題應該比較清楚了。如果在程序中做複雜的浮點數運算,誤差還會進一步放大。因此,在程序設計中,如果用到浮點數,一定要意識到可能產生的誤差問題。不僅如此,浮點數如果處理不好,還會導致程序BUG!看下面的語句: if (x != y) { z = 1 / (x -y);} 這個語句看起來沒有問題,但如果是浮點數,就可能存在問題!再看下面的語句會輸出什麼結果: public class Test { public static void main(String[] args) throws Exception { System.out.print("7.22-7.0=" + (7.22f-7.0f)); } } 我們可能會想當然地認爲輸出結果應該是 0.22 ,實際結果卻是 0.21999979 ! 因此,在編程中應儘量避免做浮點數的比較,否則可能會導致一些潛在的問題! 除了這些,還應注意浮點數中的一些特殊值,如 NaN、+0、-0、+無窮、-無窮等,IEEE754雖然對此做了一些約定,但各具體實現、不同的硬件結構,也會有一些差異,如果不注意也會造成錯誤!四、總結: 從上面的分析,我們可以得出以下結論: 1、浮點數存在誤差問題; 2、對貨幣等對精度敏感的數據,應該用定點數表示或存儲; 3、編程中,如果用到浮點數,要特別注意誤差問題,並儘量避免做浮點數比較; 4、要注意浮點數中一些特殊值的處理。 June,浮點數問題,很容易被忽視,可能具有一定的普遍性,也許應該發給其他技術人員,以免再出現這方面的問題。

mysql中float的問題這個問題不是一個Bug,而是浮點數本身存在的侷限。原因是計算機對浮點數的表示是 M * 2 的 N 次方,其中M是尾數,N是指數,在此轉換過程中存在數據損失,因此浮點數(包括double類型)是不能精確表示所有實數的。出現的問題正是由誤差和四捨五入造成的 當float數據類型超過131072時候,插入的數據會發現不穩定情況,測試過程如下:

 mysql> desc test10;+------------+---------------+------+-----+---------+-------+| Field | Type | Null | Key | Default | Extra |+------------+---------------+------+-----+---------+-------+| floattest | float(12,2) | YES | | NULL | || doubletest | double(12,2) | YES | | NULL | || dectest | decimal(12,2) | YES | | NULL | |+------------+---------------+------+-----+---------+-------+ mysql> insert into test10 values(131071,131071,131071);Query OK, 1 row affected (0.00 sec) mysql> select * from test10;+-----------+------------+-----------+| floattest | doubletest | dectest |+-----------+------------+-----------+| 131071.00 | 131071.00 | 131071.00 |+-----------+------------+-----------+1 row in set (0.00 sec) mysql> insert into test10 values(131071.32,131071.32,131071.32);Query OK, 1 row affected (0.00 sec) mysql> select * from test10;+-----------+------------+-----------+| floattest | doubletest | dectest |+-----------+------------+-----------+| 131071.00 | 131071.00 | 131071.00 || 131071.32 | 131071.32 | 131071.32 |+-----------+------------+-----------+2 rows in set (0.00 sec) mysql> insert into test10 values(131071.68,131071.68,131071.68);Query OK, 1 row affected (0.00 sec) mysql> select * from test10;+-----------+------------+-----------+| floattest | doubletest | dectest |+-----------+------------+-----------+| 131071.00 | 131071.00 | 131071.00 || 131071.32 | 131071.32 | 131071.32 || 131071.68 | 131071.68 | 131071.68 |+-----------+------------+-----------+3 rows in set (0.01 sec) mysql> insert into test10 values(131072,131072,131072);Query OK, 1 row affected (0.00 sec) mysql> select * from test10;+-----------+------------+-----------+| floattest | doubletest | dectest |+-----------+------------+-----------+| 131071.00 | 131071.00 | 131071.00 || 131071.32 | 131071.32 | 131071.32 || 131071.68 | 131071.68 | 131071.68 || 131072.00 | 131072.00 | 131072.00 |+-----------+------------+-----------+4 rows in set (0.00 sec) mysql> insert into test10 values(131072.32,131072.32,131072.32);Query OK, 1 row affected (0.00 sec) mysql> select * from test10;+-----------+------------+-----------+| floattest | doubletest | dectest |+-----------+------------+-----------+| 131071.00 | 131071.00 | 131071.00 || 131071.32 | 131071.32 | 131071.32 || 131071.68 | 131071.68 | 131071.68 || 131072.00 | 131072.00 | 131072.00 || 131072.31 | 131072.32 | 131072.32 |+-----------+------------+-----------+5 rows in set (0.00 sec) mysql> insert into test10 values(131072.68,131072.68,131072.68);Query OK, 1 row affected (0.00 sec) mysql> select * from test10;+-----------+------------+-----------+| floattest | doubletest | dectest |+-----------+------------+-----------+| 131071.00 | 131071.00 | 131071.00 || 131071.32 | 131071.32 | 131071.32 || 131071.68 | 131071.68 | 131071.68 || 131072.00 | 131072.00 | 131072.00 || 131072.31 | 131072.32 | 131072.32 || 131072.69 | 131072.68 | 131072.68 |+-----------+------------+-----------+6 rows in set (0.00 sec) mysql> insert into test10 values(131072.66,131072.66,131072.66);Query OK, 1 row affected (0.00 sec) mysql> select * from test10;+-----------+------------+-----------+| floattest | doubletest | dectest |+-----------+------------+-----------+| 131071.00 | 131071.00 | 131071.00 || 131071.32 | 131071.32 | 131071.32 || 131071.68 | 131071.68 | 131071.68 || 131072.00 | 131072.00 | 131072.00 || 131072.31 | 131072.32 | 131072.32 || 131072.69 | 131072.68 | 131072.68 || 131072.66 | 131072.66 | 131072.66 |+-----------+------------+-----------+ mysql> insert into test10 values(1310720000000000000000.66,1310720000000000000000.66,1310720000000000000000.66);Query OK, 1 row affected, 3 warnings (0.00 sec) mysql> select * from test10;+----------------+---------------+---------------+| floattest | doubletest | dectest |+----------------+---------------+---------------+| 131071.00 | 131071.00 | 131071.00 || 131071.32 | 131071.32 | 131071.32 || 131071.68 | 131071.68 | 131071.68 || 131072.00 | 131072.00 | 131072.00 || 131072.31 | 131072.32 | 131072.32 || 131072.69 | 131072.68 | 131072.68 || 131072.66 | 131072.66 | 131072.66 || 10000000000.00 | 9999999999.99 | 9999999999.99 |+----------------+---------------+---------------+ 以上測試說明:當insert的數據範圍在+-131072(65536×2)以內的時候,float數據精度是正確的,但是超出這個範圍的數據就不穩定,沒有發現有相關的參數設置建議:將float改成double或者decimal,兩者的差別是double是浮點計算,decimal是定點計算,會得到更精確的數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章