Bootstrap

为什么不用浮点型表示金额

首先论浮点数

单精度浮点数(float)在计算机存储器中占用4个字节(32 bits),利用“浮点”(浮动小数点)的方法,可以表示一个范围很大的数值。

比起单精度浮点数,双精度浮点数(double)使用 64 位(8字节) 来存储一个浮点数。

由于计算机中保存的小数其实是十进制的小数的近似值,并不是准确值,在实际应用中,需要对更大或者更小的数进行运算和处理。所以,千万不要在代码中使用浮点数来表示金额等重要的指标。

建议使用BigDecimal或者Long(单位为分)来表示金额。
float和double只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal【强烈推荐】。

-java.math.BigDecimal

BigDecimal是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。

BigDecimal的构造方法

BigDecimal(int) 创建一个具有参数所指定整数值的对象。
BigDecimal(double) 创建一个具有参数所指定双精度值的对象。(不建议采用)
BigDecimal(long) 创建一个具有参数所指定长整数值的对象。
BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象。

关于为什么不建议采用BigDecimal(double)构造方法

1.让我们看看四个构造分别打印的内容
在这里插入图片描述
打印结果
在这里插入图片描述

JDK的描述:

1、参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。2、另一方面,String 构造方法是完全可预知的:写入 newBigDecimal(“0.1”) 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用String构造方法。

当必须使用double类型时

当必须使用double类型时,请使用 String.valueOf(double)Double.toString(double);

BigDecimal的数学运算

对于常用的加,减,乘,除,BigDecimal类提供了相应的成员方法。

public BigDecimal add(BigDecimal value); //加法
public BigDecimal subtract(BigDecimal value); //减法
public BigDecimal multiply(BigDecimal value); //乘法
public BigDecimal divide(BigDecimal value); //除法

示例:在这里插入图片描述
运行结果
在这里插入图片描述

除法运算divide方法

BigDecimal除法可能出现不能整除的情况,比如 6.0/2.1,这时会报错

java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

例如:
在这里插入图片描述
在这里插入图片描述
其实divide方法有可以传三个参数:

public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)

看下源码

/**
    * Returns a new {@code BigDecimal} whose value is {@code this / divisor}.
    * As scale of the result the parameter {@code scale} is used. If rounding
    * is required to meet the specified scale, then the specified rounding mode
    * {@code roundingMode} is applied.
    *
    * @param divisor
    *            value by which {@code this} is divided.
    * @param scale
    *            the scale of the result returned.
    * @param roundingMode
    *            rounding mode to be used to round the result.
    * @return {@code this / divisor} rounded according to the given rounding
    *         mode.
    * @throws NullPointerException
    *             if {@code divisor == null}.
    * @throws IllegalArgumentException
    *             if {@code roundingMode} is not a valid rounding mode.
    * @throws ArithmeticException
    *             if {@code divisor == 0}.
    * @throws ArithmeticException
    *             if {@code roundingMode == ROUND_UNNECESSARY} and rounding is
    *             necessary according to the given scale.
    */
   public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) {
       return divide(divisor, scale, RoundingMode.valueOf(roundingMode));
   }

第一参数表示除数, 第二个参数表示小数点后保留位数,第三个参数表示舍入模式,只有在作除法运算或四舍五入时才用到舍入模式。

divide的舍入模式

有下面这几种:
是BigDecimal类下的常量

ROUND_CEILING // 向正无穷方向舍入 ROUND_DOWN // 向零方向舍入 ROUND_HALF_DOWN //
向(距离)最近的一边舍入,除非两边(的距离)是相等的,如果是这样,向下舍入(例如1.55保留一位小数为1.5)
ROUND_HALF_EVEN //
向(距离)最近的一边舍入,除非两边(的距离)是相等的,如果是这样,如果保留位数是奇数,使用ROUND_HALF_UP,如果是偶数,使用ROUND_HALF_DOWN
ROUND_HALF_UP //
向(距离)最近的一边舍入,除非两边(的距离)是相等的,如果是这样,向上舍入(例如1.55保留一位小数为1.6)
ROUND_UNNECESSARY // 计算结果是精确的,不需要舍入模式 ROUND_UP // 向远离0的方向舍入

;