Java BigDecimal与RoundingMode的用法总结

一、引进

做算法题的时候,最气人的不是不会写,而是会写却算错。明明逻辑满分,一输出"0.30000000000000004",瞬间被判死刑。别怀疑,就是 double 在背后捅刀------它眼里 0.1+0.2 永远不等于 0.3。

想甩掉这坑,只能把 double 踢下线,让 BigDecimal 上岗。它不讲二进制那一套,就按我们十进制算盘打,指哪打哪,小数点后面想留几位就留几位,永远对得上账。只要内存够,它能跟你耗到你先倒下,而且精度一分不差。

面对涉及金融计算、超大整数、或者需要绝对精确小数运算的题目时,

使用 BigDecimal

二、优点

1.算得准

double 眼里 0.1 其实是"0.100000000000000005...",攒多了就翻车。BigDecimal 把数字当"字符串"存,小学竖式怎么算它就怎么算,说 0.1 就 0.1,一分不差。

2. 数字再大也装得下

long 最大才 9 后面 18 个数字,题目动不动就要算 100 位的阶乘?BigDecimal 直接把数字当"无限长"的数组,只要内存不炸,它就能一直写下去。

3. 想怎么四舍五入你说了算

题目要"保留两位小数""银行家舍入""直接砍尾"?一行 setScale+RoundingMode,分分钟切好,double 却只能干瞪眼。

4. 绝不溢出

int、long 加着加着变负数?放心,BigDecimal 只要内存有多大,我就能算多大。

三、算法题中最常用的BigDecimal方法

构造方法:

BigDecimal(String val) : 最推荐、最安全的方式! 使用字符串构造可以精确表示你想要的数值

(如 new BigDecimal("0.1"))。避免使用BigDecimal(double val),因为传入的double本身可能已有精度损失(如 new BigDecimal(0.1) 实际上构造的是

0.1000000000000000055511151231257827021181583404541015625)。

BigDecimal.valueOf(double val): 这是将double转换为BigDecimal的相对安全方式。它内部调用Double.toString(double),利用了字符串转换的精确性。适用于已知double值能精确表示或精度要求高的场景(比直接使用double构造器好得多)。

算术运算:

add(BigDecimal augend): 加法
subtract(BigDecimal subtrahend): 减法
multiply(BigDecimal multiplicand): 乘法
divide(BigDecimal divisor): 除法

(注意: 如果结果无法精确表示(如1 / 3),会抛出ArithmeticException ,必须指定舍入模式!) divide(BigDecimal divisor, int scale, RoundingMode roundingMode): 最常用的除法形式!

指定结果的小数位数(scale)和舍入模式(roundingMode)。
divide(BigDecimal divisor, RoundingMode roundingMode): 除法,使用指定的舍入模式,结果精度由计算决定(通常位数较多)
abs(): 绝对值

pow(int n): 幂运算(n次方)

比较:

compareTo(BigDecimal val): 比较大小的标准方法

返回 -1(小于)、0(等于)、1(大于)。必须使用此方法代替equals()!

重要陷阱: equals()方法不仅比较数值是否相等 ,还比较精度(scale)是否相同 。

new BigDecimal("1.0").equals(new BigDecimal("1.00")) 会返回 false!

而compareTo() 只关心数值本身,1.0和1.00比较结果为0(相等)。

精度控制与舍入:

setScale(int newScale, RoundingMode roundingMode) : 核心精度控制方法! 设置小数点后的位数 (newScale),并使用指定的RoundingMode 进行必要的舍入操作。

如果当前scale大于newScale,需要舍入; 如果小于newScale,则在末尾补零。
scale(): 获取当前小数点后的位数。

输出:

toString():

返回此BigDecimal的规范字符串表示形式。如果需要特定格式(如固定小数位、科学计数法),应使用NumberFormat或DecimalFormat。
toPlainString():

返回不带指数字段的字符串表示。对于非常大或非常小的数,toString()可能输出科学计数法(如1e+10),而toPlainString()会输出"10000000000"。

四、RoundingMode:定义如何"舍"与"入"

RoundingMode是一个枚举类(java.math.RoundingMode),定义了8种舍入策略。它与BigDecimal的divide和setScale方法紧密结合,是精确控制结果的关键。算法竞赛中最常用的几种模式:

  1. RoundingMode.HALF_UP ("四舍五入"):

    • 规则: 舍弃部分 >= 0.5 时进位;< 0.5 时舍弃。
    • 例子: 5.5 -> 6, 2.5 -> 3, 1.4 -> 1, 1.6 -> 2
    • 谐音:"哈夫上"
      画面:哈(HALF)佛老教授走上(UP)讲台------只上一半台阶就回头,"过半就上去"。
    • 竞赛应用: 题目明确要求"四舍五入"时使用。这是最符合直觉的舍入方式。
  2. RoundingMode.DOWN ("向零舍入"):

    • 规则: 无论舍弃部分大小,总是向零的方向舍入(即绝对值变小)。
    • 例子: 5.9 -> 5, -5.9 -> -5, 1.1 -> 1, -1.1 -> -1
    • 谐音:"剁"
      画面:一把刀剁下去,向零剁,数字瞬间被砍到 0 那边。
    • 竞赛应用: 需要截断小数部分(取整)而不进行任何舍入时使用。类似于强制类型转换(int)的效果。
  3. RoundingMode.CEILING ("向正无穷舍入"):

    • 规则: 结果总是向正无穷方向舍入(向上取整)。
    • 例子: 5.1 -> 6, -5.1 -> -5, 1.0 -> 1
    • 竞赛应用: 需要计算"至少需要多少个"的场景(如物品装箱、资源分配)。
  4. RoundingMode.FLOOR ("向负无穷舍入"):

    • 规则: 结果总是向负无穷方向舍入(向下取整)。
    • 例子: 5.9 -> 5, -5.9 -> -6, 1.0 -> 1
    • 竞赛应用: 需要计算"最多能容纳多少个"的场景。
  5. RoundingMode.HALF_EVEN ("银行家舍入法"):

    • 规则: 舍弃部分 > 0.5 时进位;< 0.5 时舍弃;等于 0.5 时,看前一位数字:如果是奇数则进位,如果是偶数则舍弃。
    • 例子: 5.5 -> 6 (5是奇数,进位), 2.5 -> 2 (2是偶数,舍弃), 1.6 -> 2, 1.4 -> 1
    • 竞赛应用: 在统计学或需要减少累计误差的场景中更优。题目没有特别说明"四舍五入"时,有时会默认使用此规则(需仔细审题!)

五、在算法题中使用BigDecimal的典型流程

  1. 构造 :

    优先使用String 构造或BigDecimal.valueOf()

  2. 运算 :

    使用add, subtract, multiply 进行基本运算。进行divide 时,一定要指定精度(scale)和舍入模式(RoundingMode)!

  3. 精度控制 :

    在最终输出结果前,使用setScale(int desiredScale, RoundingMode roundingMode)将 结果调整到题目要求的精度。

  4. 比较 :

    使用compareTo()比较两个BigDecimal的大小。

  5. 输出 :

    使用toString()或toPlainString()输出,或者根据题目要求使用格式化输出。

例题

计算圆的面积,保留两位小数,四舍五入

java 复制代码
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Scanner;

public class CircleArea {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        BigDecimal radius = new BigDecimal(sc.nextDouble());
        BigDecimal PI = new BigDecimal("3.14159");

        //计算面积
        BigDecimal area = PI.multiply(radius).multiply(radius);

        //设置精度,保留两位小数,四舍五入
        area = area.setScale(2,RoundingMode.HALF_UP);

        System.out.println(area);
        
        //输入:5.0
        //输出:78.54
    }
}
相关推荐
nm_zfyjt42 分钟前
信息流广告投放优化实践:线索成本评估模型与转化链路重构(家装建材案例)
算法·重构·内蒙古朋友圈广告服务商·内蒙古朋友圈获客
海的透彻1 小时前
dockerfile如何快速构建容器(java服务)
java·开发语言
code2cat1 小时前
Java工具篇之Gradle:用任务图组织构建与交付
java·开发语言·单元测试·gradle
后台模板学习2 小时前
大模型 Prompt 工程在电商订单系统中的踩坑记录
java·python·prompt
w_zero_one2 小时前
算法--构造
java·数据结构·算法
SL_staff2 小时前
制造业订单交付崩盘?用四层协同框架重建交付可信度(技术实践)
java·产品·全栈
其实防守也摸鱼2 小时前
信创安全竞赛知识总结与题库
大数据·网络·人工智能·算法·安全·php
SL_staff3 小时前
打破风控迭代死循环:用决策编排重构业务与技术协作链路
java·产品·全栈
Mikko74 小时前
Redis 连接数满了服务端有错误信息吗?连接数不增加却命令超时?6.2 实测连接池、超时与大 key 怎么判定
java·spring boot·redis·后端