⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.im/post/5c08db5ff265da611e4d7417 「何甜甜在吗」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

公司最近在做交易系统,交易系统肯定是要和钱打交道的,和钱有关,自然而然很容易想到用float存储,但是使用float存储金额做的计算是近似计算。老板,用float做计算造成公司损失的钱都往你工资里扣

哼,扣工资就扣工资。但还是得静下心来想想为什么不能用float

为什么不能使用float存储金额

首先看个例子:FloatTest.java

public class FloatTest {
public static void main(String[] args) {
float f1 = 6.6f;
float f2 = 1.3f;
System.out.println(f1 + f2);
}
}

结果:7.8999996 和自己口算的值竟然不一样

image.png

计算机只认识0和1,所有类型的计算首先会转化为二进制的计算

从计算机二进制角度计算 6.6 + 1.3 的过程

float底层存储

计算是由CPU来完成的,CPU表示浮点数由三部分组成 分为三个部分,符号位(sign),指数部分(exponent)和有效部分(fraction, mantissa)。 其中float总共占用32位,符号位,指数部分,有效部分各占1位,8位,23位

二进制的转化

对于实数,转化为二进制分为两部分,第一部分整数部分,第二部分是小数部分。整数部分计算二进制大家都很熟悉。

整数部分的计算:6转化为二进制

除以2 结果 小数部分
6 3 0
3 1 1
1 0 1

所以6最终的二进制为110

小数部分的计算

将小数乘以2,取整数部分作为二进制的值,然后再将小数乘以2,再取整数部分,以此往复循环

0.6转化为二进制

乘以2 整数部分 小数部分
1.2 1 0.2
0.4 0 0.4
0.8 0 0.8
1.6 1 0.6
1.2 1 0.2

...进入循环,循环体为1001 所以0.6转化为二进制为0.10011001... 6.6转化为二进制为110.10011001...

规约化

通过规约化将小数转为规约形式,类似科学计数法,就是保证小数点前面有一个有效数字。在二进制里面,就是保证整数位是一个1。110.10011001规约化为:1.1010011001*2^2

指数偏移值

指数偏移值 = 固定值 + 规约化的指数值 固定值=2^(e-1)-1,其中的e为存储指数部分的比特位数,前面提到的float为8位。所以float中规定化值为127 6.6的二进制值规约化以后为1.1010011001*2^2,指数是2,所以偏移值就是127+2=129,转换为二进制就是10000001,

拼接6.6

6.6为正数,符号位为0,指数部分为偏移值的二进制10000001,有效部分为规约形式的小数部分,取小数的前23位即10100110011001100110011,最后拼接到一起即 01000000110100110011001100110011 到这里已经大致可以知道float为什么不精确了,首先在存储的时候就会造成精度损失了,在这里小数部分的二进制是循环的,但是仍然只能取前23位。double造成精度损失的原因也是如此

求和

原来如此

不能使用float那用什么类型存储金额?

  • 使用int 数据库存储的是金额的分值,显示的时候在转化为元

  • 使用decimal mysql中decimal存储类型的使用

    column_name  decimal(P,D);

    D:代表小数点后的位数 P:有效数字数的精度,小数点也算一位 测试例子 数据表的创建:

     CREATE TABLE `test_decimal` (
    `id` int(11) NOT NULL,
    `amount` decimal(10,2) NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

    对应的DAO层代码:TestDecimalDao.java

    /**
    * @description dao层
    *
    * @author JoyHe
    * @date 2018/11/05
    * @version 1.0
    */
    @Repository
    public interface TestDecimalDao {
    @Select("select * from test_decimal where id = #{id}")
    TestDecimal getTestDecimal(int id);
    }

    测试类:TestDecimalDaoTest.java

    /**
    * @description 测试类
    *
    * @author JoyHe
    * @date 2018/11/05
    * @version 1.0
    */
    public class TestDecimalDaoTest extends BaseTest {
    @Resource
    private TestDecimalDao testDecimalDao;

    @Test
    public void test() {
    TestDecimal testDecimal1 = testDecimalDao.getTestDecimal(1);
    TestDecimal testDecimal2 = testDecimalDao.getTestDecimal(2);
    BigDecimal result = testDecimal1.getAmount().add(testDecimal2.getAmount());
    System.out.println(result.floatValue());
    }
    }

    说明:jdbcType为decimal转化为javaType为BigDecimal 测试结果:

    image.png

    是符合预期的7.9

使用decimal存储类型的缺点

  • 占用存储空间。浮点类型在存储同样范围的值时,通常比decimal使用更少的空间
  • 使用decimal计算效率不高

因为使用decimal时间和空间开销较大,选用int作为数据库存储格式比较合适,可以同时避免浮点存储计算的不精确和decimal的缺点。对于存储数值较大或者保留小数较多的数字,数据库存储结构可以选择bigint,可以同时避免浮点存储计算不精准和DECIMAL精度计算代价高的问题

文章目录
  1. 1. 为什么不能使用float存储金额
  2. 2. 从计算机二进制角度计算 6.6 + 1.3 的过程
    1. 2.1. float底层存储
    2. 2.2. 二进制的转化
      1. 2.2.0.1. 整数部分的计算:6转化为二进制
      2. 2.2.0.2. 小数部分的计算
      3. 2.2.0.3. 0.6转化为二进制
  3. 2.3. 规约化
  4. 2.4. 指数偏移值
  5. 2.5. 拼接6.6
  6. 2.6. 求和
  • 3. 不能使用float那用什么类型存储金额?
  • 4. 使用decimal存储类型的缺点