从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)

 

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