mysql數據精度丟失問題深入探討


  不要盲目的說float和double精度可能發生丟失,而是說在存取時因為精度不一致會發生丟失,當然這里的丟失指的是擴展或者截斷了,丟失了原有的精度。decimal是好,但不是說不會發生任何精度丟失。如果問題看得不深入,總會以偏概全。

    我們知道,mysql存儲小數可以使用float、double、decimal等。這些類型存儲的小數精度都比較高。我們平時應用最多的就是兩位小數點,所以,這些類型都是可以滿足的。

    那么數據精度丟失是如何表現的呢?到底是什么原因呢?

    當我們不知道原因的時候,總會認為到處都是問題,找不到關鍵點。在應用的時候,總是不踏實,唯唯諾諾的。搞清楚緣由,找到問題的關鍵點,然后加以深入理解,方可給自己一個定心丸。

    精度變化不管是哪種類型,都會發生。網上言傳float、double精度可能丟失,decimal精度不會丟失,所以建議decimal來存儲金額值。但是,沒有指明確切緣由,只是大概是這么回事,其實離真相還差得遠。

    在mysql中,我們用【小數數據類型(總長度,小數點長度)】來表示小數的總長度和小數點后面的長度,如decimal(m,n)。n就是小數點后面的 數字個數。float(m,n)、double(m,n)含義差不多,都是定義長度和精度的。既然都定義了精度,為什么還會發生所謂的精度丟失問題呢?

    查閱網上的說法,要么是沒有設置精度位數,要么就是設置的精度和存儲時的精度不一致,要么就是mysql存的,用其他數據庫引擎查詢。基本是這三類可以概括各種情況。

    這三種有什么共性和差異性呢?如果能夠將這三種情況抽象為一個共同的模型,我們就找到了精度丟失問題的關鍵。而不是盲人摸象般的局部就事論事,以偏概全了。

    沒有設置精度就是使用默認的精度,此時的策略就是,盡可能保證精度,因此一般使用最高精度存儲數據的。如果設置數據類型指定了精度,那么存儲數據時就按照設置的精度來存儲。第三種暫時不討論,先把前面兩種說完。

    假設默認精度為6個小數位或設置為6位,我們存儲小數一般是:2 1.1 2.11 32.214 41.4513 5.21452 6.214522 7.1421457,這里有8個不同的小數位的數,在存儲到數據庫時會發生什么問題?

    使用默認精度,數據庫用6位來存儲。所以前面6個數字,精度都不夠6位。當然,存儲時如果是float和double,那么會盡可能以近似的值存儲,以保 證精度。所以,結果可能是:2.000001 2.110005 32.213999 41.451301 5.214519。如果用的是decimal,則結果是:2 1.1 2.11 32.214 41.4513 5.21452 。從這個結果可以看到,decimal的存儲結果沒有精度丟失問題。因為decimal內部以字符形式存儲小數,屬於准確存儲。而float和 double等則屬於浮點數數字存儲,所以沒有辦法做到准確,只能盡可能近似。這也是大家選用decimal的原因,也認為decimal精度不會丟失的 原因。

    這個結果對,但不全對。數據的精度丟失發生在存儲和獲取這一瞬間。所以,存儲和獲取這兩個方面也就是精度發生變化的時刻。這里的精度丟失發生在存儲時。這里舉例使用6位精度來兼容不同的精度數值。還有6位精度和大於6位精度的值我們還沒有討論,現在來討論一下。

    是不是6位精度值存入6位精度類型精度就不會丟失了呢?必須不能丟失。如果丟失了,誰還敢用這樣的類型呢?這樣的類型還有什么意義呢?所以,6.214522存入6位小數的float和double是不會丟失小數精度的,取出來的數還是6.214522。

    也就是說,一個小數存入相同的精度的數據類型時,精度是不會丟失的。

    另一種情況,當7或更多位精度的數字存入6位精度類型字段時,會發生什么?結果會發生四舍五入。四舍五入的結果就是匹配字段的數據類型的精度長度。此時精 度也會丟失。不管內部如何處理,我們得到的數據是經過四舍五入的。但是有一點可以確定,我們在讀取取舍后的數字時,是固定的。雖然浮點數存儲的不是確切的 數值,但是在你指定的精度長度條件下,存取都是確定的一個數值。而發生精度變化的就是數值的精度和字段的精度長度不匹配,從而發生數值擴展精度和截斷精度 問題,這也就是浮點數精度不准確的問題。

    有人說,用浮點數存儲在查詢時會發生無法匹配的問題。例如,存入的數據為15.21,查詢的時候用15.21竟然匹配不到15.21這條記錄。然而這是個 人沒有理解數據庫浮點數處理問題。我反正是可以准確匹配查詢的。在設置兩位精度的字段數據類型時,存取數據得到的都是兩位精度的值,在查詢時用兩位的數值 自然也是可以匹配到的。匹配不到的情況是發生在精度不對等的情況,就是前面討論的擴展了精度和截斷了精度的情況。這本身就是數據庫使用不正確的問題,並不 能說明float和double不能用在金額上的存儲。然而網上並沒有人對此深入思考過。不是轉載就是重復同一個觀點,缺乏思考。

    而第三個問題,mysql數據庫使用其他數據庫引擎來查詢,得到的結果會發生精度丟失。這個精度丟失的原因,就可能是不同的數據庫引擎對浮點數的精度擴展 和截斷處理策略不一致,而且,存儲時策略也不一致。所以導致精度會出現各種變化。這種問題也就是催生decimal類型的出現。我們前面看到的 decimal是可以確切存儲小數的精度的。因為在存儲的時候會將小數以字符串存儲,就不會再發生精度的擴展問題。但是decimal依然會發生精度截斷 問題。如果decimal指定精度為2位小數,存入的是這樣的值:12.123,你覺得結果如何?當然還是會發生四舍五入。結果就是12.12,然而 12.12以字符串形式存入了數據庫。此后,12.12始終都是12.12,表現出來的是小數,然而內部是字符串形式存儲,所以,小數精度不會再發生變化 了。我們不管以什么精度來獲取這個值,都是12.12,而且,不管是一般數據庫引擎讀取到的也都是12.12,所以decimal才是大家推薦使用的金額 存儲類型。

    我們應該知道,數據庫作為中心,驅動着各種軟件的發展,然而在實現上,我們可能用Java方式來訪問MySQL,也可能是.Net方式訪問mysql,不 同的語言的mysql引擎是有差別的。但是不管怎么樣,decimal類型都是可以確定精度的,不會發生四舍五入。所以在各個軟件中都表現良好。

    所以,不要盲目的說float和double精度可能發生丟失,而是說在存取時因為精度不一致會發生丟失,當然這里的丟失指的是擴展或者截斷了,丟失了原 有的精度。decimal是好,但不是說不會發生任何精度丟失。如果問題看得不深入,總會以偏概全。decimal占用的字節多。不過在計算的時候,數據 是准確的。而float和double在計算前一定要對取到的數據的精度做處理,確保和存儲的精度一致。而存儲的精度也和需要的精度一致,不要長也不要 短。

  

參考:https://www.jianshu.com/p/c81edc59546c

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM