关于商业数据的运算问题

Java中的简单浮点数类型float和double不能够进行运算,或者运算会丢失精度,不光是Java,在其它很多编程语言中也有这样的问题。在大多数情况下,计算的结果是准确的,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用 java.math.BigDecimal,要注意该构造函数是一个精确的转换,它无法得到与先调用Double.toString(double)方法将double转换成String,再使用BigDecimal(String)构造函数一样的结果。如果要达到这种结果,应该使用new BigDecimal(Stringvalue) 或 BigDecimal.valueof( double value)。

金额转千分位及中文显示

保证精度的思路

先用BigDecimal对数据进行保留位数处理,将返回的数据通过DecimalFormat做千分位处理。
DecimalFormat 在使用小数保留位数时,须用:
setRoundingMode方法:默认保留2位 + 保留规则。
setScale方法:可指定保留位数 + 保留规则。

商业计算高精确结果,指定去尾规则保留2位小数位数

对结果精度要求高,使用:BigDecimal
四舍五入并保留两位有效数字
BigDecimal a = BigDecimal.valueOf(2.235);
//RoundingMode可指定多种去尾规则
BigDecimal  b =a.setScale(2, RoundingMode.HALF_UP);//四舍五入后并保留两位小数
System.out.println("结果:"+b);

千分位财务显示

DecimalFormat中#和0的区别:
#用于整数部分没有数位的限制,用于小数部分表示最多只能有多少位小数的意思,多余的进行舍入。
eg:
	data:02345.50
	pattern:",###.##"
	result:2,345.5
0:限制整数部分最少出现的位数;不足这个位数的,补0
eg:
	data:34.5
	pattern:"0,000.00"
	result:0,034.50
	
根据需求将#和0搭配使用
//保留两位小数并展示千分位符
DecimalFormat df1 = new DecimalFormat("##,##0.00");
System.out.println(df1.format(0.22));// 0.22

DecimalFormat不准确问题 做保留两位小数时特殊情况

DecimalFormat df = new DecimalFormat("#0.00");
有个差异点需要注意:
System.out.println(df.format(0.235));// 显示0.23
System.out.println(df.format(0.2351));// 显示0.24, 因为0.2351距离0.24更近。

示例代码:

double d_qualityGuaranteeDeposit = MoneyConvertUtils.number2Precision(Double.valueOf(qualityGuaranteeDeposit));
				newContract.setQualityGuaranteeDeposit(d_qualityGuaranteeDeposit);//转保留两位有效数字
				newContract.setQualityGuaranteeDepositForShow(MoneyConvertUtils.number2ShowStr(BigDecimal.valueOf(d_qualityGuaranteeDeposit)));//double转千分位
				newContract.setIsQualification(MoneyConvertUtils.number2CNMontrayUnit(BigDecimal.valueOf(d_qualityGuaranteeDeposit)));//double转中文


/**
 * 金额转换
 * 
 * @author Admin
 *
 */
public class MoneyConvertUtils {

	/**
	 * 汉语中数字大写
	 */
	private static final String[] CN_UPPER_NUMBER = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" };
	/**
	 * 汉语中货币单位大写,这样的设计类似于占位符
	 */
	private static final String[] CN_UPPER_MONETRAY_UNIT = { "分", "角", "元", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾",
			"佰", "仟", "兆", "拾", "佰", "仟" };
	/**
	 * 特殊字符:整
	 */
	private static final String CN_FULL = "整";
	/**
	 * 特殊字符:负
	 */
	private static final String CN_NEGATIVE = "负";
	/**
	 * 金额的精度,默认值为2
	 */
	private static final int MONEY_PRECISION = 2;
	/**
	 * 特殊字符:零元整
	 */
	private static final String CN_ZEOR_FULL = "零元" + CN_FULL;
	
	/**
	 * 金额保留两位小数,并转千分位
	 * 
	 * @param dl:double类型金额
	 * @return
	 */
	public static Double number2Precision(Double dl) {
		BigDecimal a = BigDecimal.valueOf(dl);
		BigDecimal b = a.setScale(MONEY_PRECISION, RoundingMode.HALF_UP);
		DecimalFormat df1 = new DecimalFormat("##,##0.00");
		return Double.parseDouble(df1.format(b.toString()));
	}

	/**
	 * 金额保留两位小数,并转千分位
	 * 
	 * @param dl:double类型金额
	 * @return
	 */
	public static String number2ShowStr(BigDecimal numberOfMoney) {
		BigDecimal b = numberOfMoney.setScale(MONEY_PRECISION, RoundingMode.HALF_UP);// 四舍五入并保留两位小数
		DecimalFormat df1 = new DecimalFormat("##,##0.00");
		System.out.println(df1.format(b.doubleValue()));// 0.22
		return b.toString();
	}

	/**
	 * 金额转RMB大写
	 * 
	 * @param dl:double类型金额
	 * @return
	 */
	public static String number2CNMontrayUnit(BigDecimal numberOfMoney) {
		StringBuffer sb = new StringBuffer();
		// -1, 0, or 1 as the value of this BigDecimal is negative, zero, or
		// positive.
		int signum = numberOfMoney.signum();
		// 零元整的情况
		if (signum == 0) {
			return CN_ZEOR_FULL;
		}
		// 这里会进行金额的四舍五入
		long number = numberOfMoney.movePointRight(MONEY_PRECISION).setScale(0, 4).abs().longValue();
		// 得到小数点后两位值
		long scale = number % 100;
		int numUnit = 0;
		int numIndex = 0;
		boolean getZero = false;
		// 判断最后两位数,一共有四中情况:00 = 0, 01 = 1, 10, 11
		if (!(scale > 0)) {
			numIndex = 2;
			number = number / 100;
			getZero = true;
		}
		if ((scale > 0) && (!(scale % 10 > 0))) {
			numIndex = 1;
			number = number / 10;
			getZero = true;
		}
		int zeroSize = 0;
		while (true) {
			if (number <= 0) {
				break;
			}
			// 每次获取到最后一个数
			numUnit = (int) (number % 10);
			if (numUnit > 0) {
				if ((numIndex == 9) && (zeroSize >= 3)) {
					sb.insert(0, CN_UPPER_MONETRAY_UNIT[6]);
				}
				if ((numIndex == 13) && (zeroSize >= 3)) {
					sb.insert(0, CN_UPPER_MONETRAY_UNIT[10]);
				}
				sb.insert(0, CN_UPPER_MONETRAY_UNIT[numIndex]);
				sb.insert(0, CN_UPPER_NUMBER[numUnit]);
				getZero = false;
				zeroSize = 0;
			} else {
				++zeroSize;
				if (!(getZero)) {
					sb.insert(0, CN_UPPER_NUMBER[numUnit]);
				}
				if (numIndex == 2) {
					if (number > 0) {
						sb.insert(0, CN_UPPER_MONETRAY_UNIT[numIndex]);
					}
				} else if (((numIndex - 2) % 4 == 0) && (number % 1000 > 0)) {
					sb.insert(0, CN_UPPER_MONETRAY_UNIT[numIndex]);
				}
				getZero = true;
			}
			// 让number每次都去掉最后一个数
			number = number / 10;
			++numIndex;
		}
		// 如果signum == -1,则说明输入的数字为负数,就在最前面追加特殊字符:负
		if (signum == -1) {
			sb.insert(0, CN_NEGATIVE);
		}
		// 输入的数字小数点后两位为"00"的情况,则要在最后追加特殊字符:整
		if (!(scale > 0)) {
			sb.append(CN_FULL);
		}
		return sb.toString();
	}

}

解决100.80 显示一百元零捌角的问题

//整数部分的人民币大写
private static final String[] NUMBERS = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"};
//数位部分
private static final String[] IUNIT = {"元", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾", "佰", "仟", "兆", "拾", "佰", "仟"};
//小数部分的人民币大写
private static final String[] DUNIT = {"角", "分", "厘"};

private static final String CN_FULL = "整";
private static final String CN_NEGATIVE = "负";//特殊字符:负
private static final int MONEY_PRECISION = 2;
private static final String CN_ZEOR_FULL = "零元" + CN_FULL;

//转成中文的大写金额
public static String toChinese(String str) {
    //判断输入的金额字符串
    if ("0".equals(str) || "0.00".equals(str) || "0.0".equals(str)) {
        return CN_ZEOR_FULL;
    }

    //判断是否存在负号"-"
    boolean flag = false;
    if (str.startsWith("-")) {
        flag = true;
        str = str.replaceAll("-", "");
    }
    //如果输入字符串中包含逗号,替换为 "."
    str = str.replaceAll(",", ".");

    String integerStr;//整数部分数字
    String decimalStr;//小数部分数字

    //分离整数部分和小数部分
    if (str.indexOf(".") > 0) {//整数部分和小数部分
        integerStr = str.substring(0, str.indexOf("."));
        decimalStr = str.substring(str.indexOf(".") + 1);
    } else if (str.indexOf(".") == 0) {//只存在小数部分 .34
        integerStr = "";
        decimalStr = str.substring(1);
    } else { //只存在整数部分 34
        integerStr = str;
        decimalStr = "";
    }

    //整数部分超出计算能力,直接返回
    if (integerStr.length() > IUNIT.length) {
        System.out.println(str + ":超出计算能力");
        return str;
    }

    //整数部分存入数组 目的是为了可以动态的在字符串数组中取对应的值
    int[] integers = toIntArray(integerStr);

    //判断整数部分是否存在输入012的情况
    if (integers.length > 1 && integers[0] == 0) {
        System.out.println("抱歉,请输入数字!");
        if (flag) {
        str = "-" + str;
        }
        return str;
    }
    boolean isWan = isWanUnits(integerStr);//设置万单位

    //小数部分数字存入数组
    int[] decimals = toIntArray(decimalStr);

    String result = getChineseInteger(integers, isWan) + getChineseDecimal(decimals);//返回最终的大写金额

    if (flag) {
        return CN_NEGATIVE + result;//如果是负数,加上"负"
    } else {
        return result;
    }
}

//将字符串转为int数组
private static int[] toIntArray(String number) {
    //初始化一维数组长度
    int[] array = new int[number.length()];
    //循环遍历赋值
    for (int i = 0; i < number.length(); i++) {
    array[i] = Integer.parseInt(number.substring(i, i + 1));
    }
    return array;
}

//将整数部分转为大写的金额
public static String getChineseInteger(int[] integers, boolean isWan) {
    StringBuffer chineseInteger = new StringBuffer("");
    int length = integers.length;
    // 对于输入的字符串为 "0." 存入数组后为 0
    if (length == 1 && integers[0] == 0) {
        return "";
    }
    for (int i = 0; i < length; i++) {
        String key = "";//0325464646464
        if (integers[i] == 0) {
        if ((length - i) == 13)//万(亿)
            key = IUNIT[4];
        else if ((length - i) == 9) {//亿
            key = IUNIT[8];
        } else if ((length - i) == 5 && isWan) {//万
            key = IUNIT[4];
        } else if ((length - i) == 1) {//元
            key = IUNIT[0];
        }
        if ((length - i) > 1 && integers[i + 1] != 0) {
            key += NUMBERS[0];
        }
        }
        chineseInteger.append(integers[i] == 0 ? key : (NUMBERS[integers[i]] + IUNIT[length - i - 1]));
    }
    return chineseInteger.toString();
}

//将小数部分转为大写的金额
private static String getChineseDecimal(int[] decimals) { //角 分 厘 038 壹分捌厘
    StringBuffer chineseDecimal = new StringBuffer("");
		String flag0 = "";
		for (int i = 0; i < decimals.length; i++) {
			if (i == 3) {
				break;
			}
			chineseDecimal.append(decimals[i] == 0 ? "" : (NUMBERS[decimals[i]] + DUNIT[i]));
		}
		System.out.println("chineseDecimal:" + chineseDecimal);
		if("".equals(chineseDecimal.toString())){
			//如果小数部分为00或者不存在时部位 整
			chineseDecimal.append(CN_FULL);
		}else if (chineseDecimal.indexOf("角") != -1 && chineseDecimal.indexOf("分") == -1){
			// 有角没分
			chineseDecimal.append(CN_FULL);
		}
		return flag0 + chineseDecimal.toString();
}

//判断当前整数部分是否已经是达到【万】
private static boolean isWanUnits(String integerStr) {
    int length = integerStr.length();
    if (length > 4) {
        String subInteger = "";
        if (length > 8) {
            subInteger = integerStr.substring(length - 8, length - 4);
        } else {
            subInteger = integerStr.substring(0, length - 4);
        }
        return Integer.parseInt(subInteger) > 0;
    } else {
        return false;
    }
}

调用:
double dl = -508.2;
BigDecimal a = BigDecimal.valueOf(dl);
BigDecimal b = a.setScale(MONEY_PRECISION, RoundingMode.HALF_UP);
String afterStr = toChinese(b.toString());
System.out.println(b.toString() + ": " + afterStr);