float vs decimal

引子

起因是有群友在尝试解决 double 不够存数据的问题,看到了 decimal,但对 decimal 的理解还是不对。

这段话说的很含糊,可能是从 ai 的文本中截取的一段。而问为什么要用 decimal 时,这也是一般人的回答。

清晰的解释

浮点数在计算机中以二进制形式存储,而很多十进制小数(例如 0.1)在二进制下表示为无限循环小数。由于浮点数的存储空间有限,这些无限二进制小数必须被截断,从而引入精度误差。相比之下,decimal 类型使用十进制存储,可以精确表示十进制小数,因此不会产生类似的精度问题,适合对精度要求高的场景(如财务计算)。

进制与截断

decimal 如其名一样是十进制的,而一般的浮点(float/double)是基于二进制(binary floating-point)遵循 IEEE 754 标准的。

最常见的情况是 0.1 + 0.2 = 0.3 的例子,请参考以下代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
checkFiniteInBinary("0.1");
checkFiniteInBinary("0.2");
checkFiniteInBinary("0.3");
checkFiniteInBinary("0.5");

float floatResult = .1f + .2f;
System.out.printf("floatResult = %.64f\n", floatResult);

double doubleResult = .1 + .2;
System.out.printf("doubleResult = %.64f\n", doubleResult);

BigDecimal decimalResult = new BigDecimal("0.1").add(new BigDecimal("0.2"));
System.out.printf("decimalResult = %.64f\n", decimalResult);

doubleResult = .2 + .3;
System.out.printf("doubleResult = %.64f\n", doubleResult);
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
19:53:38: Executing ':org.ryuu.Main.main()'…

Starting Gradle Daemon...
Gradle Daemon started in 1 s 634 ms
> Task :compileJava UP-TO-DATE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE

> Task :org.ryuu.Main.main()
十进制数0.1在二进制数下不能有限表示。
0.0001100110011001100110011001100110011001100110011001100110011001... (仅显示64位)

十进制数0.2在二进制数下不能有限表示。
0.0011001100110011001100110011001100110011001100110011001100110011... (仅显示64位)

十进制数0.3在二进制数下不能有限表示。
0.0100110011001100110011001100110011001100110011001100110011001100... (仅显示64位)

十进制数0.5在二进制数下能有限表示。
0.1

floatResult = 0.3000000119209289600000000000000000000000000000000000000000000000
doubleResult = 0.3000000000000000400000000000000000000000000000000000000000000000
decimalResult = 0.3000000000000000000000000000000000000000000000000000000000000000
doubleResult = 0.5000000000000000000000000000000000000000000000000000000000000000

BUILD SUCCESSFUL in 6s
2 actionable tasks: 1 executed, 1 up-to-date
19:53:46: Execution finished ':org.ryuu.Main.main()'.

0.1,0.2这样的数在二进制里类似十进制的1/3(0.3333333…),是无限循环的。浮点数会将数据截断,因此会丢失精度。

1
2
0.1(decimal) = 0.0001100110011001100110011001100110011001100110011001100110011001... (binary)
0.2(decimal) = 0.0011001100110011001100110011001100110011001100110011001100110011... (binary)

另请参阅

github 有完整的示例工程