You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Aeson/TH解码Float时因科学计数法引发精度丢失的解决咨询

解决Haskell中Aeson解码大数值Float时的精度丢失问题

问题根源

你遇到的精度丢失不是Aeson转换导致的,而是单精度Float类型本身的精度限制:Float只有约6-7位有效数字,当数值超过这个范围时(比如1157265240.03的有效数字已超过7位),尾部精确值会被舍入,这是浮点数二进制存储的固有特性。

可行解决方案

1. 改用Double类型(快速解决)

把Money类型中的Float替换为Double,Double拥有15-17位有效数字,完全能覆盖当前数值量级:

import Data.Aeson hiding (json)
import Data.Aeson.TH

data Money = USD Double
$(deriveJSON defaultOptions ''Money)

-- 接收请求的代码无需修改
_money <- requireCheckJsonBody :: Handler Money

修改后,1157265240.03可被精确解码(Double的精度足以保留所有有效数字)。

2. 使用专门的货币/十进制类型(专业方案)

浮点数(Float/Double)本质不适合货币计算,因为二进制浮点数无法精确表示所有十进制小数。生产环境推荐使用专门的十进制类型:

  • decimal包的Decimal类型(可控精度的十进制数)
  • money包的Dense类型(专为货币设计)

以decimal包为例,步骤如下:

  1. 在cabal或stack配置中添加decimal依赖
  2. 定义类型并实现Aeson实例:
import Data.Aeson hiding (json)
import Data.Aeson.TH
import Data.Decimal

data Money = USD Decimal
$(deriveJSON defaultOptions ''Money)

-- 若TH自动生成失败,可手动编写实例:
-- instance FromJSON Money where
--   parseJSON = withScientific "Money" $ \s -> return $ USD (realToFrac s)
-- instance ToJSON Money where
--   toJSON (USD d) = toJSON (realToFrac d :: Scientific)

这种方案能彻底避免浮点数精度误差,适合需要精确货币计算的场景。

3. 自定义Aeson解码逻辑(不推荐)

如果因特殊原因必须使用Float,可手动将JSON数值先读为Scientific类型再转换,但这无法解决Float本身的精度限制,仅能确保转换过程无额外损失:

import Data.Aeson.Types
import Data.Scientific

instance FromJSON Money where
  parseJSON = withScientific "Money" $ \s ->
    return $ USD (realToFrac s)

但Float仍存不下超过7位的有效数字,因此不推荐该方案。


内容的提问来源于stack exchange,提问作者Shawn Zhang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 10:39:34