目录

最近遇到了一些数字类型有关的问题,重新了解一下数字类型

前言

计算机中由二进制表示数据,所以一切数字都是表示为:p10 * 2^0 + p11* 2^1 + p12 * 2^2 + ... + p1n * 2^n + p01 * 2^(-1) + p02 * 2^(-2) + ... + p0n * 2^(-n), 其中p0k p1k 都是0或者1 。

因此,对于1.5 来说,其可以拆分为: 1*2^0 + 1 * 2^(-1)是可以准确表示的。

但是对于1.6来说,其实拆分为 1* 2^0 + 1*2^(-1) + 0 * 2 ^(-2) + 0 *2^(-3) + 1 * 2 ^(-4) + … 是不能精确表示的。

因此,在计算机中,1.6 -1.5并不是0.1, 如下。这跟自然中我们的理解是不同的。

scala> 1.6 -1.5
res2: Double = 0.10000000000000009

基本数字类型

在Spark中,基本数字类型可以分为IntegerType 和Float/DoubleType 以及Decimal类型。

其中IntegerType又分为Byte, Short, Int, Long. 而Float/DoubleType 是浮点类型。

IntegerType是一种可以精准表示一个数字的类型。Byte是8位,Short 16位,Int 32位,Long 64位,其最大值是2^n -1, n为位数。其中Int的最大值小于10^11次方,而Long的最大值小于10^20次方。

浮点与定点是指小数点的位置会不会发生浮动,显然整数中小数点位置是不会浮动的,Decimal也是一种定点数。

浮点数

回到浮点数,浮点数利用指数使小数点的位置可以根据需要进行上下浮动,从而灵活表达更大范围的实数。

摘自百度百科,

浮点数从逻辑上用三元组{S, E, M}表示一个数v |S| E | M |

  • 其中S表示符号位,是正数还是负数
  • E 表示指数位,其可以为正数或者负数
  • M位于尾部。

表达式为:v=(-1)^s * 2 ^e *m

现在大部分平台的浮点数遵循IEEE 754标准,float通常是32位,double 通常是64位。

浮点数采用科学计数法,32位的float的小数部分有效位是8位,精确表示应该是7位, 而64位的double的小数部分有效位是16位, 精确表示位数应该是15位。

例如: 112345678901234567d 被表示为 1.1234567890123456E17.

所以,当两个大于10^15的double/大于10^7次方的float进行比较是一件很危险的事情。

例如下面两个完全不同的数字比较出来的结果却是相等。

scala> 112345678901234568d == 112345678901234560d
res13: Boolean = true

scala> 112345678f == 112345679f
res14: Boolean = true

scala> 112345678f
res15: Float = 1.1234568E8

类型转换

在编程中,如果两个不同类型的操作数做计算,就会将低级别的类型向高级别的类型进行类型转换。

通常的规则是Byte向Short转换,Short向Int转,然后向Long,向Float,Float向Double转换。

比较一个整型和一个浮点类型是有风险的.

scala> 1234567890 == 1234567880f
res1: Boolean = true

scala> 123456789012345675l == 123456789012345679d
res2: Boolean = true

比较两个浮点型也是有风险的:

scala> 123456789012345679d == 123456789012345679f
res1: Boolean = false

scala> 112345678901234568d == 112345678901234560d
res2: Boolean = true

将一个Int值先转为float,再转为double 和直接转为double是不一样的。

举个例子,如下:

scala> Int.MaxValue.toFloat.toDouble
res23: Double = 2.147483648E9

scala> Int.MaxValue.toDouble
res24: Double = 2.147483647E9

关于Decimal

用于表示小数,在Spark中,除了使用double/float,还有一个选择就是使用Decimal。

关于Decimal,我曾经写过一篇文章案例分析| 由Decimal操作计算引发的Spark数据丢失问题, 里面介绍到Decimal,如下:

Decimal是数据库中的一种数据类型,不属于浮点数类型,可以在定义时划定整数部分以及小数部分的位数。对于一个Decimal类型,scale表示其小数部分的位数,precision表示整数部分位数和小数部分位数之和。

一个Decimal类型表示为Decimal(precision, scale),在Spark中,precision和scale的上限都是38

一个double类型可以精确地表示小数点后15位,有效位数为16位

可见,Decimal类型则可以更加精确地表示,保证数据计算的精度。

例如一个Decimal(38, 24)类型可以精确表示小数点后23位,小数点后有效位数为24位。而其整数部分还剩下14位可以用来表示数据,所以整数部分可以表示的范围是-10^14+1~10^14-1。

所以关于金钱有关的数据,在Spark中通常是使用Decimal进行存储。

结尾

浮点类型是模糊的表示一个小数,它可以表示更大的范围,但是也丢失了很多东西。在Spark中如果以两个浮点类型的列做join是件蛮危险的事情, 可能会得到意想不到的结果。

但是在Spark中,为了减少在Decimal操作时容易溢出的问题,有时候会将Decimal转换为double类型。