從sql關聯數據異常看精度丟失問題

目錄

背景

代碼測試:

測試結果:

結果說明:

原因分析:

float與double的範圍和精度

解決方案:


背景

bigint和string做join的時候 會先都隱式轉換成double在join,可能會由於精度丟失導致join的結果不符合預期,導致sql不符合預期

代碼測試:

 set hive.mapred.mode=nonstrict;
select 
	9000000000000000000 = '9000000000000000000', 
  9000000000000000000 = '9000000000000000030',
	9000000000000000030 = '9000000000000000030',
  9000000000000000030 = '9000000000000000000',
  cast('9123456123451234230' as double),
  cast('9000000000000000030' as double),
  cast(9000000000000000030 as double),
  cast(9123456123451234200 as double),
  cast(9123456123451234230 as double)
  FROM table
  LIMIT 300

 

測試結果: 

(9000000000000000000 = '9000000000000000000')	(9000000000000000000 = '9000000000000000030')	(9000000000000000030 = '9000000000000000030')	(9000000000000000030 = '9000000000000000000')	CAST('9123456123451234230' AS double)	CAST('9000000000000000030' AS double)	CAST(9000000000000000030 AS double)	CAST(9123456123451234200 AS double)	CAST(9123456123451234230 AS double)
true	true	true	true	9.1234561234512343E18	9.0E18	9.0E18	9.1234561234512343E18	9.1234561234512343E18
true	true	true	true	9.1234561234512343E18	9.0E18	9.0E18	9.1234561234512343E18	9.1234561234512343E18
true	true	true	true	9.1234561234512343E18	9.0E18	9.0E18	9.1234561234512343E18	9.1234561234512343E18
true	true	true	true	9.1234561234512343E18	9.0E18	9.0E18	9.1234561234512343E18	9.1234561234512343E18
true	true	true	true	9.1234561234512343E18	9.0E18	9.0E18	9.1234561234512343E18	9.1234561234512343E18
true	true	true	true	9.1234561234512343E18	9.0E18	9.0E18	9.1234561234512343E18	9.1234561234512343E18
true	true	true	true	9.1234561234512343E18	9.0E18	9.0E18	9.1234561234512343E18	9.1234561234512343E18
true	true	true	true	9.1234561234512343E18	9.0E18	9.0E18	9.1234561234512343E18	9.1234561234512343E18
true	true	true	true	9.1234561234512343E18	9.0E18	9.0E18	9.1234561234512343E18	9.1234561234512343E18

 

 

 

結果說明:

圖開頭結果表現都是相等的,因爲最後一位有精度丟失。圖後面可以清楚看到小數點後第16位精度丟失,9.1234561234512343E18 裏面 9.1234561234512343E18 紅色部分是準的,後面的3是精度丟失的結果 。原來是整數,用科學計數法 E18 來表示就轉化成小數了,並且 double 8個字節,保留小數點後16位。

 

原因分析:

參考:http://www.360doc.com/content/10/1228/10/4154133_81941359.shtml

在計算機系統中,數值是用二進制來表示的。所有十進制整數都可以用二進制精確表示,小數則不然。

要弄清楚爲什麼精度會丟失,首先我們要了解如何用二進制來表示十進制小數。

十進制小數轉爲二進制小數的方法:“乘2取整”。

將十進制小數乘2得到的結果分爲整數部分和小數部分,整數部分既是相應的二進制數碼。小數部分(之前乘後得到新的小數部分)再乘2,又得到整數和小數部分。如此不斷重複,直到小數部分爲0或達到精度要求爲止。第一次所得到爲最高位,最後一次得到爲最低位。

舉一個簡單的例子,我們嘗試將 0.25 轉爲二進制:

十進制小數

乘以2之後的結果

整數部分

剩餘的小數部分

0.25

0.5

0

0.5

0.5

1

1

0

十進制小數 0.25 的二進制小數爲 0.01

接下來,我們嘗試將 0.9 轉爲二進制

十進制小數

乘以2之後的結果

整數部分

剩餘的小數部分

0.9

1.8

1

0.8

0.8

1.6

1

0.6

0.6

1.2

1

0.2

0.2

0.4

0

0.4

0.4

0.8

0

0.8

0.8

1.6

1

0.6

剩餘的小數部分出現了循環,0.9 用二進制表示爲 0.11100110011001100 ... 即用二進制無法精確表示十進制小數 0.9。

這可以類比無法用十進制來精確表示分數 1/3。

綜上,某些十進制小數無法用二進制精確表示,這造成了 float 和 double 精度丟失現象

 

float與double的範圍和精度

1. 範圍
  float和double的範圍是由指數的位數來決定的。
  float的指數位有8位,而double的指數位有11位,分佈如下:
  float:4 byte(32 bits)
  1bit(符號位) 8bits(指數位) 23bits(尾數位)
  double:8 byte(64 bits)
  1bit(符號位) 11bits(指數位) 52bits(尾數位)
  於是,float的指數範圍爲-127~+128,而double的指數範圍爲-1023~+1024,並且指數位是按補碼的形式來劃分的。
  其中負指數決定了浮點數所能表達的絕對值最小的非零數;而正指數決定了浮點數所能表達的絕對值最大的數,也即決定了浮點數的取值範圍。
  float的範圍爲-2^128 ~ +2^128,也即-3.40E+38 ~ +3.40E+38;double的範圍爲-2^1024 ~ +2^1024,也即-1.79E+308 ~ +1.79E+308。

2.  精度
  float和double的精度是由尾數的位數來決定的。浮點數在內存中是按科學計數法來存儲的,其整數部分始終是一個隱含着的“1”,由於它是不變的,故不能對精度造成影響。
  float:2^23 = 8388608,一共七位,這意味着最多能有7位有效數字,但絕對能保證的爲6位,也即float的精度爲6~7位有效數字;
  double:2^52 = 4503599627370496,一共16位,同理,double的精度爲15~16位。

 

解決方案:

1.java可以使用 // 用 BigDecimal.valueOf(double val) 來執行轉換

BigDecimal bigDecimalA = BigDecimal.valueOf(a);

2.sql關聯轉化爲統一類型進行關聯,例如都轉化爲 string

cast(9123456123451234230 as string)

 

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