国际化(i18n)-数字

背景

平常在项目中,经常会遇到String和Double间的转换,或者将Double、Float等类型格式化后显示。当我们的项目是直接在国内使用的时候,通过String.format、Double.parseDouble等方法转换通常不会出现问题。但是如果产品延伸到国外,通过这种方式通常会抛出异常、结果与实际不符等情况。本篇将整理在其它语言环境下字符和数字在实际使用中遇到的问题和采用的解决办法。

语言编码

  • 国际化:internationalization,由于首尾字母间有18个字符,简称i18n
  • 本地化:localization,由于首尾字母间有10个字符,简称l10n
  • ISO-639 标准使用编码定义了国际上常见的语言,每一种语言由两个小写字母表示。
  • ISO-3166 标准使用编码定义了国家/地区,每个国家/地区由两个大写字母表示。

常用语言编码

国家/地区 语言编码
中文 zh
中文(简体) zh-CN
中文(香港) zh-HK
中文(澳门) zh-MO
英语 en
英语(美国) en-us
法语 fr
德语 de
日语 ja
韩语 ko
俄语 ru

数字格式化

使用Kotlin作为示例代码。没做特殊说明,都采用默认中文作为环境语言。

Double转String

使用String.format:

val doubleValue = 1258.6999999999
val doubleStr1="$doubleValue"
val doubleStr2=String.format("%f",doubleValue)
val doubleStr3=String.format("%1\$s",doubleValue)
//保留小数点后五位
val doubleStr4=String.format("%.3f",doubleValue)

当语言环境为中文时,结果:
doubleStr2和doubleStr4采用四舍五入的方式,对字符转进行了处理。


当语言环境为英文时,结果:
doubleStr2和doubleStr4采用四舍五入的方式,对字符转进行了处理。
千分位没有分隔符。

当语言环境为俄文时,结果:
doubleStr2和doubleStr4采用四舍五入的方式,对字符转进行了处理。
千分位没有分隔符。
doubleStr1和doubleStr3结果与俄文格式不符。

如果不考虑精度的情况从上面三种情况可以看出,将有小数的数字转换为字符串展现出来,需要使用 String.format("%f",doubleValue)String.format("%.3f",doubleValue)的方式。

使用NumberFormat:

使用NumberFormat,我们可以设置保留多少小数位,设置结果计算方式(RoundingMode)等

fun getDoubleStr(value: Double, digits: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.CHINA): String {
            try {
                val format = NumberFormat.getNumberInstance(locale)
                //等同于NumberFormat.getNumberInstance
                //val format = DecimalFormat.getNumberInstance(locale)
                //设置小数点后最小位数
                format.minimumFractionDigits = digits
                //设置小数点后最大位数
                format.maximumFractionDigits = digits
                format.roundingMode = roundingMode
                //format.isGroupingUsed=false  //取消整数位分隔符
                return format.format(value)
            } catch (e: Exception) {
                e.printStackTrace()
            }
            return "$value"
        }
 ...
 private val digits = 5
//输出中文字符串
val chineseStr = DoubleNationalUtil.getDoubleStr(doubleValue, digits, RoundingMode.DOWN,Locale.CHINA)
//输出俄文字符串
val russianStr = DoubleNationalUtil.getDoubleStr(doubleValue, digits, RoundingMode.DOWN,russianLocale)

结果:

使用DecimalFormat:

占位符有0和#,当使用0的时候会严格按照样式来进行匹配,不够的时候会补0,而使用#时会将前后的0进行忽略。
DecimalFormat还可以对展示的字符样式进行自定义,比如设置小数分隔符、整数位分隔符用什么符号表示(Char类型),这里不做补充说明。

        fun getDefaultDoubleStr(value: Double, partten: String, locale: Locale = Locale.CHINA): String {
            try {
                //val format = DecimalFormat(partten)
                val format = DecimalFormat.getNumberInstance(locale) as DecimalFormat
                format.applyPattern(partten)
                return format.format(value)
            } catch (e: Exception) {
                e.printStackTrace()
            }
            return "$value"
        }

...
        val defaultStr=DoubleNationalUtil.getDefaultDoubleStr(doubleValue,"#0.00")
        val defaultStr2=DoubleNationalUtil.getDefaultDoubleStr(doubleValue,"#0.00#",russianLocale)
        //设置整数位分隔符
        val defaultStr3=DoubleNationalUtil.getDefaultDoubleStr(doubleValue,"#,##0.00#")

结果:

String转Double

将String转换为Double,这里很容易抛出异常或结果与预期不一致的情况。比如:

  • 不同的语言间进行转换
  • 当前程序语言和系统语言不一致,使用Double. parseDouble方法

使用Double. parseDouble:

try {
        //chineseStr=1,258.69999
        java.lang.Double.parseDouble(chineseStr)
        //russianStr=1 258,69999
        java.lang.Double.parseDouble(russianStr)
    }catch (e:Exception){
        e.printStackTrace()
    }

这里不管是先转换中文String还是俄文String,结果都会抛出异常。
不管是中文环境下,还是俄文环境下,结果都会抛出异常。
所以不建议大家采用Double. parseDouble方法将String转换为Double

使用NumberFormat:

下面使用NumberFormat转换,查看语言不一致的输出结果。
为方便阅读,命名方式没有按照驼峰命名,请大家忽略。

fun getDouble(value: String, locale: Locale = Locale.CHINA): Double {
            try {
                val format = NumberFormat.getNumberInstance(locale)
                //等同于NumberFormat.getNumberInstance
                //val format = DecimalFormat.getNumberInstance(locale)
                return format.parse(value).toDouble()
            } catch (e: Exception) {
                e.printStackTrace()
            }
            //真实情况请勿返回0.0
            return 0.0
        }
...
        //输出中文格式化字符串
        val chineseStr = DoubleNationalUtil.getDoubleStr(doubleValue, digits, RoundingMode.DOWN,Locale.CHINA)
        //中文转中文double
        val chineseStr_chinese = DoubleNationalUtil.getDouble(chineseStr, Locale.CHINA)
        //中文转俄文double
        val chineseStr_russian = DoubleNationalUtil.getDouble(chineseStr, russianLocale)
        //中文转英语double
        val chineseStr_english = DoubleNationalUtil.getDouble(chineseStr, Locale.US)
        //输出俄文格式化字符串
        val russianStr = DoubleNationalUtil.getDoubleStr(doubleValue, digits, RoundingMode.DOWN,russianLocale)
        //俄文转中文double
        val russianStr_chinese = DoubleNationalUtil.getDouble(russianStr, Locale.CHINA)
        //俄文转俄文double
        val russianStr_russian = DoubleNationalUtil.getDouble(russianStr, russianLocale)
        //俄文转英语double
        val russianStr_english = DoubleNationalUtil.getDouble(russianStr, Locale.US)

结果:
只有语言相互对应,才能保证输出结果是正确的。

其它

涉及到小数或者金额的计算,请使用BigDecimal类。
喜欢本篇的朋友,不要忘记点赞哟。

你可能感兴趣的:(国际化(i18n)-数字)