Spark exceptAll对比float与double列出现异常差值的原因咨询
Spark float/double类型exceptAll结果异常的原理说明
偏差的产生起点:浮点数的固有存储特性
所有遵循IEEE 754标准的float(32位单精度)、double(64位双精度)类型,都采用二进制科学计数法存储数值,绝大多数十进制小数无法用有限位二进制精确表示,只能存储为对应精度下最接近目标值的近似值:
- 代码中
84.99F、9.99F作为float类型字面量,赋值完成时存储的就不是精确的84.99、9.99,只是单精度精度范围内最接近两个十进制数的二进制近似值。 - 代码中
84.99、9.99作为double类型字面量,存储的是双精度精度范围内最接近两个十进制数的二进制近似值,由于双精度可用位数更多,这个近似值和真实十进制值的误差远小于float类型的误差。
为什么show()输出看不出差异
DataFrame的show()方法默认对浮点数做截断显示,不会输出完整精度的底层存储值,因此float和double存储的两个误差不同的近似值,截断后都会显示为84.99、9.99,肉眼无法直接识别到底层值的差异。
exceptAll执行环节的处理逻辑
调用exceptAll时,Spark首先会对齐两侧DataFrame的Schema,具体处理逻辑如下:
- 识别到左侧
dec列是float类型、右侧dec列是double类型,会自动将左侧float值隐式转换为double类型后再做等值比较,这个隐式转换和你手动调用cast("double")的逻辑完全一致。 - float转double的过程不会补全新的精度,只是把原来float存储的近似值原封不动拓展为64位的double表示:例如float类型存储的84.99近似值本质是
84.98999786376953,转成double后值不会发生任何变化,不会自动修正为double精度下更接近84.99的近似值。 - 右侧l2中double类型的84.99存储的是双精度下的近似值(约等于84.990000000000002),和左侧float转换得到的
84.98999786376953值不相等,因此会被判定为不存在于右侧集合,最终作为exceptAll的结果输出,也就是你看到的带长小数尾巴的值。
你可以直接在Scala REPL中运行以下代码验证底层真值:
// 打印float字面量转double后的真实值 println(84.99F.doubleValue()) // 输出 84.98999786376953 println(9.99F.doubleValue()) // 输出 9.989999771118164
注意:这不是Spark的功能bug,是浮点数存储规则带来的通用问题。任何需要精确等值比较、涉及金额等精确小数的场景,不要使用float/double类型,应该选用Decimal类型存储,避免精度误差带来的逻辑错误。
内容的提问来源于stack exchange,提问作者Ashu
相关产品推荐
相关产品推荐

