【问题标题】:How to round a number to n decimal places in Java如何在 Java 中将数字四舍五入到 n 位小数
【发布时间】:2023-02-08 11:45:36
【问题描述】:

我想要的是一种将 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 中实现此目的的最佳方法是什么?

【问题讨论】:

    标签: java decimal rounding digits


    【解决方案1】:

    使用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
    

    编辑:原始答案没有解决双值的准确性。如果您不太关心向上或向下舍入,那很好。但是,如果您想要精确舍入,则需要考虑值的预期精度。浮点值在内部具有二进制表示。这意味着像 2.7735 这样的值在内部实际上并没有那个确切的值。它可以稍大或稍小。如果内部值稍小,则不会四舍五入到 2.7740。要补救这种情况,您需要了解您正在使用的值的准确性,并在四舍五入之前添加或减去该值。例如,当您知道您的值最多精确到 6 位数字时,然后将值向上舍入,将该精度添加到该值:

    Double d = n.doubleValue() + 1e-6;
    

    要向下舍入,请减去准确度。

    【讨论】:

    • 这可能是迄今为止提出的最佳解决方案。当我第一次查看 DecimalFormat 类时,我没有发现这个功能的原因是它仅在 Java 1.6 中引入。不幸的是,我仅限于使用 1.5,但了解它对将来很有用。
    • 我试过这个:"#.##",四舍五入HALF_UP256.335f -> "256.33" ...(示例来自cmets to @asterite's answer)。
    • 请小心,因为 DecimalFormat 取决于您当前的本地配置,您可能不会得到一个点作为分隔符。我个人更喜欢下面 Asterite 的回答
    • 另请注意,您不应期望 DecimalFormat 是线程安全的。根据Java docs十进制格式一般不同步。建议为每个线程创建单独的格式实例。如果多个线程同时访问一种格式,则必须在外部进行同步。
    • 我该怎么做才能进行适当的舍入,这样它就不会将 0.0004 舍入到 0.001
    【解决方案2】:

    假设valuedouble,你可以这样做:

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

    这是 5 位数的精度。零的个数表示小数位数。

    【讨论】:

    • 更新:我刚刚确认这样做比使用 DecimalFormat 快得多。我循环使用 DecimalFormat 200 次,以及这个方法。 DecimalFormat 完成 200 次循环用了 14ms,这个方法用了不到 1ms。正如我所怀疑的那样,这更快。如果你按时钟周期获得报酬,这就是你应该做的。我很惊讶 Chris Cudmore 甚至会说出他所说的话是诚实的。分配对象总是比转换基元和使用静态方法(Math.round() 与 decimalFormat.format() 相对)更昂贵。
    • 这种技术在超过 90% 的情况下都失败了。 -1。
    • 事实上,这失败了:Math.round(0.1 * Math.pow(10,20))/Math.pow(10,20) == 0.09223372036854775
    • 使用此方法(或任何浮点数舍入)时要非常小心。对于像 265.335 这样简单的东西,它会失败。 265.335 * 100(2位精度)的中间结果为26533.499999999996。这意味着它会向下舍入为 265.33。从浮点数转换为实数十进制数时,存在一些固有的问题。在stackoverflow.com/a/12684082/144578 上查看 EJP 的回答
    • @SebastiaanvandenBroek:哇,我从来不知道得到错误答案这么容易。但是,如果使用非精确数字,则必须认识到任何值不准确. 265.335 真正的意思是265.335 += tolerance,其中公差取决于之前的操作和输入值的范围。我们不知道真实、准确的价值。在边缘值,任何一个答案可以说是正确的。如果我们需要精确,我们不应该双人工作。 fail 这里不是在转换回 double。在 OP 中,他认为传入的 265.335 就是这样。
    【解决方案3】:
    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));
        }
    

    【讨论】:

    • 这是我的首选解决方案。甚至更短:BigDecimal.valueOf(doubleVar).setScale(yourScaleHere, BigDecimal.ROUND_HALF_UP); BigDecimal.valueOf(double val) 实际上在幕后调用 Double.toString() ;)
    • 好的。不要偷工减料并使用new BigDecimal(doubleVar),因为您可能会遇到浮点数舍入问题
    • @Edd,有趣的是,四舍五入问题发生在 Sebastiaan vandenBroek 在评论 asterite 的回答时提到的案例中。 double val = 265.335;BigDecimal.valueOf(val).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString(); => 265.34,但(new BigDecimal(val)).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString(); => 265.33
    • @ToolmakerSteve 那是因为将 new BigDecimal 与 double 一起使用会直接获取 double 值并尝试使用它来创建 BigDecimal,而在使用 BigDecimal.valueOf 或 tostring 形式时会在转换之前首先将其解析为字符串(更准确的表示形式) .
    • BigDecimal.ROUND_HALF_UP 自 9 起已弃用。您可以改用:RoundingMode.HALF_UP
    【解决方案4】:

    您还可以使用

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

    以确保您有尾随的 0。

    【讨论】:

    • 我相信这个问题的目标之一是“应该不是是任何尾随零”。
    • 对于这个问题,op 不想要零,但这正是我想要的。如果你有一个小数点后 3 位的数字列表,你希望它们都具有相同的数字,即使它是 0。
    • 你忘了指定RoundingMode.
    • @IgorGanapolsky 默认Decimal mode 使用RoundingMode.HALF_EVEN.
    【解决方案5】:

    正如其他一些人指出的那样,正确的答案是使用 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
    

    编辑:为了解决下面的一些 cmets,我使用 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
    

    【讨论】:

    • 诀窍在于,在所有 9251 错误中,打印结果仍然正确。
    • @DidierL 这并不让我感到惊讶。我有幸将“数值方法”作为我的第一门计算课程,并在一开始就被介绍了浮点数可以做什么和不能做什么。大多数程序员对此都非常模糊。
    • 你所做的只是反驳说浮点数并不完全代表许多十进制值,我希望我们都能理解这一点。并不是说四舍五入确实会引起问题。正如您承认的那样,数字仍然按预期打印。
    • 你的测试失败了,取出 round() 并且测试失败的概率为 94%。 ideone.com/1y62CY 打印100 trials 94 errors 您应该从通过的测试开始,并表明引入舍入会破坏测试。
    • 反驳,在这里反驳。在 double 的这个范围内使用 Math.round 没有错误 ideone.com/BVCHh3
    【解决方案6】:

    假设你有

    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

    【讨论】:

    • 我认为这是 Java 1.5 用户(及以下版本)的最佳解决方案。不过,请不要使用 HALF_EVEN 舍入模式,因为它对奇数和偶数具有不同的行为(例如,2.5 舍入为 2,而 5.5 舍入为 6),除非这是您想要的。
    • 第一个解决方案是正确的:第二个不起作用。请参阅here 以获取证明。
    • @EJP:即使是RoundingMode.HALF_UP 的第一个解决方案也是错误的。试试1.505。正确的方法是使用BigDecimal.valueOf(d)
    • Matthias Braun,解决方案很好,因此 31 ups .. 1.505 十进制存储在浮点数中,作为 1.50499998 加倍 如果你想取 1.​​505 并将其从双精度转换为十进制,那么你必须先将它转换为 Double.toString(x)然后将其放入 BigDecimal() 中,但这非常慢,并且首先违背了使用 double 来提高速度的目的。
    • 使用 BigDecimal(耗时 225 毫秒)和 Math.round(2 毫秒)方式运行一个 100k 的循环,这里是计时......花费的时间:225 毫秒转换为:9232.13 花费的时间:2 毫秒转换为: 9232.13 techiesinfo.com
    【解决方案7】:

    您可以使用 DecimalFormat 类。

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

    【讨论】:

    • 选择Double.valueOf() 而不是Double.parseDouble() 的原因是什么? valueOf() 方法返回一个Double 对象,而parseDouble() 将返回一个double 原语。使用当前代码的编写方式,您还可以将自动拆箱应用于返回,以将其转换为您的 twoDouble 变量期望的原语,这是一个额外的字节码操作。我会更改答案以改为使用parseDouble()
    • Double.parseDouble() 需要 String 输入。
    【解决方案8】:

    Real's 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();
    

    更新:BigDecimal.ROUND_HALF_UP 已弃用 - 使用 RoundingMode

    BigDecimal bd = new BigDecimal(Double.toString(number));
    bd = bd.setScale(decimalPlaces, RoundingMode.HALF_UP);
    return bd.doubleValue();
    

    【讨论】:

      【解决方案9】:
      double myNum = .912385;
      int precision = 10000; //keep 4 digits
      myNum= Math.floor(myNum * precision +.5)/precision;
      

      【讨论】:

      • 是的,这正是 math.round 对正数所做的,但是你有没有用负数试过这个?人们在其他解决方案中使用 math.round 来涵盖负数的情况。
      • 注:Math.floor(x + 0.5)Math.round(x)
      【解决方案10】:

      @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 .. 如果你愿意的话。当然,它有点冗长 为了一些开发者的喜好——但是,嘿,它的内存占用很小 在处理过程中,非常容易实施。]

      所以本质上,DecimalFormat 的美妙之处在于它同时处理字符串 外观 - 以及四舍五入精度设置的级别。因此:你 以一个代码实现的价格获得两个好处。 ;)

      【讨论】:

      • 如果你真的想要小数来计算(而不仅仅是输出),不要使用基于二进制的浮点格式double。使用 BigDecimal 或任何其他基于十进制的格式。
      【解决方案11】:

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

      1. DecimalFormat#setRoundingMode():

        DecimalFormat df = new DecimalFormat("#.#####");
        df.setRoundingMode(RoundingMode.HALF_UP);
        String str1 = df.format(0.912385)); // 0.91239
        
      2. BigDecimal#setScale()

        String str2 = new BigDecimal(0.912385)
            .setScale(5, BigDecimal.ROUND_HALF_UP)
            .toString();
        

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

        1. Precision 来自 Apache Commons Math

          double rounded = Precision.round(0.912385, 5, BigDecimal.ROUND_HALF_UP);
          
        2. Functions来自柯尔特

          double rounded = Functions.round(0.00001).apply(0.912385)
          
        3. Utils 来自 Weka

          double rounded = Utils.roundDouble(0.912385, 5)
          

      【讨论】:

        【解决方案12】:

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

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

        【讨论】:

        • @mariolpantunes:它会失败。试试这个:round(1.005,2);round(0.50594724957626620092, 20);
        • 有用。但是无意义的 float 和 double 是近似值。让我们考虑你的第一个例子。如果您在 Math.round 之前打印 interestedInZeroDPs 的输出,它将打印 100.49999999999999。你失去了精度,因为 Math.round 将它四舍五入为 100。由于性质或浮点数和双精度数,当它不能正常工作时存在边界线情况(更多信息在这里 en.wikipedia.org/wiki/Floating_point#Accuracy_problems
        • 双是快!十进制很慢。计算机不会费心用十进制表示法来处理它们的思维。您必须放弃一些小数精度才能使浮点数保持双倍速度。
        • @hamish 问题是关于精度,而不是关于速度。
        【解决方案13】:

        一个简洁的解决方案:

           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 提供这个。

        编辑: 添加了圆括号。将整个结果加倍,而不仅仅是第一个参数!

        【讨论】:

          【解决方案14】:

          您可以使用 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/

          【讨论】:

            【解决方案15】:

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

             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.

            【讨论】:

              【解决方案16】:

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

              Use BigDecimal or any other decimal-based format.
              

              我确实使用 BigDecimal 进行计算,但请记住它取决于 你正在处理的数字。在我的大多数实现中,我发现从 double 或 整数到 Long 足以进行非常大的数字计算。

              事实上,我已经 最近使用 parsed-to-Long 来获得准确的表示(与十六进制结果相反) 在 GUI 中,对于 ################################# 字符的数字(作为 例子)。

              【讨论】:

                【解决方案17】:

                试试这个: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;
                        }
                    }
                }
                

                【讨论】:

                  【解决方案18】:

                  因为我没有找到关于这个主题的完整答案,所以我整理了一个应该正确处理这个问题的类,并支持:

                  • 格式化:轻松将双精度格式化为具有一定小数位数的字符串
                  • 解析: 将格式化后的值解析回double
                  • 语言环境: 使用默认语言环境格式化和解析
                  • 指数符号:在某个阈值后开始使用指数表示法

                  用法很简单:

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

                  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
                  

                  这是班级:

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

                  【讨论】:

                    【解决方案19】:

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

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

                    返回带有所需输出的 ​​ String

                    【讨论】:

                    • 请在评论中说明你反对的理由,否则这就是我们所说的恐吓。
                    【解决方案20】:

                    我同意选择使用DecimalFormat --- 或BigDecimal 的答案。

                    请阅读更新下面先!

                    但是如果你想要舍入 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 项目。

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

                    笔记:

                    • 此行为与 BigDecimal(double) 构造函数的行为非常相似(但与使用字符串构造函数的 valueOf(double) 不同)。
                    • 这个问题可以先通过双舍入步骤来规避,但它很复杂,我在这里不做详细介绍

                    出于这些原因以及本文上面提到的所有内容,我不推荐使用 DoubleRounder.

                    【讨论】:

                    • 您是否有指标显示您的解决方案与其他解决方案相比效率如何?
                    • 我没有将它与其他解决方案进行比较,但源代码中有一个 jmh 基准测试:github.com/tools4j/decimal4j/blob/master/src/jmh/java/org/… 我确实在 VM 上运行了基准测试,结果以 csv 文件的形式在此处提供:github.com/tools4j/decimal4j/wiki/Performance
                    • DoubleRounder 在以下情况下失败:DoubleRounder.round(256.025d, 2) - 预期:256.03,实际:256.02 或 DoubleRounder.round(260.775d, 2) - 预期:260.78,实际:260.77。
                    • @iaforek:这是正确的,因为 DoubleRounder 执行数学上正确的舍入。但是我承认这有点违反直觉,因此会相应地更新我的答案。
                    【解决方案21】:

                    所以在阅读了大部分答案后,我意识到他们中的大多数都不会精确,实际上使用 BigDecimal 似乎是最好的选择,但如果你不了解 RoundingMode 的工作原理,你将不可避免地失去精度.我在一个项目中处理大数字时发现了这一点,并认为它可以帮助其他人在四舍五入时遇到问题。例如。

                    BigDecimal bd = new BigDecimal("1363.2749");
                    bd = bd.setScale(2, RoundingMode.HALF_UP);
                    System.out.println(bd.doubleValue());
                    

                    您可能希望得到 1363.28 作为输出,但您最终会得到 1363.27,如果您不知道 RoundingMode 在做什么,这不是预期的。所以查看Oracle Docs,你会发现RoundingMode.HALF_UP的以下描述。

                    舍入模式向“最近的邻居”舍入,除非两者 邻居是等距的,在这种情况下四舍五入。

                    所以知道这一点,我们意识到我们不会得到精确的舍入,除非我们想舍入到最近的邻居.因此,为了完成足够的循环,我们需要从 n-1 小数点循环到所需的小数位数。例如。

                    private double round(double value, int places) throws IllegalArgumentException {
                    
                        if (places < 0) throw new IllegalArgumentException();
                    
                        // Cast the number to a String and then separate the decimals.
                        String stringValue = Double.toString(value);
                        String decimals = stringValue.split("\.")[1];
                    
                        // Round all the way to the desired number.
                        BigDecimal bd = new BigDecimal(stringValue);
                        for (int i = decimals.length()-1; i >= places; i--) {
                            bd = bd.setScale(i, RoundingMode.HALF_UP);
                        }
                    
                        return bd.doubleValue();
                    }
                    

                    这最终会给我们预期的输出,即1363.28

                    【讨论】:

                      【解决方案22】:

                      我来这里只是想得到一个关于如何舍入数字的简单答案。这是提供该内容的补充答案。

                      如何在 Java 中对数字进行四舍五入

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

                      Math.round(3.7) // 4
                      

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

                      round

                      如上所述,这四舍五入到最接近的整数。 .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

                      任何十进制值都向上舍入为下一个整数。它去天花板荷兰国际集团此方法返回 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
                      

                      floor

                      任何十进制值都向下舍入为下一个整数。此方法返回 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 不同,.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 ***
                      

                      【讨论】:

                      • 您只是在解决四舍五入到 0 位小数的特殊情况。原来的问题更笼统。
                      【解决方案23】:

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

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

                      【讨论】:

                      • 如果 myVal 不小于 1 并且小数点后的零超出刻度值,这将失败。假设你有 myVal = 9.00000000912385;以上将返回 9.0。我认为我们应该提供适用于所有 myVal 情况的解决方案。不是专门针对您所说的价值。
                      • @user102859 在您的示例中,9.0 是正确的结果。我不明白这怎么会失败。
                      • 对于 1.451 的比例(没有小数位)它向下舍入,即使我期望 2
                      • @PAX 你为什么期望 2? 1.45 四舍五入到没有小数位是 1,而不是 2。
                      • @Craigo 我刚刚做了一个研究:显然,我在小学就学错了。我找到了一个像往常一样解释舍入的来源,还有许多其他来源像你一样解释它。你是对的!
                      【解决方案24】:

                      这是我的回答:

                      double num = 4.898979485566356;
                      DecimalFormat df = new DecimalFormat("#.##");      
                      time = Double.valueOf(df.format(num));
                      
                      System.out.println(num); // 4.89
                      

                      【讨论】:

                        【解决方案25】:

                        这是一个更好的函数,可以正确舍入像 1.005 这样的边缘情况。

                        简单地说,我们在四舍五入之前将最小可能的浮点值(= 1 ulp;最后一位的单位)添加到数字中。这将移动到数字后的下一个可表示值,远离零。

                        这是一个测试它的小程序:ideone.com

                        /**
                         * Round half away from zero ('commercial' rounding)
                         * Uses correction to offset floating-point inaccuracies.
                         * Works symmetrically for positive and negative numbers.
                         */
                        public static double round(double num, int digits) {
                        
                            // epsilon correction
                            double n = Double.longBitsToDouble(Double.doubleToLongBits(num) + 1);
                            double p = Math.pow(10, digits);
                            return Math.round(n * p) / p;
                        }
                        
                        // test rounding of half
                        System.out.println(round(0.5, 0));   // 1
                        System.out.println(round(-0.5, 0));  // -1
                        
                        // testing edge cases
                        System.out.println(round(1.005, 2));   // 1.01
                        System.out.println(round(2.175, 2));   // 2.18
                        System.out.println(round(5.015, 2));   // 5.02
                        
                        System.out.println(round(-1.005, 2));  // -1.01
                        System.out.println(round(-2.175, 2));  // -2.18
                        System.out.println(round(-5.015, 2));  // -5.02
                        

                        【讨论】:

                        • 如果我想四舍五入没有数字的12.45,我希望是13,但它四舍五入到12
                        【解决方案26】:

                        下面的代码 sn-p 显示了如何显示 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();
                        

                        【讨论】:

                          【解决方案27】:

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

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

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

                          【讨论】:

                            【解决方案28】:

                            DecimalFormat 是最好的输出方式,但我不喜欢它。我总是这样做,因为它返回双精度值。所以我可以使用它不仅仅是输出。

                            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。

                            【讨论】:

                              【解决方案29】:

                              我在 Java 8 中使用过波纹管。它对我有用

                                  double amount = 1000.431;        
                                  NumberFormat formatter = new DecimalFormat("##.00");
                                  String output = formatter.format(amount);
                                  System.out.println("output = " + output);
                              

                              输出:

                              output = 1000.43
                              

                              【讨论】:

                                【解决方案30】:

                                需要的话可以使用下面的方法double

                                double getRandom(int decimalPoints) {
                                    double a = Math.random();
                                    int multiplier = (int) Math.pow(10, decimalPoints);
                                    int b = (int) (a * multiplier);
                                    return b / (double) multiplier;
                                }
                                

                                例如getRandom(2)

                                【讨论】:

                                  猜你喜欢
                                  • 2010-09-14
                                  • 2016-05-03
                                  • 2014-03-02
                                  • 1970-01-01
                                  • 2015-08-02
                                  • 2022-07-26
                                  相关资源
                                  最近更新 更多