Como restringir um valor flutuante a apenas duas casas após o ponto decimal em C?

215

Como arredondar um valor flutuante (como 37,777779) para duas casas decimais (37,78) em C?

Sakib Arifin
fonte
15
Você não pode arredondar adequadamente o número em si, porque float(e double) não são vírgulas flutuantes decimais - elas são vírgulas flutuantes binárias -, portanto, o arredondamento para as casas decimais não faz sentido. Você pode arredondar a saída, no entanto.
Pavel Minaev 27/08/2009
63
Não tem sentido; é inexato. Há muita diferença.
Brooks Moses
2
Que tipo de arredondamento você espera? Half-up ou arredondamento para o par mais próximo?
Truthseeker Rangwan

Respostas:

407

Se você deseja arredondar o número apenas para fins de saída, a "%.2f"sequência de formatação é realmente a resposta correta. No entanto, se você realmente deseja arredondar o valor do ponto flutuante para cálculos adicionais, algo como o seguinte funciona:

#include <math.h>

float val = 37.777779;

float rounded_down = floorf(val * 100) / 100;   /* Result: 37.77 */
float nearest = roundf(val * 100) / 100;  /* Result: 37.78 */
float rounded_up = ceilf(val * 100) / 100;      /* Result: 37.78 */

Observe que você pode escolher três regras de arredondamento diferentes: arredondar para baixo (ou seja, truncar após duas casas decimais), arredondar para o mais próximo e arredondar para cima. Normalmente, você deseja arredondar para o mais próximo.

Como vários outros apontaram, devido às peculiaridades da representação de ponto flutuante, esses valores arredondados podem não ser exatamente os valores decimais "óbvios", mas estarão muito próximos.

Para muito (muito!) Mais informações sobre arredondamento, e especialmente sobre regras de desempate para arredondar para o mais próximo, consulte o artigo da Wikipedia sobre Arredondamento .

Dale Hagglund
fonte
4
Pode ser modificado para suportar o arredondamento com precisão arbitrária?
1
@slater Quando você diz "precisão arbitrária", está perguntando sobre o arredondamento para, por exemplo, três em vez de duas casas decimais ou usando bibliotecas que implementam valores decimais de precisão ilimitada? Se for o primeiro, faça o que espero que sejam ajustes óbvios às constantes 100; caso contrário, faça exatamente os mesmos cálculos mostrados acima, apenas com qualquer biblioteca de precisão múltipla que você estiver usando.
Dale Hagglund
2
@DaleHagglung O primeiro, obrigado. O ajuste é para substituir 100 por pow (10, (int) desejadaPrecisão)?
3
Sim. Para arredondar após k casas decimais, use um fator de escala de 10 ^ k. Deve ser realmente fácil ver se você escreve alguns valores decimais à mão e brinca com múltiplos de 10. Suponha que você esteja trabalhando com o valor 1,23456789 e queira arredondá-lo para 3 casas decimais. A operação disponível para você é arredondada para inteiro . Então, como você move as três primeiras casas decimais para que elas fiquem à esquerda do ponto decimal? Espero que fique claro que você multiplica por 10 ^ 3. Agora você pode arredondar esse valor para um número inteiro. Em seguida, você coloca os três dígitos da ordem baixa de volta dividindo por 10 ^ 3.
Dale Hagglund
1
Posso fazer isso funcionar de doublesalguma maneira? Parece não fazer o trabalho que eu quero :( (usando floore ceil). #
319 Nobody
87

Usando % .2f em printf. Imprime apenas 2 casas decimais.

Exemplo:

printf("%.2f", 37.777779);

Resultado:

37.77
Andrew Coleson
fonte
Dessa forma, é melhor porque não há perda de precisão.
Albert
2
@ albert Isso também tem a vantagem de não haver perda de floatalcance, pois val * 100poderia estourar.
chux - Restabelece Monica
42

Supondo que você esteja falando sobre o valor da impressão, as respostas de Andrew Coleson e AraK estão corretas:

printf("%.2f", 37.777779);

Mas observe que, se você deseja arredondar o número para exatamente 37,78 para uso interno (por exemplo, comparar com outro valor), isso não é uma boa ideia, devido à maneira como os números de ponto flutuante funcionam: você normalmente não deseja fazer comparações de igualdade para ponto flutuante; em vez disso, use um valor-alvo +/- um valor sigma. Ou codifique o número como uma sequência com uma precisão conhecida e compare-a.

Veja o link na resposta de Greg Hewgill para uma pergunta relacionada , que também cobre por que você não deve usar ponto flutuante para cálculos financeiros.

John Carter
fonte
1
Promovido o voto por abordar o que pode ser a pergunta por trás da pergunta (ou a pergunta que deveria estar por trás da pergunta!). Esse é um ponto bastante importante.
Brooks Moses
Na verdade, 37,78 podem ser apresentados exatamente por ponto flutuante. O flutuador tem 11 a 12 dígitos para precissão. Isso deve ser suficiente para abordar 3778 377,8 ou todo o tipo de 4 dígitos decimais.
Anonymous White
@HaryantoCiu sim, é justo, editei minha resposta um pouco.
John Carter
precisão dinâmica:printf("%.*f", (int)precision, (double)number);
Minhas Kamal 26/02
24

Que tal agora:

float value = 37.777779;
float rounded = ((int)(value * 100 + .5) / 100.0);
Daniil
fonte
4
-1: a) isso não funcionará para números negativos (ok, o exemplo é positivo, mas ainda). b) você não mencionar que é impossível para armazenar o valor decimal exata no flutuador
John Carter
32
@therefromhere: (a) Você está certo (b) O que é isso? Um teste do ensino médio?
28409 Daniil
1
por que você adicionou 0,5?
muhammad tayyab
1
É necessário seguir as regras de arredondamento.
Daniil
1
regras de arredondamento no contexto do comentário @Daniil são rodada a mais próxima
Shmil The Cat
20
printf("%.2f", 37.777779);

Se você deseja gravar na string C:

char number[24]; // dummy size, you should take care of the size!
sprintf(number, "%.2f", 37.777779);
AraK
fonte
@ Sinin: Por que a edição? @ Arak: Não, você deve cuidar do tamanho :). Use snprintf ().
aib
1
@aib: Eu acho que porque / ** / são comentários de estilo C e a questão é marcado para C
Michael Haren
5
C89 somente permitido / ** / - estilo, C99 introduziu suporte para // - estilo. Use um compilador coxo / antigo (ou force o modo C89) e você não poderá usar o estilo //. Dito isto, em 2009, vamos considerá-los no estilo C e C ++.
Andrew Coleson 04/04/09
11

Não existe uma maneira de arredondar de um floatpara outro floatporque o arredondado floatpode não ser representável (uma limitação dos números de ponto flutuante). Por exemplo, digamos que você arredonde 37.777779 para 37.78, mas o número representável mais próximo é 37.781.

No entanto, você pode "arredondar" a floatusando uma função de seqüência de caracteres de formato.

Andrew Keeton
fonte
3
Isso não é diferente de dizer "não há como dividir dois carros alegóricos e obter um carros alegóricos, porque o resultado dividido pode não ser representável", o que pode ser exatamente verdadeiro, mas é irrelevante. Os carros alegóricos são sempre inexatos, mesmo para algo tão básico quanto a adição; o pressuposto é sempre que o que você realmente recebe é "o flutuador que mais se aproxima da resposta exata e arredondada".
Brooks Moses
O que eu quis dizer é que você não pode arredondar de uma floata n casas decimais e esperar que o resultado sempre tenha n casas decimais. Você ainda receberá um float, mas não o que você esperava.
Andrew Keeton
9

Além disso, se você estiver usando C ++, você pode apenas criar uma função como esta:

string prd(const double x, const int decDigits) {
    stringstream ss;
    ss << fixed;
    ss.precision(decDigits); // set # places after decimal
    ss << x;
    return ss.str();
}

Em seguida, você pode emitir qualquer duplo myDoublecom ncasas após o ponto decimal com código como este:

std::cout << prd(myDouble,n);
synaptik
fonte
7

Você ainda pode usar:

float ceilf(float x); // don't forget #include <math.h> and link with -lm.

exemplo:

float valueToRound = 37.777779;
float roundedValue = ceilf(valueToRound * 100) / 100;
ZeroCool
fonte
Isso trunca no ponto decimal (ou seja, produzirá 37) e ele precisa arredondar para duas casas após o ponto decimal.
Pavel Minaev 27/08/09
Arredondar para duas casas após o ponto decimal é uma variação trivial (embora ainda deva ser mencionada na resposta; ZeroCool, deseja adicionar uma edição?): Float roundedValue = ceilf (valueToRound * 100.0) / 100.0;
Brooks Moses
Em um estado de sono :)
zerocool
Por que essa solução não é mais popular? Isso funciona exatamente como deveria com o mínimo de código. Existe alguma ressalva com isso?
Andy
7

No C ++ (ou no C com conversão no estilo C), você pode criar a função:

/* Function to control # of decimal places to be output for x */
double showDecimals(const double& x, const int& numDecimals) {
    int y=x;
    double z=x-y;
    double m=pow(10,numDecimals);
    double q=z*m;
    double r=round(q);

    return static_cast<double>(y)+(1.0/m)*r;
}

Então std::cout << showDecimals(37.777779,2);produziria: 37.78.

Obviamente, você realmente não precisa criar todas as 5 variáveis ​​nessa função, mas eu as deixo lá para que você possa ver a lógica. Provavelmente existem soluções mais simples, mas isso funciona bem para mim - especialmente porque me permite ajustar o número de dígitos após a casa decimal conforme necessário.

synaptik
fonte
5

Sempre use a printffamília de funções para isso. Mesmo se você quiser obter o valor como flutuante, é melhor usá-lo snprintfpara obter o valor arredondado como uma sequência e analisá-lo novamente com atof:

#include <math.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>

double dround(double val, int dp) {
    int charsNeeded = 1 + snprintf(NULL, 0, "%.*f", dp, val);
    char *buffer = malloc(charsNeeded);
    snprintf(buffer, charsNeeded, "%.*f", dp, val);
    double result = atof(buffer);
    free(buffer);
    return result;
}

Digo isso porque a abordagem mostrada pela resposta atualmente mais votada e várias outras aqui - multiplicando por 100, arredondando para o número inteiro mais próximo e depois dividindo por 100 novamente - é falha de duas maneiras:

  • Para alguns valores, ele será arredondado na direção errada porque a multiplicação por 100 altera o dígito decimal, determinando a direção do arredondamento de 4 para 5 ou vice-versa, devido à imprecisão de números de ponto flutuante
  • Para alguns valores, multiplicar e depois dividir por 100 não faz ida e volta, o que significa que, mesmo que nenhum arredondamento ocorra, o resultado final estará errado

Para ilustrar o primeiro tipo de erro - a direção do arredondamento às vezes está errada - tente executar este programa:

int main(void) {
    // This number is EXACTLY representable as a double
    double x = 0.01499999999999999944488848768742172978818416595458984375;

    printf("x: %.50f\n", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.50f\n", res1);
    printf("Rounded with round, then divided: %.50f\n", res2);
}

Você verá esta saída:

x: 0.01499999999999999944488848768742172978818416595459
Rounded with snprintf: 0.01000000000000000020816681711721685132943093776703
Rounded with round, then divided: 0.02000000000000000041633363423443370265886187553406

Observe que o valor com o qual começamos era menor que 0,015 e, portanto, a resposta matematicamente correta ao arredondá-lo para 2 casas decimais é 0,01. Obviamente, 0,01 não é exatamente representável como um dobro, mas esperamos que nosso resultado seja o dobro mais próximo de 0,01. Usar snprintfnos dá esse resultado, mas usar round(100 * x) / 100nos dá 0,02, o que está errado. Por quê? Porque 100 * xnos dá exatamente 1,5 como resultado. Multiplicar por 100 altera a direção correta para arredondar.

Para ilustrar o segundo tipo de erro - o resultado, às vezes, é errado devido * 100e / 100não é realmente um inverso um do outro - podemos fazer um exercício semelhante com um número muito grande:

int main(void) {
    double x = 8631192423766613.0;

    printf("x: %.1f\n", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.1f\n", res1);
    printf("Rounded with round, then divided: %.1f\n", res2);
}

Nosso número agora nem tem uma parte fracionária; é um valor inteiro, apenas armazenados com o tipo double. Então o resultado após o arredondamento deve ser o mesmo número com o qual começamos, certo?

Se você executar o programa acima, verá:

x: 8631192423766613.0
Rounded with snprintf: 8631192423766613.0
Rounded with round, then divided: 8631192423766612.0

Opa Nosso snprintfmétodo retorna o resultado certo novamente, mas a abordagem de multiplicar, arredondar e dividir falha. Isso porque o valor matematicamente correta de 8631192423766613.0 * 100, 863119242376661300.0, não é exatamente representável como um duplo; o valor mais próximo é 863119242376661248.0. Quando você divide isso de volta por 100, obtém 8631192423766612.0- um número diferente do que você começou.

Espero que seja uma demonstração suficiente de que o uso roundfpara arredondar para um número de casas decimais está quebrado e que você deve usá-lo snprintf. Se isso parecer um truque horrível para você, talvez você fique tranquilo ao saber que é basicamente o que o CPython faz .

Mark Amery
fonte
+1 para um exemplo concreto do que está errado com a minha resposta e similares, graças à estranheza do ponto flutuante do IEEE, e fornecendo uma alternativa direta. Eu estava ciente dos periféricos, há muito tempo, muito esforço colocado na mídia impressa e os amigos deles são seguros para valores de ponto flutuante. Eu acho que o trabalho feito então pode estar aparecendo aqui.
Dale Hagglund 27/08/19
Ah ... desculpe pela palavra salada perto do final, que agora é tarde demais para editar. O que eu quis dizer foi "... um monte de esforço colocado em printf e amigos para torná-los seguros ..."
Dale Hagglund
4

Use float roundf(float x).

"As funções arredondadas arredondam seu argumento para o valor inteiro mais próximo no formato de ponto flutuante, arredondando os casos até a metade do zero, independentemente da direção de arredondamento atual." C11dr §7.12.9.5

#include <math.h>
float y = roundf(x * 100.0f) / 100.0f; 

Dependendo da sua floatimplementação, os números que podem parecer a meio caminho não são. como ponto flutuante é tipicamente orientado para a base 2. Além disso, o arredondamento preciso para o mais próximo 0.01em todos os casos "na metade do caminho" é mais desafiador.

void r100(const char *s) {
  float x, y;
  sscanf(s, "%f", &x);
  y = round(x*100.0)/100.0;
  printf("%6s %.12e %.12e\n", s, x, y);
}

int main(void) {
  r100("1.115");
  r100("1.125");
  r100("1.135");
  return 0;
}

 1.115 1.115000009537e+00 1.120000004768e+00  
 1.125 1.125000000000e+00 1.129999995232e+00
 1.135 1.134999990463e+00 1.139999985695e+00

Embora "1.115" esteja "no meio do caminho" entre 1.11 e 1.12, quando convertido para float, o valor é 1.115000009537...e não é mais "no meio do caminho", mas mais próximo do 1.12 e arredondado para o mais próximo floatde1.120000004768...

"1.125" é "intermediário" entre 1.12 e 1.13, quando convertido para float, o valor é exatamente 1.125e é " intermediário ". Arredonda para 1,13 devido a vínculos com a regra par e arredonda para o mais próximo floatde1.129999995232...

Embora "1.135" esteja "no meio do caminho" entre 1,13 e 1,14, quando convertido para float, o valor é 1.134999990463...e não é mais "no meio do caminho", mas mais próximo do 1,13 e arredondado para o mais próximo floatde1.129999995232...

Se código usado

y = roundf(x*100.0f)/100.0f;

Apesar de "1.135" é "meio caminho" entre 1,13 e 1,14, quando convertido para float, o valor é 1.134999990463...e não é "meio caminho", mas mais perto de 1.13, mas incorretamente rodadas para floatde 1.139999985695..., devido à precisão mais limitada de floatvs. double. Esse valor incorreto pode ser visto como correto, dependendo das metas de codificação.

chux - Restabelecer Monica
fonte
4

Eu fiz essa macro para arredondar números flutuantes. Adicione-o no seu cabeçalho / sendo do arquivo

#define ROUNDF(f, c) (((float)((int)((f) * (c))) / (c)))

Aqui está um exemplo:

float x = ROUNDF(3.141592, 100)

x é igual a 3,14 :)

mou
fonte
Isso trunca, mas a pergunta solicita arredondamento. Além disso, está sujeito a erros de arredondamento em operações de ponto flutuante.
Eric Postpischil 01/12/19
3
double f_round(double dval, int n)
{
    char l_fmtp[32], l_buf[64];
    char *p_str;
    sprintf (l_fmtp, "%%.%df", n);
    if (dval>=0)
            sprintf (l_buf, l_fmtp, dval);
    else
            sprintf (l_buf, l_fmtp, dval);
    return ((double)strtod(l_buf, &p_str));

}

Aqui nestá o número de casas decimais

exemplo:

double d = 100.23456;

printf("%f", f_round(d, 4));// result: 100.2346

printf("%f", f_round(d, 2));// result: 100.23
user2331026
fonte
-1 por quatro razões: 1) a falta de explicação, 2) a vulnerabilidade ao buffer overflow - isso irá estourar e, portanto, possivelmente travar, se dvalfor enorme 3) o estranho if/ elsebloco em que você faz exatamente a mesma coisa em cada ramificação e 4) o uso excessivamente complicado de sprintfpara criar o especificador de formato para uma segunda sprintfchamada; é mais simples usar .*e passar o valor duplo e o número de casas decimais como argumentos para a mesma sprintfchamada.
Mark Amery
3

Definição de código:

#define roundz(x,d) ((floor(((x)*pow(10,d))+.5))/pow(10,d))

Resultados :

a = 8.000000
sqrt(a) = r = 2.828427
roundz(r,2) = 2.830000
roundz(r,3) = 2.828000
roundz(r,5) = 2.828430
Privacidade Zyp
fonte
0

Deixe-me primeiro tentar justificar minha razão para adicionar mais uma resposta a esta pergunta. Em um mundo ideal, arredondar não é realmente um grande problema. No entanto, em sistemas reais, você pode precisar lidar com vários problemas que podem resultar em arredondamentos que podem não ser o que você espera. Por exemplo, você pode estar executando cálculos financeiros em que os resultados finais são arredondados e exibidos aos usuários como duas casas decimais; esses mesmos valores são armazenados com precisão fixa em um banco de dados que pode incluir mais de duas casas decimais (por várias razões; não há um número ideal de locais a serem mantidos ... depende de situações específicas que cada sistema deve suportar, por exemplo, itens minúsculos cujos preços são frações de um centavo por unidade); e, cálculos de ponto flutuante executados em valores em que os resultados são mais / menos epsilon. Venho enfrentando essas questões e desenvolvendo minha própria estratégia ao longo dos anos. Não vou afirmar que enfrentei todos os cenários ou tenho a melhor resposta, mas abaixo está um exemplo da minha abordagem até agora que supera esses problemas:

Suponha que 6 casas decimais sejam consideradas como precisão suficiente para cálculos em flutuadores / duplos (uma decisão arbitrária para a aplicação específica), usando a seguinte função / método de arredondamento:

double Round(double x, int p)
{
    if (x != 0.0) {
        return ((floor((fabs(x)*pow(double(10.0),p))+0.5))/pow(double(10.0),p))*(x/fabs(x));
    } else {
        return 0.0;
    }
}

O arredondamento para duas casas decimais para a apresentação de um resultado pode ser realizado da seguinte forma:

double val;
// ...perform calculations on val
String(Round(Round(Round(val,8),6),2));

Pois val = 6.825, o resultado é 6.83o esperado.

Pois val = 6.824999, o resultado é 6.82. Aqui, a suposição é que o cálculo resultou exatamente 6.824999e a sétima casa decimal é zero.

Pois val = 6.8249999, o resultado é 6.83. A sétima casa decimal, 9nesse caso, faz com que a Round(val,6)função dê o resultado esperado. Nesse caso, pode haver qualquer número de 9s à direita .

Pois val = 6.824999499999, o resultado é 6.83. Arredondar para a oitava casa decimal como primeiro passo, ou seja Round(val,8), cuida do único caso desagradável pelo qual um resultado calculado de ponto flutuante calcula 6.8249995, mas é representado internamente como 6.824999499999....

Finalmente, o exemplo da pergunta ... val = 37.777779resulta em 37.78.

Essa abordagem pode ser mais generalizada como:

double val;
// ...perform calculations on val
String(Round(Round(Round(val,N+2),N),2));

onde N é precisão a ser mantida para todos os cálculos intermediários em flutuadores / duplos. Isso funciona com valores negativos também. Não sei se essa abordagem é matematicamente correta para todas as possibilidades.

Tim D
fonte
0

Código C simples para arredondar um número:

float n = 3.56;
printf("%.f", n);

Isso produzirá:

4
Aqeel Shamsudheen
fonte
-1

... ou você pode fazer da maneira antiga, sem bibliotecas:

float a = 37.777779;

int b = a; // b = 37    
float c = a - b; // c = 0.777779   
c *= 100; // c = 77.777863   
int d = c; // d = 77;    
a = b + d / (float)100; // a = 37.770000;

Isso, é claro, se você deseja remover as informações extras do número.

NikosD
fonte
-2

esta função pega o número e a precisão e retorna o número arredondado

float roundoff(float num,int precision)
{
      int temp=(int )(num*pow(10,precision));
      int num1=num*pow(10,precision+1);
      temp*=10;
      temp+=5;
      if(num1>=temp)
              num1+=10;
      num1/=10;
      num1*=10;
      num=num1/pow(10,precision+1);
      return num;
}

ele converte o número do ponto flutuante em int, deslocando o ponto à esquerda e verificando a condição maior que cinco.

Kapil
fonte