Invalid double崩潰分析

第一次遇到

java.lang.NumberFormatException: Invalid double: "0,3"
這樣包含逗號(hào)的浮點(diǎn)數(shù)異常,第一感覺就是服務(wù)器給的數(shù)據(jù)錯(cuò)誤,但前段時(shí)間復(fù)現(xiàn)了這個(gè)異常,才發(fā)現(xiàn)是代碼不規(guī)范導(dǎo)致了這樣的異常: 崩潰的詳細(xì)log如下:

E/AndroidRuntime: FATAL EXCEPTION: mainProcess: com.frank.lollipopdemo, PID: 
10898java.lang.RuntimeException: Unable to start activity 
ComponentInfo{com.frank.lollipopdemo/com.frank.lollipopdemo.MainActivity}: 
java.lang.NumberFormatException: Invalid double: "0,3" at 
android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2325) at 
android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387) at 
android.app.ActivityThread.access$800(ActivityThread.java:151) at 
android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303) at 
android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:135) at 
android.app.ActivityThread.main(ActivityThread.java:5254) at java.lang.reflect.Method.invoke(Native 
Method) at java.lang.reflect.Method.invoke(Method.java:372) at 
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903) at 
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698) Caused by: 
java.lang.NumberFormatException: Invalid double: "0,3" at 
java.lang.StringToReal.invalidReal(StringToReal.java:63) at 
java.lang.StringToReal.initialParse(StringToReal.java:164) at 
java.lang.StringToReal.parseDouble(StringToReal.java:282) at 
java.lang.Double.parseDouble(Double.java:301) at 
com.frank.lollipopdemo.MainActivity.onCreate(MainActivity.java:43) at 
android.app.Activity.performCreate(Activity.java:5990) at 
android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106) at 
android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278) at 
android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387) at 
android.app.ActivityThread.access$800(ActivityThread.java:151) at 
android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303) at 
android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:135) at 
android.app.ActivityThread.main(ActivityThread.java:5254) at java.lang.reflect.Method.invoke(Native 
Method) at java.lang.reflect.Method.invoke(Method.java:372) at 
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903) at
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

而出錯(cuò)的代碼是這樣的:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_title = (TextView) findViewById(R.id.tv_title);
        tv_content = (TextView) findViewById(R.id.tv_content);
        String jsonVal = "0.31415926";
        String title = remain1Places(jsonVal);// -> 0.3
        tv_title.setText(title);
        tv_content.setText(getPercent(Double.parseDouble(title)));// -> ##.##%
    }

    public static String remain1Places(String string) {
        if (TextUtils.isEmpty(string)) {
            return "";
        }
        return String.format("%.1f", Double.parseDouble(string));
    }

    public static String getPercent(double value) {
        NumberFormat numberFormat = NumberFormat.getPercentInstance();
        numberFormat.setMinimumFractionDigits(1);
        numberFormat.setMaximumFractionDigits(2);
        return numberFormat.format(value);
    }

想把浮點(diǎn)數(shù)”0.31415926”保留一位小數(shù),就使用了String.format()
方法完成,而且完全不顧編譯器對(duì)我們的建議:

這里寫圖片描述
之后,我們想把保留了一位小數(shù)后的浮點(diǎn)數(shù)”0.3”轉(zhuǎn)成百分比的形式”30.0%”,當(dāng)然先轉(zhuǎn)成double,然后利用NumberFormat
格式化成百分比。 看似沒有問題,但是當(dāng)我們把系統(tǒng)語言換成法語(France)之后,程序直接Crash,而且當(dāng)我們只顯示remain1Places()
后的title時(shí),發(fā)現(xiàn)”0.31415926”變成了”0,3”,而不是”0.3”,這就直接導(dǎo)致了Double.parseDouble(“0,3”)拋出一個(gè)數(shù)據(jù)格式異常。 那為什么String.format()
會(huì)將小數(shù)點(diǎn)的”句點(diǎn)(.)“換成了”逗號(hào)(,)“呢?
摘自維基百科: A decimal mark is a symbol used to separate the integer part from the fractional part of a number written in decimal form. Different countries officially designate different symbols for the decimal mark. The choice of symbol for the decimal mark also affects the choice of symbol for the thousands separator used in digit grouping, so the latter is also treated in this article.
這里寫圖片描述

可以看到,有很多地區(qū)都是用逗號(hào)(,)作為小數(shù)點(diǎn)的,如19,90€表示19.9歐元;但計(jì)算機(jī)程序中的浮點(diǎn)數(shù)必須用句點(diǎn)(.)作為小數(shù)點(diǎn),如double price = 19.90;
表示浮點(diǎn)數(shù)19.9。所以在使用double的包裝類Double
的parseDouble(String)
或valueOf(String)
方法將字符串表示的double值轉(zhuǎn)成double時(shí),字符串所表示的double值必須是用句點(diǎn)(.)分隔的浮點(diǎn)數(shù),也就是計(jì)算機(jī)的浮點(diǎn)數(shù)表示形式。
Double.valueOf(String)
方法僅僅調(diào)用了Double.parseDouble(String)
并返回Double
對(duì)象。 Double.parseDouble(String)
方法返回原語類型的double
變量。

因此,我們就可以斷定這是一個(gè)本地化(Locale)的問題了?,F(xiàn)在再來看一下編譯器(lint)給我們String.format()
方法的建議:
Implicitly using the default locale is a common source of bugs: Use String.format(Locale, …) instead. 隱式地使用默認(rèn)的區(qū)域設(shè)置是常見Bug源,請(qǐng)使用String.format(Locale, …)等方法替換它。

也就是說,String.format(String format, Object... args)
會(huì)調(diào)用format(Locale.getDefault(), format, args)
使用用戶默認(rèn)的區(qū)域設(shè)置返回格式化好且本地化好的字符串,因用戶設(shè)置的不同而返回不同的字符串,進(jìn)而出現(xiàn)Bug。如果你只是想格式化字符串而不是人為干預(yù),應(yīng)該用

String.format(Locale locale, String format, Object... args)
方法,Locale參數(shù)用Locale.US
就可以了。
因此,我們應(yīng)該重視本地化問題:
將字符串所有字符轉(zhuǎn)為大/小寫的方法String.toLowerCase()
/String.toUpperCase()
并不一定能將字符真正的大/小寫(如區(qū)域設(shè)置為土耳其時(shí),i大寫后還是i),因此應(yīng)該指定要使用的區(qū)域設(shè)置String.toLowerCase(Locale locale)
/String.toUpperCase(Locale locale)

格式化字符串的方法String.format(String format, Object... args)
應(yīng)該指定區(qū)域設(shè)置,以避免區(qū)域設(shè)置變化導(dǎo)致的Bug。
千萬不要將數(shù)字format()
成字符串后再將該字符串轉(zhuǎn)回原語類型
,因?yàn)閒ormat()
后的字符串可能不是合法的原語類型的數(shù)字了。即永遠(yuǎn)不要出現(xiàn)類似這樣

Double.parseDouble(new DecimalFormat("#.##").format(doubleValue))
的代碼。
建議使用NumberFormat
,如:

public static String remain1Places(String str) {
    NumberFormat numberFormat = NumberFormat.getInstance(Locale.getDefault());
    numberFormat.setMinimumFractionDigits(1);
    numberFormat.setMaximumFractionDigits(1);
    return numberFormat.format(Double.parseDouble(str));
}

public static String getPercent(String str) {
    NumberFormat numberFormat = NumberFormat.getPercentInstance(Locale.getDefault());
    numberFormat.setMinimumFractionDigits(1);
    numberFormat.setMaximumFractionDigits(2);
    return numberFormat.format(Double.parseDouble(str));
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評(píng)論 19 139
  • 1.在C/C++中實(shí)現(xiàn)本地方法 生成C/C++頭文件之后,你就需要寫頭文件對(duì)應(yīng)的本地方法。注意:所有的本地方法的第...
    JayQiu閱讀 2,518評(píng)論 0 3
  • 在JavaSe5中,推出了C語言中printf()風(fēng)格的格式化輸出。這不僅使得控制輸出的代碼更加簡單,同時(shí)也給與J...
    三藏君閱讀 848評(píng)論 0 0
  • “你要是還要臉 就不要再見他” ...
    文藝胖貓閱讀 508評(píng)論 0 1
  • 我在路這邊 靜靜地等著 以為你會(huì)把我尋找 然...
    一葦可航閱讀 340評(píng)論 0 1

友情鏈接更多精彩內(nèi)容