DEBUG.WIKI
丽萨主机;为跨境业务选好 IP;原生 / 住宅 IP · 按套餐选择;查看 IP →
阿里云;从建站到云上业务;云服务器 · 数据库 · 对象存储;查看产品 →
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Java BigDecimal 数值相同,equals 却返回 false

BigDecimal 的 equals 同时检查数值和 scale。按数值比较使用 compareTo,集合查找则需要一致的键规范化规则,并保留原来的精度与展示约定。

两个十进制数分别从 "3.0" 和 "3.00" 构造,equals() 返回 false,compareTo() 却返回 0。把其中一个放进 HashSet 后,拿另一个查找也可能找不到。

这里要区分数值相等与表示方式相等。BigDecimal 的 equals() 同时检查数值和 scale;compareTo() 按数值比较,不要求 scale 相同。下面的示例适用于 Java 8 及以上。

打印 scale,而不是只看格式化后的金额

import java.math.BigDecimal;

public class DecimalEquality {
    public static void main(String[] args) {
        BigDecimal left = new BigDecimal("3.0");
        BigDecimal right = new BigDecimal("3.00");

        System.out.println(left.scale());
        System.out.println(right.scale());
        System.out.println(left.equals(right));
        System.out.println(left.compareTo(right));
    }
}

预期输出依次为 1、2、false、0。界面可能把两者都显示成两位小数,但显示格式并没有修改对象中保存的 scale。

这个差异与浮点近似误差无关。示例使用字符串构造,差异就在表示精度。不要先转成 double 再比较,这会额外引入精度丢失,反而放弃了 BigDecimal 原有的精确十进制表示。

只关心数值时使用 compareTo

boolean sameAmount = left.compareTo(right) == 0;
boolean smaller = left.compareTo(right) < 0;

把这段判断放在两个非 null 的 BigDecimal 上。业务若允许缺失值,应先明确缺失值是否可比较;不要把 null 自动当成零。

如果业务确实需要区分 3.0 与 3.00 的表示,则保留 equals()。不要仅为了让一个判断通过,统一把所有字段改成固定小数位;改变 scale 可能需要舍入,也可能改变后续计算和展示约定。

HashSet 和键查找需要一致的规范化规则

在普通集合查找中,单独改一处条件为 compareTo() 不会改变集合的等价规则。HashSet 的 contains() 使用对象相等语义;BigDecimal 的哈希计算也包含 scale。

如果集合明确以“相同数值视为同一项”为契约,可以在插入和查找两端都规范化:

import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;

public class DecimalSet {
    public static void main(String[] args) {
        Set<BigDecimal> amounts = new HashSet<>();
        amounts.add(new BigDecimal("3.0").stripTrailingZeros());
        boolean found = amounts.contains(
            new BigDecimal("3.00").stripTrailingZeros()
        );
        System.out.println(found);
    }
}

这段代码的预期输出是 true。只规范化查询参数,而集合仍存着原来的不同 scale 对象,不能作为完整修复。已有集合需要按同一规则重新构建,并评估原来不同的项合并后是否会丢掉业务信息。

stripTrailingZeros() 返回新对象,不改原对象;结果的 scale 可能是负数,字符串显示也可能变为科学计数法。因此适合规范化比较键,不应未经评估就替换所有展示值或持久化金额。

验证时分别覆盖尾随零不同、真实数值不同、零,以及集合插入后查找。回退时恢复原比较或键规范化规则;若已对持久化数据做了合并,仅回退代码无法恢复合并前的区别,应先保留原始数据,避免把这一修复扩大成不可逆的数据清洗。