Java中实现浮点数的快速简单格式化

最近在公司的系统调优中,发现对double类型数据格式化为字符串运算,占用了不少CPU时间(系统为超高并发和超快响应的应用)。

代码中使用的格式化工具为:NumberFormat,一般的使用方式是:

NumberFormat f = NumberFormat.getNumberInstance();
...
f.format(value);

对于double,会用DecimalFormat实例来处理,默认是保留3位小数,且尾零删除(不要求对齐,这样可减少返回字符的长度)。

本来格式化应该不必耗费太多的计算,但由于DecimalFormat要考虑通用性,以及同步处理,因此有较多性能损耗。(具体可参见JDK7的java.text.DecimalFormat的源代码)

因此自己实现一种简单快速的格式工具,优点:

  • 能够达到性能比DecimalFormat至少好一倍的效果;
  • 且精度不会丢失(因为计算机数据表示的原因,以浮点数表示实数,在某些情况下会造成精度丢失)。

局限性:

  • 不提供丰富的格式化模式,而只是将double转化可阅读的最短字符串(如果不做移除尾零处理效率可能会更高些)。

格式化举例:

3.145926---->(保留4位小数后的字符)3.1459

3.224450---->(保留4位小数后的字符)3.2245,注:DecimalFormat会格式化为3.2244!

1.999964---->(保留4位小数后的字符)2

0---->(保留4位小数后的字符)0

相关工具:

  • 主要是Math工具类的abs,nextUp,pow等函数(JDK1.6以后Math工具本身也做了性能的改进);
  • 对于超大数据或超高精度,转化为BigDecimal处理(此时性能就与DecimalFormat持平);
  • 对于字符串操作改为使用char[]数组进行,以便进一步减少操作提高性能。

性能测试结果:

环境:Win7 64位 i5-3320 8G内存 JDK7u40

Precision-0 and Loop times-1000000:
NumberFormat time cost(ms): 884
Fast Format time cost(ms):  188
BigDecimal time cost(ms): 482
Precision-1 and Loop times-1000000:
NumberFormat time cost(ms): 727
Fast Format time cost(ms):  180
BigDecimal time cost(ms): 485
Precision-2 and Loop times-1000000:
NumberFormat time cost(ms): 473
Fast Format time cost(ms):  164
BigDecimal time cost(ms): 516
Precision-3 and Loop times-1000000:
NumberFormat time cost(ms): 509
Fast Format time cost(ms):  157
BigDecimal time cost(ms): 487
Precision-4 and Loop times-1000000:
NumberFormat time cost(ms): 521
Fast Format time cost(ms):  159
BigDecimal time cost(ms): 480


源代码:

	private final static char[][] LEADING_DECIMALS = new char[][] { "0.".toCharArray(), "0.0".toCharArray(),
			"0.00".toCharArray(), "0.000".toCharArray(), "0.0000".toCharArray(), "0.00000".toCharArray(),
			"0.000000".toCharArray(), "0.0000000".toCharArray(), "0.00000000".toCharArray(),
			"0.000000000".toCharArray(), "0.0000000000".toCharArray(), "0.00000000000".toCharArray(),
			"0.000000000000".toCharArray(), "0.0000000000000".toCharArray(), "0.00000000000000".toCharArray(),
			"0.000000000000000".toCharArray() };

	/**
	 * 快速格式化一个double,尾零去除(非对齐)<br>
	 * 等同于:<br>
	 * NumberFormat f = NumberFormat.getNumberInstance();<br>
	 * f.setGroupingUsed(false);<br>
	 * f.setMaximumFractionDigits(precision);<br>
	 * f.format(d);<br>
	 * 但一般情况效率高于NumberFormat一倍,且精度无丢失。<br>
	 * 
	 * @param d
	 *            the double value
	 * @param precision
	 *            [0,16]
	 * @return
	 * @see NumberFormat
	 */
	public static String fastFormat(double d, int precision) {
		int posPrecision = Math.abs(precision);
		double roundUpVal = Math.abs(d) * Math.pow(10d, posPrecision) + 0.5d;
		if (roundUpVal > 999999999999999d || posPrecision > 16) {// double has max 16 precisions
			return bigDecFormat(d, posPrecision);
		}
		long longPart = (long) Math.nextUp(roundUpVal);
		if (longPart < 1) {
			return "0";
		}
		char[] longPartChars = Long.toString(longPart).toCharArray();
		char[] formatChars = null;
		if (longPartChars.length > posPrecision) {
			int end = longPartChars.length - 1;
			int decIndex = longPartChars.length - posPrecision;
			while (end >= decIndex && longPartChars[end] == '0') {
				end--;
			}
			if (end >= decIndex) {
				formatChars = new char[end + 2];
				System.arraycopy(longPartChars, 0, formatChars, 0, decIndex);
				formatChars[decIndex] = '.';
				System.arraycopy(longPartChars, decIndex, formatChars, decIndex + 1, end - decIndex + 1);
			} else {
				formatChars = new char[decIndex];
				System.arraycopy(longPartChars, 0, formatChars, 0, decIndex);
			}
		} else {
			int end = longPartChars.length - 1;
			while (end >= 0 && longPartChars[end] == '0') {
				end--;
			}
			char[] leadings = LEADING_DECIMALS[posPrecision - longPartChars.length];
			formatChars = Arrays.copyOf(leadings, leadings.length + end + 1);
			System.arraycopy(longPartChars, 0, formatChars, leadings.length, end + 1);
		}
		return Math.signum(d) > 0 ? new String(formatChars) : "-" + new String(formatChars);
	}

	private static String bigDecFormat(double d, int precision) {
		String formatStr = new BigDecimal(Double.toString(d)).setScale(Math.abs(precision), RoundingMode.HALF_UP)
				.toString();
		if (precision == 0) {
			return formatStr;
		}
		int end = formatStr.length() - 1;
		while (end >= 0 && formatStr.charAt(end) == '0') {
			end--;
		}
		formatStr = formatStr.substring(0, end + 1);
		if (formatStr.charAt(formatStr.length() - 1) == '.') {
			formatStr = formatStr.substring(0, formatStr.length() - 1);
		}
		return formatStr;
	}


你可能感兴趣的:(Java中实现浮点数的快速简单格式化)