Java Double Number Format formatDouble(double source, int decimals, int precision)

Here you can find the source of formatDouble(double source, int decimals, int precision)

Description

format Double

License

Apache License

Declaration

public static String formatDouble(double source, int decimals, int precision) 

Method Source Code

//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) &ge; 1.0)
     * @param precision
     *            the precision to round at (use if abs(source) &lt; 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) &ge; 1.0)
     * @param precision
     *            the precision to round at (use if abs(source) &lt; 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) &ge; 1.0)
     * @param precision
     *            the precision to round at (use if abs(source) &lt; 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);
    }
}

Related

  1. formatDouble(double d, int n, String pad)
  2. formatDouble(double d, int precision)
  3. formatDouble(double inDouble, boolean inComma, int inCommaPos)
  4. formatDouble(double num, int width, int precision)
  5. FormatDouble(double p_value, int p_numberOfDecimalPlaces)
  6. formatDouble(double v, int decimalPlaces)
  7. formatDouble(double val)
  8. formatDouble(double val)
  9. formatDouble(double val, int prec)