Rounds the given double value at the given precision and writes the rounded value into the given target This method internally uses the String representation of the source value, in order to avoid any double precision computation error. - Java java.lang

Java examples for java.lang:double

Description

Rounds the given double value at the given precision and writes the rounded value into the given target This method internally uses the String representation of the source value, in order to avoid any double precision computation error.

Demo Code

/*//  w  w  w.  j a v  a  2  s . co 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.
 */
//package com.java2s;

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];

    /**
     * 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);
                }
            }
        }
    }

    /**
     * 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);
    }

    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);
    }
}

Related Tutorials