Here you can find the source of formatDouble(double source, int decimals, int precision)
public static String formatDouble(double source, int decimals, int precision)
//package com.java2s; /*// w ww . ja va 2 s . c o m * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with this * work for additional information regarding copyright ownership. The ASF * licenses this file to You under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ public class Main { /** * Most used power of ten (to avoid the cost of Math.pow(10, n) */ private static final long[] POWERS_OF_TEN_LONG = new long[19]; private static final double[] POWERS_OF_TEN_DOUBLE = new double[30]; public static String formatDouble(double source, int decimals, int precision) { StringBuffer target = new StringBuffer(); int scale = (Math.abs(source) >= 1.0) ? decimals : precision; if (tooManyDigitsUsed(source, scale) || tooCloseToRound(source, scale)) { formatDoublePrecise(source, decimals, precision, target); } else { formatDoubleFast(source, decimals, precision, target); } return target.toString(); } /** * Rounds the given source value at the given precision and writes the * rounded value into the given target * * @param source * the source value to round * @param decimals * the decimals to round at (use if abs(source) ≥ 1.0) * @param precision * the precision to round at (use if abs(source) < 1.0) * @param target * the buffer to write to */ public static void formatDouble(double source, int decimals, int precision, StringBuffer target) { int scale = (Math.abs(source) >= 1.0) ? decimals : precision; if (tooManyDigitsUsed(source, scale) || tooCloseToRound(source, scale)) { formatDoublePrecise(source, decimals, precision, target); } else { formatDoubleFast(source, decimals, precision, target); } } /** * Returns true if the rounding is considered to use too many digits of the * double for a fast rounding * * @param source * the source to round * @param scale * the scale to round at * @return true if the rounding will potentially use too many digits */ private static boolean tooManyDigitsUsed(double source, int scale) { // if scale >= 308, 10^308 ~= Infinity double decExp = Math.log10(source); return scale >= 308 || decExp + scale >= 14.5; } /** * Returns true if the given source is considered to be too close of a * rounding value for the given scale. * * @param source * the source to round * @param scale * the scale to round at * @return true if the source will be potentially rounded at the scale */ private static boolean tooCloseToRound(double source, int scale) { source = Math.abs(source); long intPart = (long) Math.floor(source); double fracPart = (source - intPart) * tenPowDouble(scale); double decExp = Math.log10(source); double range = decExp + scale >= 12 ? .1 : .001; double distanceToRound1 = Math.abs(fracPart - Math.floor(fracPart)); double distanceToRound2 = Math.abs(fracPart - Math.floor(fracPart) - 0.5); return distanceToRound1 <= range || distanceToRound2 <= range; // .001 range: Totally arbitrary range, // I never had a failure in 10e7 random tests with this value // May be JVM dependent or architecture dependent } /** * Rounds the given source value at the given precision and writes the * rounded value into the given target * <p> * This method internally uses the String representation of the source * value, in order to avoid any double precision computation error. * * @param source * the source value to round * @param decimals * the decimals to round at (use if abs(source) ≥ 1.0) * @param precision * the precision to round at (use if abs(source) < 1.0) * @param target * the buffer to write to */ public static void formatDoublePrecise(double source, int decimals, int precision, StringBuffer target) { if (isRoundedToZero(source, decimals, precision)) { // Will always be rounded to 0 target.append('0'); return; } else if (Double.isNaN(source) || Double.isInfinite(source)) { // Cannot be formated target.append(Double.toString(source)); return; } boolean negative = source < 0.0; if (negative) { source = -source; // Done once and for all target.append('-'); } int scale = (source >= 1.0) ? decimals : precision; // The only way to format precisely the double is to use the String // representation of the double, and then to do mathematical integer // operation on it. String s = Double.toString(source); if (source >= 1e-3 && source < 1e7) { // Plain representation of double: "intPart.decimalPart" int dot = s.indexOf('.'); String decS = s.substring(dot + 1); int decLength = decS.length(); if (scale >= decLength) { if ("0".equals(decS)) { // source is a mathematical integer target.append(s.substring(0, dot)); } else { target.append(s); // Remove trailing zeroes for (int l = target.length() - 1; l >= 0 && target.charAt(l) == '0'; l--) { target.setLength(l); } } return; } else if (scale + 1 < decLength) { // ignore unnecessary digits decLength = scale + 1; decS = decS.substring(0, decLength); } long intP = Long.parseLong(s.substring(0, dot)); long decP = Long.parseLong(decS); format(target, scale, intP, decP); } else { // Scientific representation of double: "x.xxxxxEyyy" int dot = s.indexOf('.'); assert dot >= 0; int exp = s.indexOf('E'); assert exp >= 0; int exposant = Integer.parseInt(s.substring(exp + 1)); String intS = s.substring(0, dot); String decS = s.substring(dot + 1, exp); int decLength = decS.length(); if (exposant >= 0) { int digits = decLength - exposant; if (digits <= 0) { // no decimal part, // no rounding involved target.append(intS); target.append(decS); for (int i = -digits; i > 0; i--) { target.append('0'); } } else if (digits <= scale) { // decimal part precision is lower than scale, // no rounding involved target.append(intS); target.append(decS.substring(0, exposant)); target.append('.'); target.append(decS.substring(exposant)); } else { // decimalDigits > scale, // Rounding involved long intP = Long.parseLong(intS) * tenPow(exposant) + Long.parseLong(decS.substring(0, exposant)); long decP = Long.parseLong(decS.substring(exposant, exposant + scale + 1)); format(target, scale, intP, decP); } } else { // Only a decimal part is supplied exposant = -exposant; int digits = scale - exposant + 1; if (digits < 0) { target.append('0'); } else if (digits == 0) { long decP = Long.parseLong(intS); format(target, scale, 0L, decP); } else if (decLength < digits) { long decP = Long.parseLong(intS) * tenPow(decLength + 1) + Long.parseLong(decS) * 10; format(target, exposant + decLength, 0L, decP); } else { long subDecP = Long.parseLong(decS.substring(0, digits)); long decP = Long.parseLong(intS) * tenPow(digits) + subDecP; format(target, scale, 0L, decP); } } } } /** * Rounds the given source value at the given precision and writes the * rounded value into the given target * <p> * This method internally uses double precision computation and rounding, so * the result may not be accurate (see formatDouble method for conditions). * * @param source * the source value to round * @param decimals * the decimals to round at (use if abs(source) ≥ 1.0) * @param precision * the precision to round at (use if abs(source) < 1.0) * @param target * the buffer to write to */ public static void formatDoubleFast(double source, int decimals, int precision, StringBuffer target) { if (isRoundedToZero(source, decimals, precision)) { // Will always be rounded to 0 target.append('0'); return; } else if (Double.isNaN(source) || Double.isInfinite(source)) { // Cannot be formated target.append(Double.toString(source)); return; } boolean isPositive = source >= 0.0; source = Math.abs(source); int scale = (source >= 1.0) ? decimals : precision; long intPart = (long) Math.floor(source); double tenScale = tenPowDouble(scale); double fracUnroundedPart = (source - intPart) * tenScale; long fracPart = Math.round(fracUnroundedPart); if (fracPart >= tenScale) { intPart++; fracPart = Math.round(fracPart - tenScale); } if (fracPart != 0L) { // Remove trailing zeroes while (fracPart % 10L == 0L) { fracPart = fracPart / 10L; scale--; } } if (intPart != 0L || fracPart != 0L) { // non-zero value if (!isPositive) { // negative value, insert sign target.append('-'); } // append integer part target.append(intPart); if (fracPart != 0L) { // append fractional part target.append('.'); // insert leading zeroes while (scale > 0 && fracPart < tenPowDouble(--scale)) { target.append('0'); } target.append(fracPart); } } else { target.append('0'); } } private static double tenPowDouble(int n) { assert n >= 0; return n < POWERS_OF_TEN_DOUBLE.length ? POWERS_OF_TEN_DOUBLE[n] : Math.pow(10, n); } /** * Returns true if the given source value will be rounded to zero * * @param source * the source value to round * @param decimals * the decimals to round at (use if abs(source) ≥ 1.0) * @param precision * the precision to round at (use if abs(source) < 1.0) * @return true if the source value will be rounded to zero */ private static boolean isRoundedToZero(double source, int decimals, int precision) { // Use 4.999999999999999 instead of 5 since in some cases, 5.0 / 1eN > // 5e-N (e.g. for N = 37, 42, 45, 66, ...) return source == 0.0 || Math.abs(source) < 4.999999999999999 / tenPowDouble(Math.max(decimals, precision) + 1); } /** * Helper method to do the custom rounding used within formatDoublePrecise * * @param target * the buffer to write to * @param scale * the expected rounding scale * @param intP * the source integer part * @param decP * the source decimal part, truncated to scale + 1 digit */ private static void format(StringBuffer target, int scale, long intP, long decP) { if (decP != 0L) { // decP is the decimal part of source, truncated to scale + 1 digit. // Custom rounding: add 5 decP += 5L; decP /= 10L; if (decP >= tenPowDouble(scale)) { intP++; decP -= tenPow(scale); } if (decP != 0L) { // Remove trailing zeroes while (decP % 10L == 0L) { decP = decP / 10L; scale--; } } } target.append(intP); if (decP != 0L) { target.append('.'); // Use tenPow instead of tenPowDouble for scale below 18, // since the casting of decP to double may cause some imprecisions: // E.g. for decP = 9999999999999999L and scale = 17, // decP < tenPow(16) while (double) decP == tenPowDouble(16) while (scale > 0 && (scale > 18 ? decP < tenPowDouble(--scale) : decP < tenPow(--scale))) { // Insert leading zeroes target.append('0'); } target.append(decP); } } /** * Returns ten to the power of n * * @param n * the nth power of ten to get * @return ten to the power of n */ public static long tenPow(int n) { assert n >= 0; return n < POWERS_OF_TEN_LONG.length ? POWERS_OF_TEN_LONG[n] : (long) Math.pow(10, n); } }