**高并发、微服务 、性能调优实战案例100讲，所有案例均源于个人工作实战，均配合代码落地**

加我微信：<span style="font-weight:bold; color:red">itsoku</span>，所有案例均提供在线答疑。



# 第93节 涉及到钱的，千万不要用double，请用BigDecimal

非常实用的一节，先收藏点赞，慢慢看。

<span style="font-weight:bold; color:red">目前整个课程59块钱，100个案例，含所有源码 & 文档 & 技术支持，可点击左下角小黄车了解</span>



# 1、金融系统不要用double，会有精度问题

```java
@Test
public void m1() {
    double a = 4.015, b = 100.00;
    System.out.println(a * b);  // 期望结果：401.5

    double c = 123.30, d = 100.00;
    System.out.println(c / d); //期望结果：1.233
}
```

运行输出如下

```
401.49999999999994
1.2329999999999999
```

和期望的结果不太一致。

# 2、原因

是由于计算机存储double类型数据的时候，会产生精度问题；float类型同样也有精度问题。

具体底层原理，大家可以查阅相关资料了解。

如何解决这个问题呢？



# 3、使用BigDecimal，可解决问题

java为我们提供了一个类：BigDecimal，可以用来解决double、float这种浮点数的精度问题。

代码如下

```java
@Test
public void m2() {
    BigDecimal a = new BigDecimal("4.015");
    BigDecimal b = new BigDecimal("100.00");
    System.out.println(a.multiply(b));

    BigDecimal c = new BigDecimal("123.30");
    BigDecimal d = new BigDecimal("100.00");
    System.out.println(c.divide(d));
}
```

运行输出

```java
401.50000
1.233
```



# 4、如何构造BigDecimal对象 ？

## 正确做法

**请使用字符串来构造BigDecimal对象，如下**

```java
BigDecimal bd = new BigDecimal("0.1");  
```

## 错误做法

下面这种，使用double类型的参数构造BigDecimal，会有精度问题，**不要使用**

```java
BigDecimal bd = new BigDecimal(0.1);  
```



# 5、BigDecimal对象如何比较大小？

需要用使用BigDecimal中的compareTo方法，如下，需传入另外一个BigDecimal对象，和当前对象比较大小

```java
public int compareTo(BigDecimal val);
```

返回值有3种情况

- -1：小于
- 0：相等
- 1：大于

案例

```java
@Test
public void m3() {
    System.out.println(new BigDecimal("0.1").compareTo(new BigDecimal("0.2")));
    System.out.println(new BigDecimal("0.2").compareTo(new BigDecimal("0.1")));
    System.out.println(new BigDecimal("0.123").compareTo(new BigDecimal("0.123000000")));
}
```

输出

```java
-1
1
0
```



# 6、BigDecimal保留到分，如何处理？

BigDecimal计算出的结果，小数位可能很长，此时就需要考虑保留几位小数，如何舍掉后面的位数。

金融系统中通常会将计算的结果采用四舍五入，保留到分，即2位小数，处理如下

```java
@Test
public void m4() {
    System.out.println(new BigDecimal("0.12345678").setScale(2, BigDecimal.ROUND_HALF_UP));
    System.out.println(new BigDecimal("0.45678910").setScale(2, BigDecimal.ROUND_HALF_UP));
}
```

输出

```java
0.12
0.46
```



# 7、数据库中对应的类型：decimal

BigDecimal中在db中也有对应的类型，MySQL中对应decimal类型，如下，保留2位小数，具体保留几位小数，大家可以根据业务来决定。

```java
decimal(18,2)
```



# 8、给大家提供一个BigDecimal工具类

下面这个类，是我在做金融系统的时候，写的一个，大家直接拿去用，包含了数据常见的各种方法。

```java
package com.itsoku.lesson093;

import java.math.BigDecimal;
import java.text.DecimalFormat;

/**
 * <b>description</b>： Java高并发、微服务、性能优化实战案例100讲，视频号：程序员路人，源码 & 文档 & 技术支持，请加个人微信号：itsoku <br>
 * <b>time</b>：2024/9/10 23:21 <br>
 * <b>author</b>：ready likun_557@163.com
 */
public class BigDecimalUtils {

    /**
     * 除法默认保留精度
     */
    private static final int DEF_DIV_SCALE = 10;

    /**
     * 默认舍入模式（ROUND_HALF_UP：四舍五入）
     */
    private static final int DEF_ROUNDING_MODE = BigDecimal.ROUND_HALF_UP;

    private static final String FORMAT_1 = "#.##";

    /**
     * 将字符串转换为 BigDecimal
     *
     * @param value
     * @return
     */
    public static BigDecimal of(String value) {
        return value == null ? null : new BigDecimal(value);
    }

    /**
     * v1 + v2
     *
     * @param v1 被加数
     * @param v2 加数
     * @return 两个参数的和
     */

    public static BigDecimal add(BigDecimal v1, BigDecimal v2) {
        return v1.add(v2);
    }

    /**
     * args[0] + args[1] + ... + args[n]
     *
     * @param args
     * @return
     */
    public static BigDecimal add(BigDecimal... args) {
        if (args == null || args.length <= 1) {
            throw new IllegalArgumentException("args Contains at least two parameters");
        }
        BigDecimal result = args[0];
        for (int i = 1; i < args.length; i++) {
            result = add(result, args[i]);
        }
        return result;
    }


    /**
     * v1 - v2
     *
     * @param v1 被加数
     * @param v2 加数
     * @return 两个参数的和
     */

    public static BigDecimal subtract(BigDecimal v1, BigDecimal v2) {
        return v1.subtract(v2);
    }

    /**
     * args[0] - args[1] - ... - args[n]
     *
     * @param args
     * @return
     */
    public static BigDecimal subtract(BigDecimal... args) {
        if (args == null || args.length <= 1) {
            throw new IllegalArgumentException("args Contains at least two parameters");
        }
        BigDecimal result = args[0];
        for (int i = 1; i < args.length; i++) {
            result = subtract(result, args[i]);
        }
        return result;
    }

    /**
     * v1 * v2
     *
     * @param v1 被乘数
     * @param v2 乘数
     * @return 两个参数的积
     */

    public static BigDecimal multiply(BigDecimal v1, BigDecimal v2) {
        return v1.multiply(v2);
    }

    /**
     * args[0] * args[1] * ... * args[n]
     *
     * @return 两个参数的积
     */

    public static BigDecimal multiply(BigDecimal... args) {
        if (args == null || args.length <= 1) {
            throw new IllegalArgumentException("args Contains at least two parameters");
        }
        BigDecimal result = args[0];
        for (int i = 1; i < args.length; i++) {
            result = multiply(result, args[i]);
        }
        return result;
    }


    /**
     * v1 / v2
     *
     * @param v1 被乘数
     * @param v2 乘数
     * @return 两个参数的积
     */

    public static BigDecimal divide(BigDecimal v1, BigDecimal v2) {
        return v1.divide(v2, DEF_DIV_SCALE, DEF_ROUNDING_MODE);
    }

    /**
     * args[0] / args[1] / ... / args[n]
     *
     * @return 两个参数的积
     */

    public static BigDecimal divide(BigDecimal... args) {
        if (args == null || args.length <= 1) {
            throw new IllegalArgumentException("args Contains at least two parameters");
        }
        BigDecimal result = args[0];
        for (int i = 1; i < args.length; i++) {
            result = divide(result, args[i]);
        }
        return result;
    }

    /**
     * 四舍五入，保留两位小数
     *
     * @param d
     * @return
     */
    public static BigDecimal round(BigDecimal d) {
        if (d == null) {
            return null;
        }
        return d.setScale(2, BigDecimal.ROUND_HALF_UP);
    }


    /**
     * v1>v2?
     *
     * @param v1
     * @param v2
     * @return
     */
    public static boolean gt(BigDecimal v1, BigDecimal v2) {
        return v1.compareTo(v2) > 0;
    }

    /**
     * v1 == v2
     *
     * @param v1
     * @param v2
     * @return
     */
    public static boolean eq(BigDecimal v1, BigDecimal v2) {
        return v1.compareTo(v2) == 0;
    }

    /**
     * 判断 values 是否等于 0？
     *
     * @param value
     * @return
     */
    public static boolean eq0(BigDecimal value) {
        return BigDecimal.ZERO.compareTo(value) == 0;
    }

    /**
     * v1>=v2?
     *
     * @param v1
     * @param v2
     * @return
     */
    public static boolean ge(BigDecimal v1, BigDecimal v2) {
        return v1.compareTo(v2) >= 0;
    }

    /**
     * v1<v2?
     *
     * @param v1
     * @param v2
     * @return
     */
    public static boolean lt(BigDecimal v1, BigDecimal v2) {
        return v1.compareTo(v2) < 0;
    }

    /**
     * 格式化，保留2为小数
     *
     * @param value
     * @return
     */
    public static String format(BigDecimal value) {
        if (value == null) {
            return null;
        }
        DecimalFormat df = new DecimalFormat(FORMAT_1);
        return df.format(value);
    }

    /**
     * 格式化
     *
     * @param value
     * @param pattern 模式字符串，如：#.##
     * @return
     */
    public static String format(BigDecimal value, String pattern) {
        if (value == null) {
            return null;
        }
        DecimalFormat df = new DecimalFormat(pattern);
        return df.format(value);
    }

}
```

案例代码

```java
@Test
public void m5() {
    //1、通过字符串构造 BigDecimal
    System.out.println(BigDecimalUtils.of("0.123"));

    //2、加法
    System.out.println(BigDecimalUtils.add(new BigDecimal("0.111"), new BigDecimal("0.123")));

    //3、减法
    System.out.println(BigDecimalUtils.subtract(new BigDecimal("0.111"), new BigDecimal("0.123")));

    //4、乘法
    System.out.println(BigDecimalUtils.multiply(new BigDecimal("0.111"), new BigDecimal("0.123")));

    //5、除法
    System.out.println(BigDecimalUtils.divide(new BigDecimal("0.111"), new BigDecimal("0.123")));

    //6、四舍五入，结果保留2位小数，金融系统中通常会将计算的结果保留到分，此时就可以用这个方法
    System.out.println(BigDecimalUtils.round(BigDecimalUtils.divide(new BigDecimal("0.111"), new BigDecimal("0.123"))));

    //7、判断 v1<v2?
    BigDecimal v1 = new BigDecimal("0.111");
    BigDecimal v2 = new BigDecimal("0.123");
    System.out.println(BigDecimalUtils.lt(v1, v2));

    //8、判断2个数是否相等 0.111 == 0.11100000000，相等
    System.out.println(BigDecimalUtils.eq(v1, new BigDecimal("0.11100000000"))); // true

    //9、判断是否等于0
    System.out.println(BigDecimalUtils.eq0(new BigDecimal("0")));  // true
    System.out.println(BigDecimalUtils.eq0(new BigDecimal("0.000"))); // ture
    System.out.println(BigDecimalUtils.eq0(new BigDecimal("0.001"))); // false

    //10、格式化
    System.out.println(BigDecimalUtils.format(BigDecimalUtils.of("123.45678"))); // 123.46
}
```

运行输出

```java
0.123
0.234
-0.012
0.013653
0.9024390244
0.90
true
true
true
true
false
123.46
```



# 9、其他知识点

## 1）接口中，资金直接使用BigDecimal类型

```java
@RequestMapping("/transfer")
public ResultDto<Boolean> transfer(@RequestBody TransferRequest request){
}

@Data
public class TransferRequest {
    //付款人账户id
    private String fromAccountId;
    
    //收款人账号id
    private String toAccountId;

    //转账金额
    private BigDecimal transferPrice;
}
```

## 2）PO中也使用BigDecimal类型

```sql
create table if not exists t_account
(
    id      varchar(32)    not null primary key comment '用户id',
    balance decimal(12, 2) not null comment '账户余额',
) comment '账户表';
```

对应的po如下，balance用的是BigDecimal类型

```java
@Data
public class AccountPO {
    //用户id
    private String id;
    //账户余额
    private BigDecimal balance;
}
```



# 获取，源码 & 文档 & 技术支持

源码在lesson093模块中，需要的小伙伴可以加我微信：itsoku，获取。



# 高并发 & 微服务 & 性能调优实战案例100讲

## 已更新 93 节课

<span style="font-weight:bold; color:red">目前整个课程59块钱，含所有源码 & 文档 & 技术支持，一杯咖啡的价格，还没下手的朋友，赶紧了</span>。

```java
1. 分片上传实战
2. 通用并发处理工具类实战
3. 实现一个好用接口性能压测工具类
4. 超卖问题的4种解决方案，也是防止并发修改数据出错的通用方案
5. Semaphore实现接口限流实战
6. 并行查询，优化接口响应速度实战
7. 接口性能优化之大事务优化
8. 通用的Excel动态导出功能实战
9. 手写线程池管理器，管理&监控所有线程池
10. 动态线程池
11. SpringBoot实现动态Job实战
12. 并行查询，性能优化利器，可能有坑
13. 幂等的4种解决方案，吃透幂等性问题
14. 接口通用返回值设计与实现
15. 接口太多，各种dto、vo不计其数，如何命名？
16. 一个业务太复杂了，方法太多，如何传参？
17. 接口报错，如何快速定位日志？
18. 线程数据共享必学的3个工具类：ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal
19. 通过AOP统一打印请求链路日志，排错效率飞升
20. 大批量任务处理常见的方案（模拟余额宝发放收益）
21. 并发环境下，如何验证代码是否正常？
22. MySql和Redis数据一致性
23. SpringBoot数据脱敏优雅设计与实现
24. 一行代码搞定系统操作日志
25. Aop简化MyBatis分页功能
26. ThreadLocal 遇到线程池有大坑 & 通用解决方案
27. SpringBoot读写分离实战（一个注解搞定读写分离 && 强制路由主库）
28. MQ专题-MQ典型的使用场景
29. MQ专题-如何确保消息的可靠性
30. MQ专题-SpringBoot中，手把手教你实现事务消息
31. 手写一个好用的延迟任务处理工具类
32. MQ专题-MQ延迟消息通用方案实战
33. MQ消息幂等消费 & 消费失败衰减式重试通用方案 & 代码 & 文档
34. MQ专题：顺序消息通用方案实战 & 代码落地 & 文档
35. MQ专题：消息积压相关问题及解决思路
36. 分布式事务-MQ最终一致性-实现跨库转账（案例+源码+文档）
37. 分布式事务-MQ最终一致性-实现电商账户余额提现到微信钱包（案例+源码+文档）
38. 分布式事务：通用的TCC分布式事务生产级代码落地实战
39. 分布式锁详解
40. 分享一个特别好用的Redissson分布式锁工具类
41. 一个注解轻松搞定分布式锁
42. 微服务中如何传递公共参数？
43. 接口幂等，通用方案 & 代码落地
44. 微服务链路日志追踪实战
45. 接口测试利器HTTP Client，不用Postman也可以
46. 封装MyBatis，实现通用无SQL版CRUD功能ORM框架
47. MyBatisPlus 轻松实现多租户数据隔离
48. 电商系统-资金账户表设计 及 应用实战
49. UML画图神器：PlantUML，画图效率飞升
50. 多线程事务，3秒插入百万数据
51. SpringBoot中自动初始化数据库功能，非常好用
52. SpringBoot优雅停机
53. 分享一个特好用的集合工具类，开发效率轻松翻倍
54. 性能调优：线程死锁相关问题
55. 如何排查OOM？
56. cpu飙升，如何快速排查？
57. cpu飙升，使用Arthas，3秒定位问题
58. 接口响应慢，使用Arthas，3秒定位问题代码
59. 策略模式，轻松消除ifelse代码
60. 生产上，代码未生效，如何排查？
61. 使用MySQL，实现一个高性能，分布式id生成器
62. 方法执行异常，使用arthas，快速定位问题
63. 扫码登录详解
64. 使用hutool生成&解析二维码，太方便了
65. SpringBoot中，redis中实现排行榜
66. SpringBoot中，Redis如何实现查找附近的人功能？
67. SpringBoot中，接口签名，通用方案，一次性搞懂
68. SpringBoot中，接口加解密，通用方案实战
69. 分库、分表、分库分表，如何选择？
70. 分库分表：分表字段如何选择？
71. 分库分表：分表数量为什么建议是2的n次方？
72. 分库分表：如何平滑迁移数据？
73. 并发编程有多难？值得反复研究的一个案例
74. 使用Redis Pipeline，接口性能提升10倍
75. 电商中，重复支付如何解决？
76. 千万级数据，全表update的正确姿势
77. 优雅实现树形菜单，适用于所有树，太好用了
78. 接口调用利器：RestTemplate，吃透它
79. 微服务跨库查询，如何解决？一次性搞懂
80. 逻辑删除与唯一约束冲突，如何解决？
81. 评论系统如何设计，一次性给你讲清楚
82. SpringBoot下载文件的几种方式，一次性搞懂
83. 订单超时自动取消，最常见的方案
84. 责任链模式优化代码，太好用了
85. CompletableFuture 实现异步任务编排，太好用了
86. idea中的必备debug技巧，高手必备
87. Java动态生成word，太强大了
88. 海量据量统计，如何提升性能？
89. MyBatis模糊查询，千万不要再用${}了，容易搞出大事故
90. Spring事务失效，常见的几种场景，带你精通Spring事务
91. idea多线程调试，这个技巧也太棒了吧，你会么？
92. MySQL排序分页，可能有坑，需要注意
93. 涉及到钱的，千万不要用double，请用BigDecimal
```




## 课程部分大纲，连载中。。。。

以下课程均来源于个人多年的实战，均提供原理讲解 && 源码落地

1. 分片上传实战
2. 通用并发处理工具类实战
3. 实现一个好用接口性能压测工具类
4. 超卖问题的4种解决方案，也是防止并发修改数据出错的通用方案
5. Semaphore实现接口限流实战
6. 并行查询，优化接口响应速度实战
7. 接口性能优化之大事务优化
8. 通用的Excel动态导出功能实战
9. 手写线程池管理器，管理&监控所有线程池
10. 动态线程池
11. SpringBoot实现动态Job实战
12. 并行查询，性能优化利器，可能有坑
13. 幂等的4种解决方案，吃透幂等性问题
14. 接口通用返回值设计与实现
15. 接口太多，各种dto、vo不计其数，如何命名？
16. 一个业务太复杂了，方法太多，如何传参？
17. 接口报错，如何快速定位日志？
18. 线程数据共享必学的3个工具类：ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal
19. 通过AOP统一打印请求链路日志，排错效率飞升
20. 大批量任务处理常见的方案（模拟余额宝发放收益）
21. 并发环境下，如何验证代码是否正常？
22. MySql和Redis数据一致性
23. SpringBoot数据脱敏优雅设计与实现
24. 一行代码搞定系统操作日志
25. Aop简化MyBatis分页功能
26. ThreadLocal 遇到线程池有大坑 & 通用解决方案
27. SpringBoot读写分离实战（一个注解搞定读写分离 && 强制路由主库）
28. MQ专题：MQ典型的7种使用场景
29. MQ专题：如何确保消息的可靠性
30. MQ专题：SpringBoot中，手把手教你实现事务消息
31. 手写一个好用的延迟任务处理工具类
32. MQ专题：延迟消息通用方案实战
33. MQ专题：消息幂等消费 & 消费失败自动重试通用方案 & 代码落地
34. MQ专题：顺序消息通用方案实战
35. MQ专题：消息积压问题
36. 分布式事务-MQ最终一致性-实现跨库转账（案例+源码+文档）
37. 分布式事务-MQ最终一致性-实现电商账户余额提现到微信钱包（案例+源码+文档）
38. 分布式事务：通用的TCC分布式事务生产级代码落地实战
39. 分布式锁详解
40. 分享一个特别好用的Redissson分布式锁工具类
41. 分布式锁：一个注解轻松实现布式锁
42. 微服务中如何传递上下文？实战
43. 接口幂等，通用方案 & 代码落地
44. 微服务链路日志追踪实战
45. 接口测试利器HTTP Client，不用Postman也可以
46. 封装MyBatis，实现通用无SQL版CRUD功能
47. MyBatisPlus 轻松实现 多租户数据隔离
48. 电商系统-资金账户表设计 及 应用实战
49. 开发者必须掌握的一款UML画图工具，画图效率飞升
50. 多线程事务，3秒插入百万数据
51. SpringBoot自动初始化数据库功能，太好用了
52. SpringBoot优雅停机
53. 分享一个特别好用的集合工具类，开发效率大幅提升
54. 性能调优：如何排查死锁？
55. 如何排查OOM？
56. cpu飙升，如何快速排查？
57. cpu飙升，使用Arthas，3秒定位问题
58. 接口响应慢，使用Arthas，3秒定位问题代码
59. 策略模式，轻松消除ifelse代码
60. 生产上，代码未生效，如何排查？
61. 使用MySQL，实现一个高性能，分布式id生成器
62. 方法执行异常，使用arthas，快速定位问题
63. 扫码登录详解
64. 使用hutool生成&解析二维码，太方便了
65. SpringBoot中，Redis如何实现排行榜功能？
66. SpringBoot中，Redis如何实现查找附近的人功能？
67. SpringBoot中，接口签名，通用方案，一次性搞懂
68. SpringBoot中，接口加解密，通用方案实战
69. 分库、分表、分库分表，如何选择？
70. 分库分表：分表字段如何选择？
71. 分库分表：分表数量为什么建议是2的n次方？
72. 分库分表：如何平滑迁移数据？
73. 并发编程有多难？值得反复研究的一个案例
74. 使用Redis Pipeline，接口性能提升10倍
75. 电商系统中，如何解决重复支付？
76. 千万级数据，全表update的正确姿势
77. 优雅实现树形菜单，适用于所有树，太好用了
78. 接口调用利器：RestTemplate，太好用了，吃透它
79. 微服务跨库查询，如何解决？
80. 逻辑删除与唯一约束冲突，如何解决？
81. 评论系统如何设计，一次性给你讲清楚
82. SpringBoot下载文件的几种方式，一次性搞懂
83. 订单超时自动取消，最常见的方案
84. 责任链模式优化代码，太好用了
85. CompletableFuture 实现异步任务编排，太好用了
86. idea中的必备debug技巧，高手必备
87. Java动态生成word，太强大了
88. 海量据量统计，如何提升性能？
89. MyBatis模糊查询，千万不要再用${}了，容易搞出大事故
90. Spring事务失效，常见的几种场景，带你精通Spring事务
91. idea多线程调试，这个技巧也太棒了吧，你会么？
92. MySQL排序分页，可能有坑，需要注意
93. 涉及到钱的，千万不要用double，请用BigDecimal
94. 更多实战案例详解