一、背景
阿裡技術的公衆發了一篇文章《誰是代碼界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
顯然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