首页 文章

如何在Java中将数字舍入到n个小数位

提问于
浏览
1067

我想要的是一种将double转换为使用half-up方法进行舍入的字符串的方法 - 即如果要舍入的小数是5,则它总是向上舍入到前一个数字 . 这是在大多数情况下舍入大多数人所期望的标准方法 .

我也希望只显示有效数字 - 即不应该有任何尾随零 .

我知道这样做的一种方法是使用 String.format 方法:

String.format("%.5g%n", 0.912385);

收益:

0.91239

这很好,但它总是显示5位小数的数字,即使它们不重要:

String.format("%.5g%n", 0.912300);

收益:

0.91230

另一种方法是使用 DecimalFormatter

DecimalFormat df = new DecimalFormat("#.#####");
df.format(0.912385);

收益:

0.91238

但是你可以看到它使用半均匀舍入 . 也就是说,如果前一个数字是偶数,它将向下舍入 . 我想要的是这样的:

0.912385 -> 0.91239
0.912300 -> 0.9123

在Java中实现这一目标的最佳方法是什么?

29 回答

  • 9

    我来到这里只是想要一个如何围绕数字的简单答案 . 这是提供这一点的补充答案 .

    如何在Java中舍入数字

    最常见的情况是使用 Math.round() .

    Math.round(3.7) // 4
    

    数字四舍五入到最接近的整数 . .5 值向上舍入 . 如果您需要不同的舍入行为,则可以使用其他Math函数之一 . 请参阅下面的比较 .

    如上所述,这舍入到最接近的整数 . .5 小数向上舍入 . 此方法返回 int .

    Math.round(3.0); // 3
    Math.round(3.1); // 3
    Math.round(3.5); // 4
    Math.round(3.9); // 4
    
    Math.round(-3.0); // -3
    Math.round(-3.1); // -3
    Math.round(-3.5); // -3 *** careful here ***
    Math.round(-3.9); // -4
    

    ceil

    任何十进制值都会向上舍入到下一个整数 . 它转到 ceil ing . 此方法返回 double .

    Math.ceil(3.0); // 3.0
    Math.ceil(3.1); // 4.0
    Math.ceil(3.5); // 4.0
    Math.ceil(3.9); // 4.0
    
    Math.ceil(-3.0); // -3.0
    Math.ceil(-3.1); // -3.0
    Math.ceil(-3.5); // -3.0
    Math.ceil(-3.9); // -3.0
    

    任何十进制值都向下舍入到下一个整数 . 此方法返回 double .

    Math.floor(3.0); // 3.0
    Math.floor(3.1); // 3.0
    Math.floor(3.5); // 3.0
    Math.floor(3.9); // 3.0
    
    Math.floor(-3.0); // -3.0
    Math.floor(-3.1); // -4.0
    Math.floor(-3.5); // -4.0
    Math.floor(-3.9); // -4.0
    

    rint

    这类似于round,十进制值四舍五入到最接近的整数 . 但是,与 round 不同, .5 值舍入为偶数 . 此方法返回 double .

    Math.rint(3.0); // 3.0
    Math.rint(3.1); // 3.0
    Math.rint(3.5); // 4.0 ***
    Math.rint(3.9); // 4.0
    Math.rint(4.5); // 4.0 ***
    Math.rint(5.5); // 6.0 ***
    
    Math.rint(-3.0); // -3.0
    Math.rint(-3.1); // -3.0
    Math.rint(-3.5); // -4.0 ***
    Math.rint(-3.9); // -4.0
    Math.rint(-4.5); // -4.0 ***
    Math.rint(-5.5); // -6.0 ***
    
  • 15

    假设 valuedouble ,您可以这样做:

    (double)Math.round(value * 100000d) / 100000d
    

    这是5位精度 . 零的数量表示小数位数 .

  • 405

    我同意所选答案使用 DecimalFormat ---或者 BigDecimal .

    请先阅读下面的 Update

    但是,如果您想要对double值进行舍入并获得double值结果,则可以使用上面提到的org.apache.commons.math3.util.Precision.round(..) . 实现使用BigDecimal,速度慢并且会产生垃圾 .

    decimal4j库中的DoubleRounder实用程序提供了类似但快速且无垃圾的方法:

    double a = DoubleRounder.round(2.0/3.0, 3);
     double b = DoubleRounder.round(2.0/3.0, 3, RoundingMode.DOWN);
     double c = DoubleRounder.round(1000.0d, 17);
     double d = DoubleRounder.round(90080070060.1d, 9);
     System.out.println(a);
     System.out.println(b);
     System.out.println(c);
     System.out.println(d);
    

    会输出

    0.667
     0.666
     1000.0
     9.00800700601E10
    

    https://github.com/tools4j/decimal4j/wiki/DoubleRounder-Utility

    免责声明:我参与了decimal4j项目 .

    Update: 正如@iaforek所指出的,DoubleRounder有时会返回违反直觉的结果 . 原因是它在数学上执行正确的舍入 . 例如 DoubleRounder.round(256.025d, 2) 将向下舍入到256.02,因为表示为256.025d的double值略小于有理值256.025,因此将向下舍入 .

    Notes:

    • 此行为与 BigDecimal(double) 构造函数的行为非常相似(但不使用 valueOf(double) ,它使用字符串构造函数) .

    • 首先可以通过双舍入步骤来避免问题,但是它很复杂,我不会在这里详细说明

    由于这些原因以及本文中上面提到的一切,我都是 cannot recommend to use DoubleRounder .

  • 0
    double myNum = .912385;
    int precision = 10000; //keep 4 digits
    myNum= Math.floor(myNum * precision +.5)/precision;
    
  • 3

    您可以使用DecimalFormat类 .

    double d = 3.76628729;
    
    DecimalFormat newFormat = new DecimalFormat("#.##");
    double twoDecimal =  Double.valueOf(newFormat.format(d));
    
  • 103

    由于我没有找到关于这个主题的完整答案,我已经整理了一个应该正确处理这个问题的课程,并支持:

    • Formatting :使用一定数量的小数位轻松地将double格式化为字符串

    • Parsing :将格式化的值解析为double

    • Locale :使用默认语言环境格式化和解析

    • Exponential notation :在某个阈值后开始使用指数表示法

    Usage is pretty simple

    (为了这个例子,我使用的是自定义语言环境)

    public static final int DECIMAL_PLACES = 2;
    
    NumberFormatter formatter = new NumberFormatter(DECIMAL_PLACES);
    
    String value = formatter.format(9.319); // "9,32"
    String value2 = formatter.format(0.0000005); // "5,00E-7"
    String value3 = formatter.format(1324134123); // "1,32E9"
    
    double parsedValue1 = formatter.parse("0,4E-2", 0); // 0.004
    double parsedValue2 = formatter.parse("0,002", 0); // 0.002
    double parsedValue3 = formatter.parse("3423,12345", 0); // 3423.12345
    

    Here is the class

    import java.math.RoundingMode;
    import java.text.DecimalFormat;
    import java.text.DecimalFormatSymbols;
    import java.text.ParseException;
    import java.util.Locale;
    
    public class NumberFormatter {
    
        private static final String SYMBOL_INFINITE           = "\u221e";
        private static final char   SYMBOL_MINUS              = '-';
        private static final char   SYMBOL_ZERO               = '0';
        private static final int    DECIMAL_LEADING_GROUPS    = 10;
        private static final int    EXPONENTIAL_INT_THRESHOLD = 1000000000; // After this value switch to exponential notation
        private static final double EXPONENTIAL_DEC_THRESHOLD = 0.0001; // Below this value switch to exponential notation
    
        private DecimalFormat decimalFormat;
        private DecimalFormat decimalFormatLong;
        private DecimalFormat exponentialFormat;
    
        private char groupSeparator;
    
        public NumberFormatter(int decimalPlaces) {
            configureDecimalPlaces(decimalPlaces);
        }
    
        public void configureDecimalPlaces(int decimalPlaces) {
            if (decimalPlaces <= 0) {
                throw new IllegalArgumentException("Invalid decimal places");
            }
    
            DecimalFormatSymbols separators = new DecimalFormatSymbols(Locale.getDefault());
            separators.setMinusSign(SYMBOL_MINUS);
            separators.setZeroDigit(SYMBOL_ZERO);
    
            groupSeparator = separators.getGroupingSeparator();
    
            StringBuilder decimal = new StringBuilder();
            StringBuilder exponential = new StringBuilder("0.");
    
            for (int i = 0; i < DECIMAL_LEADING_GROUPS; i++) {
                decimal.append("###").append(i == DECIMAL_LEADING_GROUPS - 1 ? "." : ",");
            }
    
            for (int i = 0; i < decimalPlaces; i++) {
                decimal.append("#");
                exponential.append("0");
            }
    
            exponential.append("E0");
    
            decimalFormat = new DecimalFormat(decimal.toString(), separators);
            decimalFormatLong = new DecimalFormat(decimal.append("####").toString(), separators);
            exponentialFormat = new DecimalFormat(exponential.toString(), separators);
    
            decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
            decimalFormatLong.setRoundingMode(RoundingMode.HALF_UP);
            exponentialFormat.setRoundingMode(RoundingMode.HALF_UP);
        }
    
        public String format(double value) {
            String result;
            if (Double.isNaN(value)) {
                result = "";
            } else if (Double.isInfinite(value)) {
                result = String.valueOf(SYMBOL_INFINITE);
            } else {
                double absValue = Math.abs(value);
                if (absValue >= 1) {
                    if (absValue >= EXPONENTIAL_INT_THRESHOLD) {
                        value = Math.floor(value);
                        result = exponentialFormat.format(value);
                    } else {
                        result = decimalFormat.format(value);
                    }
                } else if (absValue < 1 && absValue > 0) {
                    if (absValue >= EXPONENTIAL_DEC_THRESHOLD) {
                        result = decimalFormat.format(value);
                        if (result.equalsIgnoreCase("0")) {
                            result = decimalFormatLong.format(value);
                        }
                    } else {
                        result = exponentialFormat.format(value);
                    }
                } else {
                    result = "0";
                }
            }
            return result;
        }
    
        public String formatWithoutGroupSeparators(double value) {
            return removeGroupSeparators(format(value));
        }
    
        public double parse(String value, double defValue) {
            try {
                return decimalFormat.parse(value).doubleValue();
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return defValue;
        }
    
        private String removeGroupSeparators(String number) {
            return number.replace(String.valueOf(groupSeparator), "");
        }
    
    }
    
  • 30

    你也可以使用

    DecimalFormat df = new DecimalFormat("#.00000");
    df.format(0.912385);
    

    确保你有0的结尾 .

  • 8

    正如其他人所说,正确的答案是使用 DecimalFormatBigDecimal . 浮点没有小数位,因此您不可能首先将其舍入/截断为特定数量的小数位 . 你必须使用十进制基数,这就是这两个类的作用 .

    我发布了以下代码作为这个线程中所有答案的反例 - 实际上遍布StackOverflow(以及其他地方)建议乘法,然后是截断,然后是除法 . 这种技术的拥护者有责任解释为什么以下代码在超过92%的情况下产生错误的输出 .

    public class RoundingCounterExample
    {
    
        static float roundOff(float x, int position)
        {
            float a = x;
            double temp = Math.pow(10.0, position);
            a *= temp;
            a = Math.round(a);
            return (a / (float)temp);
        }
    
        public static void main(String[] args)
        {
            float a = roundOff(0.0009434f,3);
            System.out.println("a="+a+" (a % .001)="+(a % 0.001));
            int count = 0, errors = 0;
            for (double x = 0.0; x < 1; x += 0.0001)
            {
                count++;
                double d = x;
                int scale = 2;
                double factor = Math.pow(10, scale);
                d = Math.round(d * factor) / factor;
                if ((d % 0.01) != 0.0)
                {
                    System.out.println(d + " " + (d % 0.01));
                    errors++;
                }
            }
            System.out.println(count + " trials " + errors + " errors");
        }
    }
    

    该计划的输出:

    10001 trials 9251 errors
    

    EDIT: 为了解决下面的一些注释,我使用 BigDecimalnew MathContext(16) 为模数运算重新设置了测试循环的模数部分,如下所示:

    public static void main(String[] args)
    {
        int count = 0, errors = 0;
        int scale = 2;
        double factor = Math.pow(10, scale);
        MathContext mc = new MathContext(16, RoundingMode.DOWN);
        for (double x = 0.0; x < 1; x += 0.0001)
        {
            count++;
            double d = x;
            d = Math.round(d * factor) / factor;
            BigDecimal bd = new BigDecimal(d, mc);
            bd = bd.remainder(new BigDecimal("0.01"), mc);
            if (bd.multiply(BigDecimal.valueOf(100)).remainder(BigDecimal.ONE, mc).compareTo(BigDecimal.ZERO) != 0)
            {
                System.out.println(d + " " + bd);
                errors++;
            }
        }
        System.out.println(count + " trials " + errors + " errors");
    }
    

    结果:

    10001 trials 4401 errors
    
  • 76

    您可以使用以下实用方法 -

    public static double round(double valueToRound, int numberOfDecimalPlaces)
    {
        double multipicationFactor = Math.pow(10, numberOfDecimalPlaces);
        double interestedInZeroDPs = valueToRound * multipicationFactor;
        return Math.round(interestedInZeroDPs) / multipicationFactor;
    }
    
  • 6

    下面的代码段显示了如何显示n位数 . 诀窍是将变量pp设置为1,然后是n个零 . 在下面的示例中,变量pp值有5个零,因此将显示5个数字 .

    double pp = 10000;
    
    double myVal = 22.268699999999967;
    String needVal = "22.2687";
    
    double i = (5.0/pp);
    
    String format = "%10.4f";
    String getVal = String.format(format,(Math.round((myVal +i)*pp)/pp)-i).trim();
    
  • 4

    请记住,String.format()和DecimalFormat使用默认的Locale生成字符串 . 因此,他们可以使用点或逗号作为整数和小数部分之间的分隔符来编写带格式的数字 . 要确保圆角字符串采用您想要的格式,请使用java.text.NumberFormat:

    Locale locale = Locale.ENGLISH;
      NumberFormat nf = NumberFormat.getNumberInstance(locale);
      // for trailing zeros:
      nf.setMinimumFractionDigits(2);
      // round to 2 digits:
      nf.setMaximumFractionDigits(2);
    
      System.out.println(nf.format(.99));
      System.out.println(nf.format(123.567));
      System.out.println(nf.format(123.0));
    

    将以英语语言环境打印(无论您的语言环境是什么):0.99 123.57 123.00

    这个例子来自Farenda - how to convert double to String correctly .

  • 2

    如果你真的想要十进制数来计算(而不仅仅是输出),不要使用像double这样的基于二进制的浮点格式 .

    Use BigDecimal or any other decimal-based format.
    

    我确实使用BigDecimal进行计算,但请记住它取决于您正在处理的数字的大小 . 在我的大多数实现中,我发现从double或integer到Long的解析足以进行大量计算 .

    事实上,我最近使用parsed-to-Long在GUI中获得与##################一样大的数字的准确表示(而不是十六进制结果) ###############字符(作为示例) .

  • 3

    您可以使用BigDecimal

    BigDecimal value = new BigDecimal("2.3");
    value = value.setScale(0, RoundingMode.UP);
    BigDecimal value1 = new BigDecimal("-2.3");
    value1 = value1.setScale(0, RoundingMode.UP);
    System.out.println(value + "n" + value1);
    

    参考:http://www.javabeat.net/precise-rounding-of-decimals-using-rounding-mode-enumeration/

  • 0

    如果您使用的是具有最小JDK的技术 . 这是一种没有任何Java库的方法:

    double scale = 100000;    
    double myVal = 0.912385;
    double rounded = (int)((myVal * scale) + 0.5d) / scale;
    
  • 616

    通常,舍入是通过缩放来完成的: round(num / p) * p

    /**
     * MidpointRounding away from zero ('arithmetic' rounding)
     * Uses a half-epsilon for correction. (This offsets IEEE-754
     * half-to-even rounding that was applied at the edge cases).
     */
    double RoundCorrect(double num, int precision) {
        double c = 0.5 * EPSILON * num;
    //  double p = Math.pow(10, precision); //slow
        double p = 1; while (precision--> 0) p *= 10;
        if (num < 0)
            p *= -1;
        return Math.round((num + c) * p) / p;
    }
    
    // testing edge cases
    RoundCorrect(1.005, 2);   // 1.01 correct
    RoundCorrect(2.175, 2);   // 2.18 correct
    RoundCorrect(5.015, 2);   // 5.02 correct
    
    RoundCorrect(-1.005, 2);  // -1.01 correct
    RoundCorrect(-2.175, 2);  // -2.18 correct
    RoundCorrect(-5.015, 2);  // -5.02 correct
    
  • 1

    以防有人仍然需要帮助 . 这个解决方案非常适合我 .

    private String withNoTrailingZeros(final double value, final int nrOfDecimals) {
    return new BigDecimal(String.valueOf(value)).setScale(nrOfDecimals,  BigDecimal.ROUND_HALF_UP).stripTrailingZeros().toPlainString();
    
    }
    

    返回带有所需输出的 String .

  • 2

    Real的Java How-to posts这个解决方案,它也兼容Java 1.6之前的版本 .

    BigDecimal bd = new BigDecimal(Double.toString(d));
    bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
    return bd.doubleValue();
    
  • 4

    简洁的解决方案:

    public static double round(double value, int precision) {
          int scale = (int) Math.pow(10, precision);
          return (double) Math.round(value * scale) / scale;
      }
    

    另见,https://stackoverflow.com/a/22186845/212950感谢jpdymond提供此功能 .

  • 5

    @Milhous:舍入的十进制格式非常好:

    您也可以使用DecimalFormat df = new DecimalFormat(“# . 00000”);
    df.format(0.912385);
    确保你有0的结尾 .

    我想补充一点,这种方法非常擅长提供一种实际的数字舍入机制 - 不仅在视觉上,而且在处理时 .

    假设:您必须在GUI程序中实现舍入机制 . 要改变结果输出的准确度/精度,只需更改插入符号格式(即括号内) . 以便:

    DecimalFormat df = new DecimalFormat("#0.######");
    df.format(0.912385);
    

    将作为输出返回: 0.912385

    DecimalFormat df = new DecimalFormat("#0.#####");
    df.format(0.912385);
    

    将作为输出返回: 0.91239

    DecimalFormat df = new DecimalFormat("#0.####");
    df.format(0.912385);
    

    将作为输出返回: 0.9124

    [编辑:如果插入符号格式如此("#0.############")并输入小数,例如3.1415926,为了论证的缘故,DecimalFormat不会产生任何垃圾(例如尾随零)并将返回: 3.1415926 ..如果你对某些开发人员的喜好有点冗长's - but hey, it' s在处理期间内存占用空间很小并且非常容易实行 . ]

    从本质上讲,DecimalFormat的优点在于它同时处理字符串外观 - 以及舍入精度集的级别 . Ergo:您可以从一个代码实现的价格中获得两个好处 . ;)

  • 34

    使用setRoundingMode,明确设置RoundingMode来处理半偶数问题,然后使用格式模式输出所需的输出 .

    例:

    DecimalFormat df = new DecimalFormat("#.####");
    df.setRoundingMode(RoundingMode.CEILING);
    for (Number n : Arrays.asList(12, 123.12345, 0.23, 0.1, 2341234.212431324)) {
        Double d = n.doubleValue();
        System.out.println(df.format(d));
    }
    

    给出输出:

    12
    123.1235
    0.23
    0.1
    2341234.2125
    
  • 0

    如果您使用 DecimalFormatdouble 转换为 String ,则非常简单:

    DecimalFormat formatter = new DecimalFormat("0.0##");
    formatter.setRoundingMode(RoundingMode.HALF_UP);
    
    double num = 1.234567;
    return formatter.format(num);
    

    有几个 RoundingMode 枚举值可供选择,具体取决于您需要的行为 .

  • 164
    new BigDecimal(String.valueOf(double)).setScale(yourScale, BigDecimal.ROUND_HALF_UP);
    

    会给你一个 BigDecimal . 要从中获取字符串,只需调用 BigDecimaltoString 方法或Java 5的 toPlainString 方法作为普通格式字符串 .

    示例程序:

    package trials;
    import java.math.BigDecimal;
    
    public class Trials {
    
        public static void main(String[] args) {
            int yourScale = 10;
            System.out.println(BigDecimal.valueOf(0.42344534534553453453-0.42324534524553453453).setScale(yourScale, BigDecimal.ROUND_HALF_UP));
        }
    
  • 0

    如果你考虑5或n个小数 . 可能是这个答案解决你的问题 .

    double a = 123.00449;
        double roundOff1 = Math.round(a*10000)/10000.00;
        double roundOff2 = Math.round(roundOff1*1000)/1000.00;
        double roundOff = Math.round(roundOff2*100)/100.00;
    
        System.out.println("result:"+roundOff);
    

    Output will be: 123.0 1
    这可以通过循环和递归函数来解决 .

  • 27

    DecimalFormat是输出的最佳方式,但我不喜欢它 . 我总是这样做,因为它返回double值 . 所以我可以使用它而不仅仅是输出 .

    Math.round(selfEvaluate*100000d.0)/100000d.0;
    

    要么

    Math.round(selfEvaluate*100000d.0)*0.00000d1;
    

    如果需要大小数位值,则可以使用BigDecimal . 无论如何 .0 很重要 . 没有它,0.33333d5的舍入返回0.33333,只允许9位数 . 没有 .0 的第二个函数有0.30000返回0.30000000000000004的问题 .

  • 6

    以下是您希望结果为String时可以使用的内容摘要:

    DecimalFormat df = new DecimalFormat("#.#####");
    df.setRoundingMode(RoundingMode.HALF_UP);
    String str1 = df.format(0.912385)); // 0.91239
    
    String str2 = new BigDecimal(0.912385)
        .setScale(5, BigDecimal.ROUND_HALF_UP)
        .toString();
    

    如果您希望 double 作为结果,这里建议您可以使用哪些库 . 我不建议将其用于字符串转换,因为double可能无法准确表示您想要的内容(请参阅例如here):

    来自Apache Commons Math的

    double rounded = Precision.round(0.912385, 5, BigDecimal.ROUND_HALF_UP);
    

    来自柯尔特的

    double rounded = Functions.round(0.00001).apply(0.912385)
    

    来自Weka的

    double rounded = Utils.roundDouble(0.912385, 5)
    
  • 73

    假设你有

    double d = 9232.129394d;
    

    你可以用BigDecimal

    BigDecimal bd = new BigDecimal(d).setScale(2, RoundingMode.HALF_EVEN);
    d = bd.doubleValue();
    

    要么没有BigDecimal

    d = Math.round(d*100)/100.0d;
    

    两种解决方案 d == 9232.13

  • 17

    其中dp =你想要的小数位,而值是double .

    double p = Math.pow(10d, dp);
    
        double result = Math.round(value * p)/p;
    
  • 52

    为此,我们可以使用此格式化程序:

    DecimalFormat df = new DecimalFormat("#.00");
     String resultado = df.format(valor)
    

    要么:

    DecimalFormat df = new DecimalFormat("0.00"); :
    

    使用此方法始终获得两位小数:

    private static String getTwoDecimals(double value){
          DecimalFormat df = new DecimalFormat("0.00"); 
          return df.format(value);
        }
    

    定义此值:

    91.32
    5.22
    11.5
    1.2
    2.6
    

    使用该方法我们可以得到以下结果:

    91.32
    5.22
    11.50
    1.20
    2.60
    

    demo online.

  • 7

    试试这个:org.apache.commons.math3.util.Precision.round(double x,int scale)

    见:http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/util/Precision.html

    Apache Commons数学图书馆主页是:http://commons.apache.org/proper/commons-math/index.html

    该方法的内部实现是:

    public static double round(double x, int scale) {
        return round(x, scale, BigDecimal.ROUND_HALF_UP);
    }
    
    public static double round(double x, int scale, int roundingMethod) {
        try {
            return (new BigDecimal
                   (Double.toString(x))
                   .setScale(scale, roundingMethod))
                   .doubleValue();
        } catch (NumberFormatException ex) {
            if (Double.isInfinite(x)) {
                return x;
            } else {
                return Double.NaN;
            }
        }
    }
    

相关问题