java数字人民币转大写的方法及注意事项

最近做需求,需要用到数字人民币转大写的方法,项目里面本身就有相应的工具方法,我就直接拿来用,结果到了生产上就有bug,原来是double类型十进制转二进制后小数点后的精度问题。于是自己总结写了一个比较健全的,顺便把对应的坑贴一下,提醒自己
先上正确的代码,需要的同学可以直接拿走,想要了解注意事项的同学往代码下面翻

public static String digitCapital(double n) {
   	String fraction[] = {"角", "分"};
    String digit[] = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", 	"玖"};
    String unit[][] = {{"元", "万", "亿"}, {"", "拾", "佰", "仟"}};
    String head = n < 0 ? "负" : "";
    // 如果是负数取绝对值
    n = Math.abs(n);
    String s = "";
    BigDecimal bigDecimal = null;
    String value = String.valueOf(n);
    if(value.contains("E")){
        bigDecimal = new BigDecimal(n);
    }else{
        bigDecimal = new BigDecimal(value);
    }
    String nStr = bigDecimal.toString();
    // 小数部分
    String[] split = nStr.split("\\.");
    if (split.length > 1) {
        // 小数点为特殊符号,在分割时需进行转义
        String decimalStr = split[1];
        if (decimalStr.length() > 2) {
            decimalStr = decimalStr.substring(0, 2);
        }
        // 将小数部分转换为整数
        Integer integer = Integer.valueOf(decimalStr);
        String p = "";
        for (int i = 0; i < decimalStr.length() && i < fraction.length; i++) {
            p = digit[integer % 10] + fraction[decimalStr.length() - i - 1] + p;
            integer = integer / 10;
        }
        s = p.replaceAll("(零.)+", "") + s;
    }
    if (s.length() < 1) {
        s = "整";
    }
    int integerPart = (int)Math.floor(n);
    // 整数部分
    for (int i = 0; i < unit[0].length && integerPart > 0; i++) {
        String p = "";
        for (int j = 0; j < unit[1].length && n > 0; j++) {
            p = digit[integerPart % 10] + unit[1][j] + p;
            integerPart = integerPart / 10;
        }
        s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s;
    }
    return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整");
}

接下来说一下注意事项:
1、网上大多数是直接使用double类型,没有转bigDecimal,这样就把double类型十进制转二进制精度丢失的天然属性问题带进来了,比如会把 “82602.65” 转成 “捌万贰仟陆佰零贰元陆角肆分” ,底层机制分析一下。
先按常规的十进制转二进制逻辑来一遍(稍后再反着转一遍看得更明白)

 (1) 十进制整数如何转化为二进制数

       算法很简单。82602表示成二进制数:

                   82602/2=41301   余   0
                   41301/2=20650   余   1
                   20650/2=10325   余   0
                   10325/2=5162    余   1
                   5162/2=2581     余   0
                   2581/2=1290     余   1
                   1290/2=645      余   0
                   645/2=322       余   1
                   322/2=161       余   0
                   161/2=80    	   余   1
                   80/2=40         余   0
                   40/2=20         余   0
                   20/2=10         余   0
                   10/2=5          余   0
                   5/2=2           余   1
                   2/2=1  		   余   0
                   1/2=0   		   余   1

                      0结束      82602二进制表示为(从下往上):10100001010101010

      这里提一点:只要遇到除以后的结果为0了就结束了,大家想一想,
      所有的整数除以2是不是一定能够最终得到0。
      换句话说,所有的整数转变为二进制数的算法会不会无限循环下去呢?
      绝对不会,整数永远可以用二进制精确表示 ,但小数就不一定了。

  (2) 十进制小数如何转化为二进制数

       算法是乘以2直到没有了小数为止。0.65表示成二进制数

                 0.65*2=1.3   取整数部分 1
                 0.3(1.3的小数部分)*2=0.6    取整数部分 0
                 0.6*2=1.2   取整数部分 1
                 0.2*2=0.4   取整数部分 0
                 0.4*2=0.8   取整数部分 0
                 0.8*2=1.6   取整数部分 1
                 0.6*2=1.2   取整数部分 1(这个时候开始了循环)

   .........      0.65二进制表示为(从上往下): 101001100110011......

       注意:上面的计算过程循环了,也就是说*2永远不可能消灭小数部分,
       这样算法将无限下去。很显然,小数的二进制表示有时是不可能精确的 。
       其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统
       也无法准确表示1/10。这也就解释了为什么浮点型减法出现了"减不尽"的
       精度丢失问题。
       我们得把10100001010101010.101001100110011.…放进一个 double 双精度浮点数里面
	双精度浮点数能表示多少精度呢?查看文档会发现:
	半精度(16bit):11位有效数字
	单精度(32bit):24位有效数字
	双精度(64bit):53位有效数字
	四精度(128bit):113位有效数字
好吧,双精度是53位有效数字,那么上面十进制的82602.65在计算机中的
二进制就是10100001010101010.10100110011001100110011001100110011
       然后我们再把二进制转换回十进制,整数部分不赘述还是82602;
       小数部分是1*2^-1+0*2^-2+1*2^-3+0*2^-4+0*2^-5+1*2^-6+1*2^-7
       							   +0*2^-8+0*2^-9+1*2^-10+1*2^-11
       								+0*2^-12+0*2^-13+1*2^-14+1*2^-15
       								+0*2^-16+0*2^-17+1*2^-18+1*2^-19
       								+0*2^-20+0*2^-21+1*2^-22+1*2^-23
       								+0*2^-24+0*2^-25+1*2^-26+1*2^-27
       								+0*2^-28+0*2^-29+1*2^-30+1*2^-31
       								+0*2^-32+0*2^-33+1*2^-34+1*2^-35
       
     得到了十进制小数0.6499999999941792,这好好的82602.65就变成了
     82602.6499999999941792,转换成中文大写会自动丢弃分之后的数据,
     所以就少了一分钱。

2、double类型在大数据时会采用科学计数法表示数值。
比如150000000就会使用1.5E8来表示,如果这里直接使用Double.toString()方法,它真的就会变成字符串“1.5E8”,后面就会直接报错了

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
//ChangeRMB.java /** * * programmed by HuangHeliang * 2009.04.15 10:20:51 * */ //package com.avtech.hhl; import java.io.*; public final class ChangeRMB { //每个数字对应的大写 private static final String[] num = { "", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖", }; //从低到高排列的单位 private static final String[] bit = { "圆", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿" }; //金额里面的角和分 private static final String[] jf={ "角","分" }; /** * 处理金额整数部分,返回"...圆整" * @param integer * @return String * @throws Exception */ public static String praseUpcaseRMB(String integer)throws Exception{ StringBuilder sbdr=new StringBuilder(""); int j=integer.length(); if(j>bit.length){ throw new Exception("\n只能处理亿万亿以内的数据(含亿万亿)!"); } char[] rmb=integer.toCharArray(); for (int i = 0; i 壹佰亿陆仟伍佰万肆仟伍佰捌拾叁圆伍分 */ if(bit[bitLocate].equals("仟")){ String s=sbdr.toString(); if(!s.endsWith(bit[bitLocate+1]) && s.length()>0){ if (s.endsWith(num[0])) { sbdr.deleteCharAt(sbdr.length() - 1); } sbdr.append(bit[bitLocate+1]); } } sbdr.append(num[numLocate]); sbdr.append(bit[bitLocate]); }//end for /* * 去掉结尾""后,补全 */ if(sbdr.toString().endsWith(num[0])){ sbdr.deleteCharAt(sbdr.length()-1); sbdr.append("圆整"); }else{ sbdr.append("整"); } return sbdr.toString(); } /** * 处理带小数的金额,整数部分交由上一个方法处理,小数部分自己处理 * @param integer * @param decimal * @return String * @throws Exception */ public static String praseUpcaseRMB(String integer, String decimal)throws Exception{ String ret=ChangeRMB.praseUpcaseRMB(integer); ret=ret.split("整")[0]; //处理整数部分 StringBuilder sbdr=new StringBuilder(""); sbdr.append(ret); char[] rmbjf=decimal.toCharArray(); for(int i=0;i rmbDouble){ theInt-=1; } double theDecimal=rmbDouble-theInt; String integer=new Long((long)theInt).toString(); String decimal=""+Math.round(theDecimal*100); if(decimal.equals("0")){ result=ChangeRMB.praseUpcaseRMB(integer); }else{ result=ChangeRMB.praseUpcaseRMB(integer, decimal); } return result; } public static void main(String[] args) throws Exception{ System.out.print("输入小写人民币金额:"); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String in = br.readLine(); String result=ChangeRMB.doChangeRMB(in); System.out.println("\n"+"------------换结果------------"); System.out.println(result); double d=54628569856.68; String ret=ChangeRMB.doChangeRMB(d); System.out.println("\n"+"------------换结果------------"); System.out.println(ret); } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值