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


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


1022
2017-09-30 16:01


起源




答案:


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

585
2017-09-30 16:14



这可能是目前为止提出的最佳解决方案。我第一次看到DecimalFormat类时没有发现这个工具的原因是它只是在Java 1.6中引入的。不幸的是,我只能使用1.5,但知道将来会很有用。 - Alex Spurling
不适用于指数小数格式,例如格式(“0.0E00”)。在这个例子中它将围绕0.0155到0.0E00而不是1.6E-02。 - Martin Clemens Bloch
我试过这个: "#.##"四舍五入 HALF_UP。 256.335f  - > "256.33" ...(例子来自对@ asterite的回答的评论)。 - bigstones
请小心,因为DecimalFormat依赖于您当前的本地配置,您可能无法获得一个点作为分隔符。我个人更喜欢下面的Asterite答案 - Gomino
我怎么做它使它做一个适当的舍入,所以它不会围绕0.0004到0.001


假设 value 是一个 double, 你可以做:

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

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


398
2017-09-30 16:07



更新:我刚刚确认这比使用DecimalFormat更快。我使用DecimalFormat 200次和这个方法循环。 DecimalFormat花了14ms来完成200个循环,这种方法花了不到1ms。我怀疑,这更快。如果您在时钟周期内获得报酬,那么您应该这样做。我很惊讶克里斯·库德莫尔甚至会说出他所说的诚实。分配对象总是比使用静态方法(Math.round()而不是decimalFormat.format())更昂贵。 - Andi Jay
超过90%的情况下,该技术失败。 -1。 - user207421
确实,这失败了: Math.round(0.1 * Math.pow(10,20))/Math.pow(10,20) == 0.09223372036854775。 - Robert Tupelo-Schneck
@ RobertTupelo-Schneck这不起作用,因为double只有大约14个小数位的精度(不管怎么说都不准确) - sambe
使用此方法(或任何浮点舍入)时要非常小心。它没有像265.335这样简单的东西。 265.335 * 100(2位精度)的中间结果为26533.499999999996。这意味着它将向下舍入到265.33。从浮点数转换为实数十进制数时,只存在固有的问题。请参阅EJP的回答 stackoverflow.com/a/12684082/144578 - Sebastiaan van den Broek


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

会得到你的 BigDecimal。要从中获取字符串,只需调用它即可 BigDecimaltoString 方法,或 toPlainString 用于普通格式字符串的Java 5+的方法。

示例程序:

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

164
2017-09-30 18:33



这是我的首选解决方案。更短:BigDecimal.valueOf(doubleVar).setScale(yourScaleHere,BigDecimal.ROUND_HALF_UP); BigDecimal.valueOf(double val)实际上在引擎盖下调用Double.toString();) - Etienne Neveu
尼斯。不要偷工减料并使用 new BigDecimal(doubleVar) 因为你可能会遇到浮点舍入的问题 - Edd
@Edd,有趣的是,在SebastiaanvandenBroek对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
@ToolmakerSteve那是因为使用带有double的新BigDecimal直接获取double值并尝试使用它来创建BigDecimal,而当使用BigDecimal.valueOf或tostring表单在转换之前首先将其解析为字符串(更精确的表示) 。 - MetroidFan2002


你也可以使用

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

确保你有0的结尾。


98
2017-09-30 16:58



我相信这个问题的目标之一是“应该 不 是任何尾随的零“。 - Lunchbox
对于这个问题,op不想要零,但这正是我想要的。如果你有一个带有3个小数位的数字列表,你希望它们都具有相同的数字,即使它是0。 - Tom Kincaid
你忘了指定 RoundingMode. - Igor Ganapolsky
@IgorGanapolsky默认情况下 Decimal mode 使用 RoundingMode.HALF_EVEN. - EndermanAPM


正如其他一些人所指出的那样,正确的答案是使用其中之一 DecimalFormat 要么 BigDecimal。浮点没有  小数位,这样你就不可能在第一时间将其舍入/截断为特定数量的它们。你必须使用十进制基数,这就是这两个类的作用。

我发布了以下代码作为这个线程中所有答案的反例 - 实际上遍布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

编辑: 为了解决下面的一些评论,我使用了重复测试循环的模数部分 BigDecimal 和 new 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

75
2017-10-02 03:18



诀窍在于,在所有9251错误中,打印结果仍然正确。 - Didier L
@DidierL我并不感到惊讶。我非常幸运地将'数值方法'作为我的第一个计算过程,并在开始时介绍浮点可以做什么和不能做什么。大多数程序员对此都很模糊。 - user207421
您所做的只是反驳浮动并不完全代表许多十进制值,我希望大家都能理解。不是圆角确实会导致问题。正如您承认的那样,数字仍按预期打印。 - Peter Lawrey
你的测试被打破,取出(),测试在94%的时间内失败。 ideone.com/1y62CY 版画 100 trials 94 errors 你应该从一个通过的测试开始,并表明引入舍入会破坏测试。 - Peter Lawrey
反驳,在这里驳斥。在这个范围内使用Math.round double 因为没有错误 ideone.com/BVCHh3 - Peter Lawrey


假设你有

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


71
2018-01-28 09:52



我认为这是Java 1.5用户(及以下)的最佳解决方案。一个评论,不要使用HALF_EVEN舍入模式,因为它具有奇数和偶数的差异行为(例如,2.5轮到2,而5.5轮到6),除非这是你想要的。 - IcedDante
第一个解决方案是正确的:第二个解决方案不起作用。看到 这里 证明。 - user207421
@EJP:即使是第一个解决方案 RoundingMode.HALF_UP 是错的。尝试一下 1.505。正确的方法是使用 BigDecimal.valueOf(d)。 - Matthias Braun
Matthias Braun,解决方案很好,因此31个ups .. 1.505十进制存储在浮点双倍为1.50499998如果你想取1.505并从double转换为十进制,那么你必须先将其转换为Double.toString(x)然后把它放到一个BigDecimal()中,但这非常慢,并且首先打败了使用double for speed的目的。 - hamish
使用BigDecimal(花费225毫秒)和Math.round(2毫秒)方式运行100k的循环,这里是时间...时间:225毫秒转换使用到:9232.13时间:转换为2毫秒:9232.13 techiesinfo.com - user1114134


您可以使用DecimalFormat类。

double d = 3.76628729;

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

52
2017-09-29 07:01



任何理由 Double.valueOf() 被选中了 Double.parseDouble()?该 valueOf() 方法返回一个 Double 对象,而 parseDouble() 将返回一个 double 原始。通过编写当前代码的方式,您还可以对返回应用自动拆箱以将其转换为您的原语 twoDouble 变量需要一个额外的字节码操作。我会改变使用的答案 parseDouble() 代替。 - ecbrodie
Double.parseDouble() 需求 String 输入。 - Ryde


Real的Java How-to 帖子 此解决方案也适用于Java 1.6之前的版本。

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

34
2017-09-01 11:14





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

30
2017-09-30 16:09



是的,这正是math.round对正数所做的,但是你用负数做过这个吗?人们在其他解决方案中使用math.round也涵盖了负数的情况。 - hamish
注意: Math.floor(x + 0.5) 和 Math.round(x) - Peter Lawrey


@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的优点在于它同时处理字符串 外观 - 以及舍入精度设置的级别。 Ergo:你 以一个代码实现的价格获得两个好处。 ;)


27
2017-07-03 15:50



如果你真的想要十进制数来计算(而不仅仅是输出), 不要使用基于二进制的浮点格式 喜欢 double。使用BigDecimal或任何其他基于十进制的格式。 - Paŭlo Ebermann


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

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

17
2018-04-27 15:56



不行,看 这里 证明。 - user207421
为什么你说它不起作用?它对我有用。 - mariolpantunes
@mariolpantunes:它会失败。尝试这个: round(1.005,2); 要么 round(0.50594724957626620092, 20); - Matthias Braun
有用。但是没有信息的浮动和双打是近似值。让我们考虑你的第一个例子。如果在Math.round之前打印interestInZeroDPs的输出,则会打印100.49999999999999。你失去了精度,因为Math.round将其舍入为100.由于性质或浮点数和双精度数,当它无法正常工作时会出现边界线情况(更多信息请点击此处 en.wikipedia.org/wiki/Floating_point#Accuracy_problems) - mariolpantunes
双是快!小数很慢。计算机不用十进制表示法来处理他们的思维。你必须放弃一些小数精度,以保持浮点速度加倍。 - hamish