All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.intelie.pipes.util.DoubleUtils Maven / Gradle / Ivy

There is a newer version: 0.25.5
Show newest version
package net.intelie.pipes.util;

import java.util.List;

public abstract class DoubleUtils {
    /**
     * 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];

    static {
        POWERS_OF_TEN_LONG[0] = 1L;
        for (int i = 1; i < POWERS_OF_TEN_LONG.length; i++) {
            POWERS_OF_TEN_LONG[i] = POWERS_OF_TEN_LONG[i - 1] * 10L;
        }
        for (int i = 0; i < POWERS_OF_TEN_DOUBLE.length; i++) {
            POWERS_OF_TEN_DOUBLE[i] = Double.parseDouble("1e" + i);
        }
    }

    public static String format(double value) {
        StringBuilder builder = new StringBuilder();
        formatDouble(value, 10, 10, builder);
        return builder.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, StringBuilder 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);
        }
    }

    /**
     * Rounds the given source 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. * * @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, StringBuilder 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); removeTrailingZeroes(target); } 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 (!appendZero(digits < 0, target)) { 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); } } } } } public static boolean appendZero(boolean condition, StringBuilder target) { if (condition) target.append('0'); return condition; } public static void removeTrailingZeroes(StringBuilder target) { for (int l = target.length() - 1; l >= 0 && target.charAt(l) == '0'; l--) { target.setLength(l); } } /** * 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); } /** * 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); } /** * 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(StringBuilder 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); } } /** * Rounds the given source value at the given precision * and writes the rounded value into the given target *

* 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, StringBuilder 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 (!appendZero(intPart == 0L && fracPart == 0L, target)) { // 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); } } } /** * 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 } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy