一、背景
阿里技术的公众发了一篇文章《谁是代码界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