Como arredondar um número para n casas decimais em Java

1259

O que eu gostaria é de um método para converter um duplo em uma string que arredonde usando o método half-up - ou seja, se o decimal a ser arredondado for 5, ele sempre arredondará para o próximo número. Esse é o método padrão de arredondamento que a maioria das pessoas espera na maioria das situações.

Eu também gostaria que apenas dígitos significativos fossem exibidos - ou seja, não deveria haver zeros à direita.

Eu sei que um método para fazer isso é usar o String.formatmétodo:

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

retorna:

0.91239

o que é ótimo, porém sempre exibe números com 5 casas decimais, mesmo que não sejam significativos:

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

retorna:

0.91230

Outro método é usar o DecimalFormatter:

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

retorna:

0.91238

No entanto, como você pode ver, isso usa arredondamentos semi-uniformes. Ou seja, será arredondado para baixo se o dígito anterior for par. O que eu gostaria é isso:

0.912385 -> 0.91239
0.912300 -> 0.9123

Qual é a melhor maneira de conseguir isso em Java?

Alex Spurling
fonte

Respostas:

751

Use setRoundingMode, defina o RoundingModeexplicitamente para lidar com seu problema com a metade do ciclo e use o padrão de formato para a saída necessária.

Exemplo:

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

dá a saída:

12
123.1235
0.23
0.1
2341234.2125

EDIT : A resposta original não aborda a precisão dos valores duplos. Isso é bom se você não se importa muito, seja para cima ou para baixo. Mas se você deseja arredondamentos precisos, precisará levar em consideração a precisão esperada dos valores. Valores de ponto flutuante têm uma representação binária internamente. Isso significa que um valor como 2,7735 na verdade não tem esse valor exato internamente. Pode ser um pouco maior ou um pouco menor. Se o valor interno for um pouco menor, ele não arredondará para 2,7740. Para remediar essa situação, você precisa estar ciente da precisão dos valores com os quais está trabalhando e adicionar ou subtrair esse valor antes de arredondar. Por exemplo, quando você souber que seus valores são precisos até 6 dígitos e, para arredondar valores intermediários, adicione essa precisão ao valor:

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

Para arredondar para baixo, subtraia a precisão.

curtisk
fonte
9
Esta é provavelmente a melhor solução apresentada até agora. A razão pela qual não localizei esse recurso quando examinei a classe DecimalFormat pela primeira vez é que ele foi introduzido apenas no Java 1.6. Infelizmente, estou restrito a usar o 1.5, mas será útil saber para o futuro.
Alex Spurling 01/10/08
1
Eu tentei isso com "#.##":, arredondamento HALF_UP. 256.335f-> "256.33"... (exemplo vem de comentários para a resposta de @ asterita).
bigstones
6
Tenha cuidado, pois DecimalFormat depende da sua configuração local atual; talvez você não consiga um ponto como separador. Pessoalmente, prefiro a resposta de Asterite abaixo
Gomino
1
Lembre-se também de que você não deve esperar que DecimalFormat seja seguro para threads. Conforme documentos Java : Formatos decimais geralmente não são sincronizados. É recomendável criar instâncias de formato separadas para cada thread. Se vários threads acessarem um formato simultaneamente, ele deverá ser sincronizado externamente.
CGK
1
como faço para torná-lo para que ele faz um arredondamento adequada para que ele não vai rodada ,0004-,001
471

Assumindo que valueé um double, você pode fazer:

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

Isso é para precisão de 5 dígitos. O número de zeros indica o número de casas decimais.

asterita
fonte
71
UPDATE: Acabei de confirmar que isso é MUITO mais rápido do que usar DecimalFormat. Fiz um loop usando DecimalFormat 200 vezes e esse método. DecimalFormat levou 14ms para concluir os 200 loops, esse método levou menos de 1ms. Como eu suspeitava, isso é mais rápido. Se você é pago pelo ciclo do relógio, é isso que você deve fazer. Estou surpreso que Chris Cudmore diria o que ele disse para ser honesto. alocar objetos é sempre mais caro do que converter primitivas e usar métodos estáticos (Math.round () em oposição a decimalFormat.format ()).
And Jay Jay
99
Essa técnica falha em mais de 90% dos casos. -1.
Marquês de Lorne
25
Na verdade, esta falha: Math.round(0.1 * Math.pow(10,20))/Math.pow(10,20) == 0.09223372036854775.
Robert Tupelo-Schneck
54
Tenha muito cuidado ao usar este método (ou qualquer arredondamento de pontos flutuantes). Ele falha em algo tão simples quanto 265.335. O resultado intermediário de 265.335 * 100 (precisão de 2 dígitos) é 26533.499999999996. Isso significa que ele é arredondado para 265,33. Simplesmente existem problemas inerentes ao converter de números de ponto flutuante para números decimais reais. Resposta See de EJP aqui no stackoverflow.com/a/12684082/144578
Sebastiaan van den Broek
6
@SebastiaanvandenBroek: Uau, eu nunca soube que era tão fácil obter uma resposta errada. No entanto, se alguém trabalha com números não exatos, deve reconhecer que qualquer valor não é exato . 265.335realmente significa 265.335 += tolerance, onde a tolerância depende de operações anteriores e da faixa de valores de entrada. Não sabemos o valor exato e verdadeiro. Nos valores de borda, qualquer uma das respostas está indiscutivelmente correta. Se precisarmos ser exatos, não devemos trabalhar em dobro. O failaqui não está na conversão de volta para o dobro. Está pensando em OP, que ele pode confiar na entrada 265.335como exatamente isso.
Home
191
new BigDecimal(String.valueOf(double)).setScale(yourScale, BigDecimal.ROUND_HALF_UP);

você receberá um BigDecimal. Para obter a cadeia de fora, é só chamar que BigDecimal's toStringmétodo ou o toPlainStringmétodo de Java 5+ para uma seqüência de formato simples.

Programa de exemplo:

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));
    }
MetroidFan2002
fonte
38
Essa é a minha solução preferida. Ainda mais curto: BigDecimal.valueOf (doubleVar) .setScale (yourScaleHere, BigDecimal.ROUND_HALF_UP); BigDecimal.valueOf (double val) na verdade chama Double.toString () sob o capô;)
Etienne Neveu
4
Agradável. Não corte cantos e uso new BigDecimal(doubleVar), como você pode ter problemas com arredondamento de pontos flutuantes
Edd
8
@ Edd, curiosamente, a questão do arredondamento ocorre no caso de SebastiaanvandenBroek mencionar no comentário a resposta do asterita. double val = 265.335;, BigDecimal.valueOf(val).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString();=> 265.34, mas (new BigDecimal(val)).setScale(decimals, BigDecimal.ROUND_HALF_UP).toPlainString();=> 265.33.
Home
7
@ToolmakerSteve Isso ocorre porque o uso do novo BigDecimal com o dobro pega o valor duplo diretamente e tenta usá-lo para criar o BigDecimal, enquanto que ao usar o BigDecimal.valueOf ou o formato alternativo o analisa primeiro para uma string (uma representação mais exata) antes da conversão .
MetroidFan2002
116

Você também pode usar o

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

para garantir que você tenha os zeros à direita.

Milhous
fonte
25
Eu acredito que uma das metas da questão era que "não deve não haver quaisquer zeros à direita".
Lunchbox
7
Para esta pergunta, o op não queria zeros, mas era exatamente isso que eu queria. Se você tem uma lista de números com 3 casas decimais, você quer que eles têm todos os mesmos dígitos mesmo que seja 0.
Tom Kincaid
Você esqueceu de especificarRoundingMode.
IgorGanapolsky 29/09
1
@IgorGanapolsky por Decimal modeusos padrãoRoundingMode.HALF_EVEN.
EndermanAPM
87

Como alguns outros observaram, a resposta correta é usar um DecimalFormatou BigDecimal. O ponto flutuante não possui casas decimais, portanto, você não pode arredondar / truncar para um número específico deles em primeiro lugar. Você tem que trabalhar em um radical decimal, e é isso que essas duas classes fazem.

Estou postando o código a seguir como um contra-exemplo para todas as respostas neste segmento e, de fato, em todo StackOverflow (e em outros lugares) que recomendam multiplicação seguida de truncamento e divisão. Cabe aos defensores dessa técnica explicar por que o código a seguir produz a saída errada em mais de 92% dos casos.

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

Saída deste programa:

10001 trials 9251 errors

EDIT: Para endereçar alguns comentários abaixo, refiz a parte do módulo do loop de teste usando BigDecimale new MathContext(16)para a operação do módulo da seguinte maneira:

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

Resultado:

10001 trials 4401 errors
Marquês de Lorne
fonte
8
O truque é que, em todos os seus erros 9251, o resultado impresso ainda está correto.
Didier L
7
@DidierL Isso não me surpreende. Tive a sorte de fazer 'Métodos Numéricos' como meu primeiro curso de computação e ser introduzido logo no início ao que o ponto flutuante pode ou não fazer. A maioria dos programadores é bastante vaga.
Marquês de Lorne,
15
Tudo o que você está fazendo é refutar que flutuante não representa exatamente muitos valores decimais, o que eu espero que todos entendamos. Não que o arredondamento cause um problema. Como você admite, os números ainda são impressos conforme o esperado.
precisa saber é o seguinte
8
Seu teste está quebrado, faça a ronda () e o teste falha 94% das vezes. ideone.com/1y62CY imprime 100 trials 94 errorsVocê deve começar com um teste aprovado e mostrar que a introdução do arredondamento interrompe o teste.
precisa saber é o seguinte
6
Refutação, refutada aqui. Usando Math.round para esta gama de doublecomo nenhum erro ideone.com/BVCHh3
Peter Lawrey
83

Suponha que você tenha

double d = 9232.129394d;

você pode usar BigDecimal

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

ou sem BigDecimal

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

com ambas as soluções d == 9232.13

user593581
fonte
2
Eu acho que essa é a melhor solução para usuários do Java 1.5 (e abaixo). Um comentário, não use o modo de arredondamento HALF_EVEN, pois ele possui um comportamento diferente para números pares e ímpares (2,5 arredondamentos para 2 enquanto 5,5 arredondamentos para 6, por exemplo), a menos que seja isso que você deseja.
IcedDante
4
A primeira solução está correta: a segunda não funciona. Veja aqui a prova.
Marquês de Lorne # /
1
@ EPJ: Mesmo a primeira solução com RoundingMode.HALF_UPestá errada. Experimente com 1.505. O caminho certo é usar BigDecimal.valueOf(d).
Matthias Braun
Matthias Braun, a solução é boa, daí 31 ups .. 1,505 decimal é armazenado em ponto flutuante duplo como 1,50499998 se você quiser pegar 1,505 e converter de dobro em decimal, será necessário convertê-lo em Double.toString (x) primeiro em seguida, coloque-o em um BigDecimal (), mas isso é extremamente lento e derrota o objetivo de usar o dobro da velocidade em primeiro lugar.
Hamish
1
Executei um loop de 100k com o caminho BigDecimal (demorou 225 ms) e Math.round (2 ms) e aqui está o tempo ... Tempo gasto: 225 mili segundos para converter usando: 9232.13 Tempo gasto: 2 mili segundos para converter para : 9232.13 techiesinfo.com
user1114134
59

Você pode usar a classe DecimalFormat.

double d = 3.76628729;

DecimalFormat newFormat = new DecimalFormat("#.##");
double twoDecimal =  Double.valueOf(newFormat.format(d));
JibW
fonte
Alguma razão pela qual Double.valueOf()foi escolhido Double.parseDouble()? O valueOf()método retorna um Doubleobjeto, enquanto parseDouble()retornará um doubleprimitivo. Com a maneira como o código atual é gravado, você também aplica o desempacotamento automático ao retorno para convertê-lo no primitivo que sua twoDoublevariável espera, uma operação extra de bytecode. Eu mudaria a resposta para usar em seu parseDouble()lugar.
Ecclodie
Double.parseDouble()precisa de Stringentrada.
Ryde
38

O Java How-to da Real publica essa solução, que também é compatível com versões anteriores ao Java 1.6.

BigDecimal bd = new BigDecimal(Double.toString(d));
bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
return bd.doubleValue();
Ovesh
fonte
33
double myNum = .912385;
int precision = 10000; //keep 4 digits
myNum= Math.floor(myNum * precision +.5)/precision;
Chris Cudmore
fonte
2
sim, é exatamente isso que math.round faz para números positivos, mas você já tentou isso com números negativos? as pessoas estão usando math.round nas outras soluções para também cobrir o caso de números negativos.
22414
Nota: Math.floor(x + 0.5)eMath.round(x)
Peter Lawrey
30

@Milhous: o formato decimal para arredondamento é excelente:

Você também pode usar o

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

para garantir que você tenha os zeros à direita.

Eu acrescentaria que esse método é muito bom em fornecer um mecanismo de arredondamento numérico real - não apenas visualmente, mas também durante o processamento.

Hipotético: você precisa implementar um mecanismo de arredondamento em um programa GUI. Para alterar a exatidão / precisão de uma saída de resultado, basta alterar o formato de cursor (entre colchetes). De modo a:

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

retornaria como saída: 0.912385

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

retornaria como saída: 0.91239

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

retornaria como saída: 0.9124

[EDIT: também se o formato de sinal de intercalação for assim ("# 0. ############") e você digitar um decimal, por exemplo, 3.1415926, pelo argumento, DecimalFormat não produzirá nenhum lixo ( por exemplo, zeros à direita) e retornará: 3.1415926.. se você estiver assim. Concedido, é um pouco detalhado para o gosto de alguns desenvolvedores - mas, ei, ele tem uma baixa presença de memória durante o processamento e é muito fácil de implementar.]

Portanto, essencialmente, a beleza do DecimalFormat é que ele lida simultaneamente com a aparência da string - assim como com o nível de precisão do arredondamento. Ergo: você obtém dois benefícios pelo preço de uma implementação de código. ;)

Ivan
fonte
2
Se você realmente deseja números decimais para cálculo (e não apenas para saída), não use um formato de ponto flutuante baseado em binário como double. Use BigDecimal ou qualquer outro formato baseado em decimal.
Pa Elo Ebermann
20

Aqui está um resumo do que você pode usar se desejar o resultado como 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();

Aqui está uma sugestão de quais bibliotecas você pode usar, se desejar doublecomo resultado. Eu não o recomendaria para conversão de string, pois o double pode não ser capaz de representar exatamente o que você deseja (veja, por exemplo, aqui ):

  1. Precisão do Apache Commons Math

    double rounded = Precision.round(0.912385, 5, BigDecimal.ROUND_HALF_UP);
  2. Funções da Colt

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

    double rounded = Utils.roundDouble(0.912385, 5)
Mifeet
fonte
18

Você pode usar o seguinte método utilitário -

public static double round(double valueToRound, int numberOfDecimalPlaces)
{
    double multipicationFactor = Math.pow(10, numberOfDecimalPlaces);
    double interestedInZeroDPs = valueToRound * multipicationFactor;
    return Math.round(interestedInZeroDPs) / multipicationFactor;
}
Amit
fonte
@ mariolpantunes: Falhará. Tente isto: round(1.005,2);ouround(0.50594724957626620092, 20);
Matthias Braun
Funciona. Mas flutuar e dobrar de forma não informativa são aproximações. Vamos considerar o seu primeiro exemplo. Se você imprimir a saída de interestInZeroDPs antes de Math.round, ela imprimirá 100.49999999999999. Você perdeu a precisão do
Math
double é um jejum! decimal é lento. os computadores não se incomodam em processar seus pensamentos em notação decimal. você precisa desistir de uma precisão decimal para manter o ponto flutuante duplamente rápido.
hamish
@hamish A questão é sobre precisão, não sobre velocidade.
Marquês de Lorne
13

Uma solução sucinta:

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

Consulte também https://stackoverflow.com/a/22186845/212950 Obrigado a jpdymond por oferecer isso.

MAbraham1
fonte
7

Tente isto: org.apache.commons.math3.util.Precision.round (double x, int scale)

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

A página inicial da Biblioteca de Matemática Apache Commons é: http://commons.apache.org/proper/commons-math/index.html

A implementação interna desse método é:

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;
        }
    }
}
Li Ying
fonte
7

Como não encontrei resposta completa sobre esse tema, montei uma classe que deveria lidar com isso corretamente, com suporte para:

  • Formatação : formate facilmente uma cadeia dupla com um certo número de casas decimais
  • Análise : analise o valor formatado novamente para dobrar
  • Localidade : Formate e analise usando o código do idioma padrão
  • Notação exponencial : comece a usar a notação exponencial após um certo limite

O uso é bem simples :

(Para o propósito deste exemplo, estou usando um código de idioma personalizado)

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

Aqui está a classe :

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

}
Andrei Lupsa
fonte
7

Se você realmente deseja números decimais para cálculo (e não apenas para saída), não use um formato de ponto flutuante baseado em binário como double.

Use BigDecimal or any other decimal-based format.

Eu uso o BigDecimal para cálculos, mas lembre-se de que depende do tamanho dos números com os quais você está lidando. Na maioria das minhas implementações, acho que a análise de duplo ou inteiro para Long é suficiente para cálculos de números muito grandes.

De fato, recentemente usei o parsed-long para obter representações precisas (em oposição aos resultados hexadecimais) em uma GUI para números tão grandes quanto ################### ############### caracteres (como exemplo).

Ivan
fonte
6

Para conseguir isso, podemos usar este formatador:

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

ou:

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

Use este método para obter sempre duas casas decimais:

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

Definindo esses valores:

91.32
5.22
11.5
1.2
2.6

Usando o método, podemos obter esses resultados:

91.32
5.22
11.50
1.20
2.60

demonstração online.

Jorgesys
fonte
5

Apenas no caso de alguém ainda precisar de ajuda com isso. Esta solução funciona perfeitamente para mim.

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

}

retorna a String com a saída desejada.

Asher. M. O
fonte
Por favor, inclua o motivo da votação no comentário, caso contrário é o que chamamos de intimidação.
Asher. O.
4

Concordo com a resposta escolhida para usar DecimalFormat--- ou alternativamenteBigDecimal .

Leia a atualização abaixo primeiro!

No entanto, se você não deseja arredondar o valor de casal e obter um doubleresultado de valor, você pode usar org.apache.commons.math3.util.Precision.round(..)como mencionado acima. A implementação usa BigDecimal, é lenta e cria lixo.

Um método semelhante, mas rápido e sem lixo, é fornecido pelo DoubleRounderutilitário na biblioteca decimal4j:

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

Saída

 0.667
 0.666
 1000.0
 9.00800700601E10

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

Isenção de responsabilidade: Estou envolvido no projeto decimal4j.

Atualização: como a @iaforek apontou, o DoubleRounder às vezes retorna resultados contra-intuitivos. O motivo é que ele executa arredondamentos matematicamente corretos. Por exemplo DoubleRounder.round(256.025d, 2), será arredondado para 256,02 porque o valor duplo representado como 256,025d é um pouco menor que o valor racional 256,025 e, portanto, será arredondado para baixo.

Notas:

  • Esse comportamento é muito semelhante ao do BigDecimal(double)construtor (mas não ao valueOf(double)qual usa o construtor de cadeias).
  • O problema pode ser contornado com uma etapa de arredondamento duplo para uma precisão mais alta primeiro, mas é complicado e não vou entrar nos detalhes aqui

Por esses motivos e tudo mencionado acima neste post, não posso recomendar o uso do DoubleRounder .

marco
fonte
Você tem métricas mostrando o quão eficiente é sua solução em comparação com as outras?
Iaforek 19/05
Não o comparei com outras soluções, mas há um benchmark jmh disponível no código-fonte: github.com/tools4j/decimal4j/blob/master/src/jmh/java/org/… Eu executei o benchmark em uma VM , os resultados estão disponíveis como arquivo csv aqui: github.com/tools4j/decimal4j/wiki/Performance
marco
1
O DoubleRounder falha nos seguintes casos: DoubleRounder.round (256.025d, 2) - esperado: 256.03, atual: 256.02 ou para DoubleRounder.round (260.775d, 2) - esperado: 260.78, real: 260.77.
iaforek
@iaforek: isso está correto, porque o DoubleRounder executa arredondamentos matematicamente corretos. No entanto, admito que isso seja um pouco contra-intuitivo e, portanto, atualizarei minha resposta de acordo.
marco
3

O trecho de código abaixo mostra como exibir n dígitos. O truque é definir a variável pp como 1 seguida por n zeros. No exemplo abaixo, o valor da variável pp possui 5 zeros, portanto, 5 dígitos serão exibidos.

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();
Jasdeep Singh
fonte
3

Se você estiver usando DecimalFormatpara converter doublepara String, é muito simples:

DecimalFormat formatter = new DecimalFormat("0.0##");
formatter.setRoundingMode(RoundingMode.HALF_UP);

double num = 1.234567;
return formatter.format(num);

Existem vários RoundingModevalores de enumeração para selecionar, dependendo do comportamento necessário.

Drew Noakes
fonte
3

Eu vim aqui apenas querendo uma resposta simples sobre como arredondar um número. Esta é uma resposta suplementar para fornecer isso.

Como arredondar um número em Java

O caso mais comum é usar Math.round().

Math.round(3.7) // 4

Os números são arredondados para o número inteiro mais próximo. Um .5valor é arredondado para cima. Se você precisar de um comportamento de arredondamento diferente daquele, poderá usar uma das outras funções matemáticas . Veja a comparação abaixo.

volta

Como mencionado acima, isso arredonda para o número inteiro mais próximo. .5decimais arredondados para cima. Este método retorna um 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

teto

Qualquer valor decimal é arredondado para o próximo número inteiro. Vai para o teto . Este método retorna a 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

chão

Qualquer valor decimal é arredondado para o próximo número inteiro. Este método retorna a 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

É semelhante ao arredondamento nos valores decimais, arredondado para o número inteiro mais próximo. No entanto, diferentemente round, os .5valores são arredondados para o número par. Este método retorna a 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 ***
Suragch
fonte
1
você está resolvendo apenas o caso específico de arredondar para 0 casas decimais. A pergunta original é mais genérica.
Lukas84 5/03/19
3

Se você estiver usando uma tecnologia que possui um JDK mínimo. Aqui está uma maneira sem nenhuma biblioteca Java:

double scale = 100000;    
double myVal = 0.912385;
double rounded = (int)((myVal * scale) + 0.5d) / scale;
Craigo
fonte
Isso falharia nos casos em que myVal não fosse menor que 1 e com zeros após decimal além do valor da escala. Digamos que você tenha myVal = 9.00000000912385; O acima retornará 9.0. Acho que devemos fornecer uma solução que funcione em todos os casos de myVal. Não especificamente para o valor que você declarou.
tavalendo 21/06/19
@ user102859 No seu exemplo, 9.0 é o resultado correto. Eu não entendo como isso falharia.
Craigo 26/06/19
2

DecimalFormat é a melhor maneira de produzir, mas eu não prefiro. Eu sempre faço isso o tempo todo, porque retorna o valor duplo. Para que eu possa usá-lo mais do que apenas produzir.

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

OU

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

Se você precisar de um valor grande de casas decimais, poderá usar BigDecimal. De qualquer forma .0é importante. Sem ele, o arredondamento de 0,33333d5 retorna 0,33333 e apenas 9 dígitos são permitidos. A segunda função sem .0problemas com 0,30000 retorna 0,30000000000000004.

Se Song
fonte
2

aqui está a minha resposta:

double num = 4.898979485566356;
DecimalFormat df = new DecimalFormat("#.##");      
time = Double.valueOf(df.format(num));

System.out.println(num); // 4.89
Mestre Jamal Uddin
fonte
1

Então, depois de ler a maioria das respostas, percebi que a maioria delas não será precisa, na verdade, usar BigDecimalparece ser a melhor escolha, mas se você não entender como as coisas RoundingModefuncionam, inevitavelmente perderá a precisão. Eu descobri isso ao trabalhar com grandes números em um projeto e pensei que poderia ajudar outras pessoas com problemas para arredondar números. Por exemplo.

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

Você esperaria obter 1363.28uma saída, mas acabará com o 1363.27que não é esperado, se não souber o que RoundingModeestá fazendo. Então, olhando para o Oracle Docs , você encontrará a seguinte descrição para RoundingMode.HALF_UP.

Modo de arredondamento para arredondar para o "vizinho mais próximo", a menos que os dois vizinhos sejam equidistantes; nesse caso, arredonde para cima.

Então, sabendo disso, percebemos que não obteríamos um arredondamento exato, a menos que desejássemos arredondar para o vizinho mais próximo . Portanto, para realizar uma rodada adequada, precisaríamos fazer um loop do n-1decimal para os dígitos decimais desejados. Por exemplo.

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

Isso acabará nos dando o resultado esperado, o que seria 1363.28.

Alain Cruz
fonte
0

Onde dp = casa decimal você deseja e o valor é um dobro.

    double p = Math.pow(10d, dp);

    double result = Math.round(value * p)/p;
alvo
fonte
1
Produz 1.0para value = 1.005e dp = 2. Use isso em seu lugar.
Matthias Braun
está ok Matt, seu exemplo não é válido. porque 1.005 não pode ser representado em ponto flutuante duplo de qualquer maneira. ele tem que ser armazenado realmente acima ou abaixo de 1.005, ou seja, é armazenado como o dobro quando você compila: 1.0049998 (não é armazenado como decimal no seu código compilado como você faria com os leitores acreditam) o objetivo está correto, ele está armazenando valores como ponto flutuante duplo, onde casos marginais como o seu são insignificantes de qualquer maneira. se fosse, você usaria o 3dp e depois o converteria em decimal, executando uma função de decimal decimal, assim como o link que você postou.
Hamish
1
@hamish Não vejo onde Matthias 'faria os leitores acreditarem' em algo que o valor seja compilado como decimal. Não coloque palavras na boca de outras pessoas.
Marquês de Lorne
0

Lembre-se de que String.format () e DecimalFormat produzem string usando o Locale padrão. Portanto, eles podem escrever um número formatado com ponto ou vírgula como um separador entre partes inteiras e decimais. Para garantir que a String arredondada esteja no formato que você deseja, use java.text.NumberFormat da seguinte forma:

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

Será impressa na localidade em inglês (independentemente da localidade): 0,99 123,57 123,00

O exemplo é retirado de Farenda - como converter o dobro para o String corretamente .

pwojnowski
fonte
0

Em geral, o arredondamento é feito pela escala: 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
Amr Ali
fonte