Apache Spark DecimalType读取数值时BigDecimal向上取整问题咨询
首先,这个问题的核心原因是二进制浮点数的精度限制,导致你的原始数值在转换过程中被近似,最终触发了四舍五入。
为什么会出现取整?
你提到的原始数值 0.070070073108537356 是一个十进制小数,但绝大多数十进制小数无法被二进制浮点数(比如Java的double)精确表示。当Spark读取数据时,如果你的数据源是通过浮点数格式存储(比如CSV自动推断为double类型,或者数据源本身是float/double),这个数值会先被转换成二进制浮点数的近似值——而这个近似值对应的十进制表示就是0.07007007310853736左右。
当你用DecimalType(38,18)去转换这个近似值时,Spark会按照指定的scale(18位小数)进行四舍五入,于是末尾的56就变成了60。
你可以自己验证这个点:用Java的BigDecimal直接包装这个double值,会得到近似结果:
new BigDecimal(0.070070073108537356) // 输出结果会是 0.07007007310853736028...
怎么解决这个问题?
解决的关键是避免中间经过浮点数的转换步骤,直接从原始的十进制字符串表示解析为DecimalType。这里有几个可行的方案:
方案1:读取时显式指定Decimal schema
如果你的数据源是文本格式(比如CSV),不要让Spark自动推断列类型,而是直接指定该列为DecimalType(38,18),让Spark直接从字符串解析为Decimal,跳过浮点数转换:import org.apache.spark.sql.types.{DecimalType, StructType, StructField} val customSchema = new StructType() .add(StructField("target_column", DecimalType(38, 18), nullable = false)) val df = spark.read.schema(customSchema).csv("your_input_path.csv")方案2:确保数据源存储为Decimal类型
如果你的数据是写入到Parquet/ORC等列式存储中,写入时就指定列类型为DecimalType,而不是double。这样后续读取时就能直接拿到精确的十进制数值,不会有近似问题。方案3:浮点数转Decimal的补救方法
如果已经无法避免浮点数存储,可以尝试先将浮点数格式化为高精度字符串,再转换为Decimal:import org.apache.spark.sql.functions._ df.withColumn("accurate_decimal", format_number(col("float_column"), 18) .cast(DecimalType(38, 18)) )注意这个方法依赖于字符串格式化的精度,需要确保格式字符串的小数位数足够覆盖你的原始精度。
内容的提问来源于stack exchange,提问作者John Fisherman

