谁是代码界3%的王者?- 第四题BigDecimal问题简单解读

简介: 谁是代码界3%的王者?- 第四题BigDecimal问题简单解读

一、背景

阿里技术的公众发了一篇文章《谁是代码界3%的王者?》,


提到“在Java代码界,有些陷阱外表看起来是个青铜实际上是王者,据说97%工程师会被“秒杀””


给出了五道题,非常考验基础。


本文简单解读第4题,并分享通用的学习和研究方法。



二、题目

题目配套代码


public class BigDecimalTest {

   public static void main(String[] args) {

       BigDecimal a = new BigDecimal(0.1);

       System.out.println(a);

       BigDecimal b = new BigDecimal("0.1");

       System.out.println(b);

   }

}

题目内容


下列哪种说法是正确的:


A: 两种赋值的方式是一样的

B: 推荐a的赋值方式

C: 推荐b的赋值方式


先公布答案:C


三、分析

3.1 直接运行看效果

上面源代码输出的效果如下


0.1000000000000000055511151231257827021181583404541015625

0.1


显然b是我们想要的效果



3.2 源代码大法

java.math.BigDecimal#BigDecimal(java.lang.String)
  /**
     * Translates the string representation of a {@code BigDecimal}
     * into a {@code BigDecimal}.  The string representation consists
     * of an optional sign, {@code '+'} ( '\u002B') or
     * {@code '-'} ('\u002D'), followed by a sequence of
     * zero or more decimal digits ("the integer"), optionally
     * followed by a fraction, optionally followed by an exponent.
     *
     * 
The fraction consists of a decimal point followed by zero
     * or more decimal digits.  The string must contain at least one
     * digit in either the integer or the fraction.  The number formed
     * by the sign, the integer and the fraction is referred to as the
     * significand.
     *
     * 
The exponent consists of the character {@code 'e'}
     * ('\u0065') or {@code 'E'} ('\u0045')
     * followed by one or more decimal digits.  The value of the
     * exponent must lie between -{@link Integer#MAX_VALUE} ({@link
     * Integer#MIN_VALUE}+1) and {@link Integer#MAX_VALUE}, inclusive.
     *
     * 
More formally, the strings this constructor accepts are
     * described by the following grammar:
     * 
     * 
     * BigDecimalString:
     * Signopt Significand Exponentopt
     * Sign:
     * {@code +}
     * {@code -}
     * Significand:
     * IntegerPart {@code .} FractionPartopt
     * {@code .} FractionPart
     * IntegerPart
     * IntegerPart:
     * Digits
     * FractionPart:
     * Digits
     * Exponent:
     * ExponentIndicator SignedInteger
     * ExponentIndicator:
     * {@code e}
     * {@code E}
     * SignedInteger:
     * Signopt Digits
     * Digits:
     * Digit
     * Digits Digit
     * Digit:
     * any character for which {@link Character#isDigit}
     * returns {@code true}, including 0, 1, 2 ...
     * 
     * 
     *
     * 
The scale of the returned {@code BigDecimal} will be the
     * number of digits in the fraction, or zero if the string
     * contains no decimal point, subject to adjustment for any
     * exponent; if the string contains an exponent, the exponent is
     * subtracted from the scale.  The value of the resulting scale
     * must lie between {@code Integer.MIN_VALUE} and
     * {@code Integer.MAX_VALUE}, inclusive.
     *
     * 
The character-to-digit mapping is provided by {@link
     * java.lang.Character#digit} set to convert to radix 10.  The
     * String may not contain any extraneous characters (whitespace,
     * for example).
     *
     * 
Examples:
     * The value of the returned {@code BigDecimal} is equal to
     * significand × 10 exponent.
     * For each string on the left, the resulting representation
     * [{@code BigInteger}, {@code scale}] is shown on the right.
     * 
     * "0"            [0,0]
     * "0.00"         [0,2]
     * "123"          [123,0]
     * "-123"         [-123,0]
     * "1.23E3"       [123,-1]
     * "1.23E+3"      [123,-1]
     * "12.3E+7"      [123,-6]
     * "12.0"         [120,1]
     * "12.3"         [123,1]
     * "0.00123"      [123,5]
     * "-1.23E-12"    [-123,14]
     * "1234.5E-4"    [12345,5]
     * "0E+7"         [0,-7]
     * "-0"           [0,0]
     * 
     *
     * 
Note: For values other than {@code float} and
     * {@code double} NaN and ±Infinity, this constructor is
     * compatible with the values returned by {@link Float#toString}
     * and {@link Double#toString}.  This is generally the preferred
     * way to convert a {@code float} or {@code double} into a
     * BigDecimal, as it doesn't suffer from the unpredictability of
     * the {@link #BigDecimal(double)} constructor.
     *
     * @param val String representation of {@code BigDecimal}.
     *
     * @throws NumberFormatException if {@code val} is not a valid
     *         representation of a {@code BigDecimal}.
     */
    public BigDecimal(String val) {
        this(val.toCharArray(), 0, val.length());
    }
人家都怕你不仔细看给了你那么多示例,还专门给了一个note
This is generally the preferred way to convert a {@code float} or {@code double} into a BigDecimal, as it doesn't suffer from the unpredictability of the {@link #BigDecimal(double)} constructor.
此构造函数是float或double转到BigDecimal的推荐方式,因为该构造方法不会像BigDecimal(double)一样会有一些不可预测的情况。
它最终调用了java.math.BigDecimal#BigDecimal(char[], int, int) 感兴趣大家可以自己去看。
我们再看另外一个构造函数
java.math.BigDecimal#BigDecimal(double)
 /**
     * Translates a {@code double} into a {@code BigDecimal} which
     * is the exact decimal representation of the {@code double}'s
     * binary floating-point value.  The scale of the returned
     * {@code BigDecimal} is the smallest value such that
     * (10scale × val) is an integer.
     * 
     * Notes:
     * 
     * 
     * The results of this constructor can be somewhat unpredictable.
     * One might assume that writing {@code new BigDecimal(0.1)} in
     * Java creates a {@code BigDecimal} which is exactly equal to
     * 0.1 (an unscaled value of 1, with a scale of 1), but it is
     * actually equal to
     * 0.1000000000000000055511151231257827021181583404541015625.
     * This is because 0.1 cannot be represented exactly as a
     * {@code double} (or, for that matter, as a binary fraction of
     * any finite length).  Thus, the value that is being passed
     * in to the constructor is not exactly equal to 0.1,
     * appearances notwithstanding.
     *
     * 
     * The {@code String} constructor, on the other hand, is
     * perfectly predictable: writing {@code new BigDecimal("0.1")}
     * creates a {@code BigDecimal} which is exactly equal to
     * 0.1, as one would expect.  Therefore, it is generally
     * recommended that the {@linkplain #BigDecimal(String)
     * String constructor} be used in preference to this one.
     *
     * 
     * When a {@code double} must be used as a source for a
     * {@code BigDecimal}, note that this constructor provides an
     * exact conversion; it does not give the same result as
     * converting the {@code double} to a {@code String} using the
     * {@link Double#toString(double)} method and then using the
     * {@link #BigDecimal(String)} constructor.  To get that result,
     * use the {@code static} {@link #valueOf(double)} method.
     * 
     *
     * @param val {@code double} value to be converted to
     *        {@code BigDecimal}.
     * @throws NumberFormatException if {@code val} is infinite or NaN.
     */
    public BigDecimal(double val) {
        this(val,MathContext.UNLIMITED);
    }
专门提到
new BigDecimal(0.1)的结果是0.1000000000000000055511151231257827021181583404541015625.
This is because 0.1 cannot be represented exactly as a {@code double} (or, for that matter, as a binary fraction of any finite length). 
Thus, the value that is being passed in to the constructor is not exactly equal to 0.1, appearances notwithstanding.
这是因为double类型无法精确表示0.1。因此传入0.1参数到该构造方法其实并不精确等于0.1。
The {@code String} constructor, on the other hand, is perfectly predictable: writing {@code new BigDecimal("0.1")} creates a {@code BigDecimal} which is exactly equal to  0.1, as one would expect.  Therefore, it is generally recommended that the {@linkplain #BigDecimal(String String constructor} be used in preference to this one.
更推荐使用参数为String的构造方法,换句话说用BigDecimal("0.1")来构造完全等于0.1的BigDecimal。
因此,推荐带String参数的构造方法。
When a {@code double} must be used as a source for a {@code BigDecimal}, note that this constructor provides an exact conversion; it does not give the same result asconverting the {@code double} to a {@code String} using the {@link Double#toString(double)} method and then using the {@link #BigDecimal(String)} constructor.
如果必须把double作为构造方法的参数时,注意和new BigDecimal(Double.toString(0.1d))的结果是完全不同的。


因此答案就不言而喻了。


四、其他

4.1 双精度问题

计算机通过二进制来存储数据,双精度8字节(64位)的表示




其中第63索引位,共1位,表示符号位(sign bit),用s表示;0表示正数,1表示负数


第52到62索引位,共11位,表示指数(signed exponent),用e表示;2的多少次方


第51到0索引位(significant/mantissa value),共52位,表示小数部分,用m表示;有效位


浮点型:https://docs.oracle.com/cd/E19957-01/806-3568/ncg_math.html



十进制无法表示三分之一,二进制无法表示十分之一。


像三分之一一样,三分之一无法用有限个十进制数表示。10的-1次幂(0.1)不是有限个2的幂的和,所以不能用有限个2进制位表示,而double是8字节的,只有64位,是有限个二进制数,因此无法精确表示0.1。



五、启发

正如前面的几个问题解答中我提到的几个常见方法一样,这类问题我们最好的办法是看源码!看源码的注释!!


看官方文档!!看权威规范!!(如本文提到的《IEEE Arithmetic》的网页)。


另外一个启发是计算机专业基础要扎实!!!二进制要理解的透彻一些。


开发的时候尽量多去源码里看注释!!!




附录


《谁是代码界3%的王者?- 第三题switch问题简单解读》


《谁是代码界3%的王者?- 第五题Lock的简单解读》



 

————————————————

版权声明:本文为CSDN博主「明明如月学长」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/w605283073/article/details/93226572


相关文章
|
6月前
|
Java
【Java】如果一个集合中类型是String如何使用拉姆达表达式 进行Bigdecimal类型计算?
【Java】如果一个集合中类型是String如何使用拉姆达表达式 进行Bigdecimal类型计算?
115 0
|
6月前
|
存储 Java
你知道Java中的BigInteger类和BigDecimal类吗?
你知道Java中的BigInteger类和BigDecimal类吗?
|
Java
Java开发篇- 从BigDecimal的divide的异常说起
在过去做项目的某一天中,突然有小伙伴说两个BigDecimal的数据相除(divide)报错了,觉得不可能,然后问他是怎么编写的,他说很简单呀,就是new了2个BigDecimal,然后相除的结果赋值给另外一个BigDecimal对象。听起来觉得没有问题,正常来说,2个Integer(int),2个Double(double)都不会报错,然后问是什么异常,说是一个很奇怪的异常
432 0
Zp
|
Java
Java中BigDecimal保留两位小数
Java中BigDecimal保留两位小数
Zp
872 0
|
3月前
|
Java
【Java】Math、System、RunTime、BigDecimal类常用方法
【Java】Math、System、RunTime、BigDecimal类常用方法
|
5月前
|
Java
深入了解Java中的BigDecimal类及其方法
深入了解Java中的BigDecimal类及其方法
|
5月前
|
安全 Java
java中BigDecimal详解
java中BigDecimal详解
|
6月前
|
算法 Java API
java BigDecimal使用详细介绍
java BigDecimal使用详细介绍
java BigDecimal使用详细介绍
|
6月前
|
存储 Java
Java中BigDecimal怎样取反
在上述示例中,`number.negate()`会将BigDecimal对象 `number`的值取反,并将结果存储在新的BigDecimal对象 `negated`中。
134 0
|
存储 Java API
从零开始学习 Java:简单易懂的入门指南之Objects、BigInteger、BigDecimal(十四)
从零开始学习 Java:简单易懂的入门指南之Objects、BigInteger、BigDecimal(十四)