round () para float em C ++


232

Eu preciso de uma função simples de arredondamento de ponto flutuante, assim:

double round(double);

round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1

Eu posso encontrar ceil()e floor()no math.h - mas não round().

Está presente na biblioteca C ++ padrão com outro nome ou está faltando?


1
Se você apenas deseja exibir o número como um número arredondado, parece que pode fazê-lo std::cout << std::fixed << std::setprecision(0) << -0.9, por exemplo.
21411 Frank

44
Protegendo isso ... Novos usuários com esquemas de arredondamento novos e brilhantes devem ler as respostas existentes primeiro.
Shog9

12
roundestá disponível desde C ++ 11 in <cmath>. Infelizmente, se você estiver em Microsoft Visual Studio ainda está faltando: connect.microsoft.com/VisualStudio/feedback/details/775474/...
Alessandro Jacopson

3
Como observo na minha resposta, fazer o seu próprio roundrol tem muitas ressalvas. Antes do C ++ 11, o padrão contava com o C90, que não incluía round. O C ++ 11 conta com o C99, que possui, roundmas também como observei, truncque possui propriedades diferentes e pode ser mais apropriado, dependendo do aplicativo. A maioria das respostas também parece ignorar que um usuário pode retornar um tipo integral que tem ainda mais problemas.
Shafik Yaghmour

2
@uvts_cvs isso não parece ser um problema da versão mais recente do visual studio, veja ao vivo .
Shafik Yaghmour

Respostas:


144

Não há round () na biblioteca padrão do C ++ 98. Você pode escrever um você mesmo. A seguir, é apresentada uma implementação de metade do processo :

double round(double d)
{
  return floor(d + 0.5);
}

A provável razão pela qual não há função redonda na biblioteca padrão do C ++ 98 é que ela pode ser implementada de maneiras diferentes. A descrição acima é uma maneira comum, mas existem outras como o arredondamento para o par , que é menos tendencioso e geralmente melhor se você for fazer muitos arredondamentos; é um pouco mais complexo de implementar.


53
Isso não lida com números negativos corretamente. A resposta de litb está correta.
Usuário registrado

39
@InnerJoin: Sim, ele lida com números negativos de maneira diferente da resposta do litb, mas isso não o torna "incorreto".
Roddy

39
A adição de 0,5 antes do truncamento falha ao arredondar para o número inteiro mais próximo para várias entradas, incluindo 0,49999999999999994. Veja blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Pascal Cuoq

10
@ Sergi0: Não há "correto" e "incorreto" porque existem mais de uma definição de arredondamento que decide o que acontece no meio do caminho. Verifique seus fatos antes de julgar.
31413 Jon

16
@MuhammadAnnaqeeb: Você está certo, as coisas melhoraram imensamente desde o lançamento do C ++ 11. Esta pergunta foi feita e respondida em outro momento em que a vida era difícil e as alegrias eram poucas. Permanece aqui como uma homenagem aos heróis que viveram e lutaram naquela época e pelas pobres almas que ainda não conseguem usar ferramentas modernas.
Andreas Magnusson

96

O Boost oferece um conjunto simples de funções de arredondamento.

#include <boost/math/special_functions/round.hpp>

double a = boost::math::round(1.5); // Yields 2.0
int b = boost::math::iround(1.5); // Yields 2 as an integer

Para mais informações, consulte a documentação do Boost .

Editar : Como C ++ 11, existem std::round, std::lroundestd::llround .


2
Eu já estava usando o boost no meu projeto, +1 para isso, muito melhor do que usar a floor(value + 0.5)abordagem ingênua !
Gustavo Maciel

@GustavoMaciel Eu sei que estou um pouco atrasado para o jogo, mas impulsionar a implementação é floor(value + 0.5).
n. 'pronomes' m.

Na verdade, não existe: github.com/boostorg/math/blob/develop/include/boost/math/… 4 anos depois, eu também gostaria de dizer que floor(value + 0.5)não é nada ingênuo, mas depende do contexto e da natureza dos valores que você deseja arredondar!
Gustavo Maciel

84

O C ++ 03 norma baseia-se no padrão C90 para o que as chamadas padrão do C biblioteca padrão que é coberto no projecto C ++ 03 padrão ( mais próxima disponível publicamente projecto de norma para C ++ 03 é N1804 ) seção 1.2 Referências normativas :

A biblioteca descrita na cláusula 7 da ISO / IEC 9899: 1990 e cláusula 7 da ISO / IEC 9899 / Amd.1: 1995 é a seguir denominada Biblioteca C Padrão. 1)

Se formos à documentação C para round, lround, llround na cppreference , podemos ver que as funções round e relacionadas fazem parte do C99 e, portanto, não estarão disponíveis no C ++ 03 ou anterior.

No C ++ 11, isso muda, pois o C ++ 11 depende do padrão de rascunho C99 da biblioteca padrão C e, portanto, fornece std :: round e para os tipos de retorno integral std :: lround, std :: llround :

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::round( 0.4 ) << " " << std::lround( 0.4 ) << " " << std::llround( 0.4 ) << std::endl ;
    std::cout << std::round( 0.5 ) << " " << std::lround( 0.5 ) << " " << std::llround( 0.5 ) << std::endl ;
    std::cout << std::round( 0.6 ) << " " << std::lround( 0.6 ) << " " << std::llround( 0.6 ) << std::endl ;
}

Outra opção também do C99 seria std :: trunc, que:

Calcula o número inteiro mais próximo não maior em magnitude que arg.

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::trunc( 0.4 ) << std::endl ;
    std::cout << std::trunc( 0.9 ) << std::endl ;
    std::cout << std::trunc( 1.1 ) << std::endl ;

}

Se você precisa para apoiar non C ++ 11 aplicações a sua melhor aposta seria usar boost rodada, iround, lround, llround ou impulso trunc .

Rodar sua própria versão da rodada é difícil

Rolar o seu próprio provavelmente não vale o esforço, pois é mais difícil do que parece: arredondar a flutuação para o número inteiro mais próximo, parte 1 , arredondar a flutuação para o número inteiro mais próximo, parte 2 e Arredondar a flutuação para o número inteiro mais próximo, parte 3 explica:

Por exemplo, um teste comum de sua implementação usando std::floore adicionando 0.5não funciona para todas as entradas:

double myround(double d)
{
  return std::floor(d + 0.5);
}

Uma entrada pela qual falhará é 0.49999999999999994( veja ao vivo ).

Outra implementação comum envolve converter um tipo de ponto flutuante para um tipo integral, que pode chamar um comportamento indefinido no caso em que a parte integral não pode ser representada no tipo de destino. Podemos ver isso no rascunho da seção padrão do C ++ 4.9 Conversões integrais flutuantes que diz ( ênfase minha ):

Um pré-valor de um tipo de ponto flutuante pode ser convertido em um pré-valor de um tipo inteiro. A conversão trunca; isto é, a parte fracionária é descartada. O comportamento é indefinido se o valor truncado não puder ser representado no tipo de destino. [...]

Por exemplo:

float myround(float f)
{
  return static_cast<float>( static_cast<unsigned int>( f ) ) ;
}

Dado std::numeric_limits<unsigned int>::max()é 4294967295então a seguinte chamada:

myround( 4294967296.5f ) 

causará estouro ( veja ao vivo ).

Podemos ver o quão difícil isso realmente é, olhando para esta resposta da maneira Concisa de implementar round () em C? que faz referência à versão newlibs da precisão única flutuante. É uma função muito longa para algo que parece simples. Parece improvável que qualquer pessoa sem conhecimento íntimo das implementações de ponto flutuante possa implementar corretamente esta função:

float roundf(x)
{
  int signbit;
  __uint32_t w;
  /* Most significant word, least significant word. */
  int exponent_less_127;

  GET_FLOAT_WORD(w, x);

  /* Extract sign bit. */
  signbit = w & 0x80000000;

  /* Extract exponent field. */
  exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;

  if (exponent_less_127 < 23)
    {
      if (exponent_less_127 < 0)
        {
          w &= 0x80000000;
          if (exponent_less_127 == -1)
            /* Result is +1.0 or -1.0. */
            w |= ((__uint32_t)127 << 23);
        }
      else
        {
          unsigned int exponent_mask = 0x007fffff >> exponent_less_127;
          if ((w & exponent_mask) == 0)
            /* x has an integral value. */
            return x;

          w += 0x00400000 >> exponent_less_127;
          w &= ~exponent_mask;
        }
    }
  else
    {
      if (exponent_less_127 == 128)
        /* x is NaN or infinite. */
        return x + x;
      else
        return x;
    }
  SET_FLOAT_WORD(x, w);
  return x;
}

Por outro lado, se nenhuma das outras soluções for utilizável, newlib pode ser uma opção, pois é uma implementação bem testada.


5
@ downvoter, por favor, explique o que pode ser melhorado? A grande maioria das respostas aqui estão erradas, uma vez que tentam rolar sua própria rodada, que falha todas de uma forma ou de outra. Se houver algo faltando em minha explicação, entre em contato.
Shafik Yaghmour

1
Boa resposta completa - especialmente a parte abaixo de 0,5. Outro nicho: round(-0.0). C spec não parece especificar. Eu esperaria -0.0como resultado.
chux - Restabelece Monica

3
@chux interessante, e o padrão IEEE 754-2008 especifica que o arredondamento preserva sinais de zeros e infinitos (consulte 5.9).
Ruslan

1
@ Shafik, esta é uma ótima resposta. Eu nunca pensei que mesmo o arredondamento seja uma operação não trivial.
Ruslan

1
Talvez valha a pena mencionar que std::rint()geralmente é preferível std::round()quando o C ++ 11 está disponível por razões numéricas e de desempenho. Ele usa o modo de arredondamento atual, diferente round()do modo especial. Pode ser muito mais eficiente no x86, onde rintpode ser integrado a uma única instrução. (o gcc e o clang fazem isso mesmo sem o -ffast-math godbolt.org/g/5UsL2e , enquanto o clang apenas destaca o quase equivalente nearbyint()) O ARM tem suporte para instruções únicas round(), mas no x86 ele pode apenas alinhar com várias instruções e somente com-ffast-math
Peter Cordes

71

Pode ser interessante notar que, se você deseja um resultado inteiro do arredondamento, não precisa passar pelo teto ou pelo piso. Ou seja,

int round_int( double r ) {
    return (r > 0.0) ? (r + 0.5) : (r - 0.5); 
}

3
Não dá o resultado esperado para 0,49999999999999994 embora (bem, dependendo do que você espera é claro, mas 0 parece mais razoável para mim do que 1)
Stijn

@stijn Boa captura. Descobri que adicionar o sufixo literal duplo e longo às minhas constantes corrigia o problema de exemplo, mas não sei se existem outros exemplos de precisão que não seriam detectados.
kalaxy

1
btw, se você adicionar 0,49999999999999994 em vez de 0,5, ele funcionará bem para 0,49999999999999994 e 5000000000000001,0 como entrada. No entanto, não tenho certeza se está tudo bem com todos os valores e não consegui encontrar nenhuma referência afirmando que essa é a correção definitiva.
28513 stjn

1
@stijn Tudo bem para todos os valores, se você não se importa em qual direção os valores exatamente entre dois números inteiros são arredondados. Sem pensar, eu o provaria por análise de caso com os seguintes casos: 0 <= d <0,5, 0,5 <= d <1,5, 1,5 <= d <2 ^ 52, d> = 2 ^ 52. Também testei exaustivamente o gabinete de precisão única.
Pascal Cuoq

3
Por 4.9 [conv.fpint], "O comportamento é indefinido se o valor truncado não puder ser representado no tipo de destino". , então isso é um pouco perigoso. Outras respostas do SO descrevem como fazer isso de maneira robusta.
Tony Delroy

41

Está disponível desde C ++ 11 em cmath (de acordo com http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf )

#include <cmath>
#include <iostream>

int main(int argc, char** argv) {
  std::cout << "round(0.5):\t" << round(0.5) << std::endl;
  std::cout << "round(-0.5):\t" << round(-0.5) << std::endl;
  std::cout << "round(1.4):\t" << round(1.4) << std::endl;
  std::cout << "round(-1.4):\t" << round(-1.4) << std::endl;
  std::cout << "round(1.6):\t" << round(1.6) << std::endl;
  std::cout << "round(-1.6):\t" << round(-1.6) << std::endl;
  return 0;
}

Resultado:

round(0.5):  1
round(-0.5): -1
round(1.4):  1
round(-1.4): -1
round(1.6):  2
round(-1.6): -2

1
há também lrounde llroundpor resultados integrais
sp2danny

@ sp2danny: ou melhor, lrintpara usar o modo de arredondamento atual, em vez do roundtie-break longe de zero.
Peter Cordes

27

Geralmente é implementado como floor(value + 0.5).

Edit: e provavelmente não é chamado de round, já que existem pelo menos três algoritmos de arredondamento: arredondar para zero, arredondar para o número inteiro mais próximo e arredondar para banqueiros. Você está pedindo o arredondamento para o número inteiro mais próximo.


1
É bom fazer a distinção entre diferentes versões do 'round'. É bom saber quando escolher qual também.
Xtofl

5
De fato, existem algoritmos de arredondamento diferentes que podem fazer afirmações razoáveis ​​de estarem "corretos". No entanto, o piso (valor + 0,5) não é um deles. Para alguns valores, como 0,49999997f ou o dobro equivalente, a resposta está errada - ela será arredondada para 1,0 quando todos concordarem que deve ser zero. Veja esta publicação para obter detalhes: blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Bruce Dawson

14

Existem 2 problemas que estamos analisando:

  1. arredondando conversões
  2. conversão de tipo.

Conversões de arredondamento significam arredondamento ± flutuante / duplo para o piso / teto mais próximo / duplo mais próximo. Talvez o seu problema termine aqui. Mas se você espera retornar Int / Long, é necessário executar a conversão de tipo e, portanto, o problema "Estouro" pode atingir sua solução. SO, verifique se há erros na sua função

long round(double x) {
   assert(x >= LONG_MIN-0.5);
   assert(x <= LONG_MAX+0.5);
   if (x >= 0)
      return (long) (x+0.5);
   return (long) (x-0.5);
}

#define round(x) ((x) < LONG_MIN-0.5 || (x) > LONG_MAX+0.5 ?\
      error() : ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))

from: http://www.cs.tut.fi/~jkorpela/round.html


Usar LONG_MIN-0.5e LONG_MAX+0.5 introduzir complicações, pois a matemática pode não ser exata. LONG_MAXpode exceder a doubleprecisão para a conversão exata. Provavelmente, deseja assert(x < LONG_MAX+0.5); (<vs <=), pois LONG_MAX+0.5pode ser exatamente representável e (x)+0.5pode ter um resultado exato do LONG_MAX+1qual falha na longconversão. Outros problemas de canto também.
chux - Reinstala Monica

Não chame sua função round(double), já existe uma função de biblioteca matemática padrão com esse nome (no C ++ 11), por isso é confusa. Use std::lrint(x)se estiver disponível.
Peter Cordes

11

Um certo tipo de arredondamento também é implementado no Boost:

#include <iostream>

#include <boost/numeric/conversion/converter.hpp>

template<typename T, typename S> T round2(const S& x) {
  typedef boost::numeric::conversion_traits<T, S> Traits;
  typedef boost::numeric::def_overflow_handler OverflowHandler;
  typedef boost::numeric::RoundEven<typename Traits::source_type> Rounder;
  typedef boost::numeric::converter<T, S, Traits, OverflowHandler, Rounder> Converter;
  return Converter::convert(x);
}

int main() {
  std::cout << round2<int, double>(0.1) << ' ' << round2<int, double>(-0.1) << ' ' << round2<int, double>(-0.9) << std::endl;
}

Observe que isso funciona apenas se você fizer uma conversão para inteiro.


2
O Boost também oferece um conjunto de funções simples de arredondamento; veja minha resposta.
Daniel Wolf

Você também pode usar boost:numeric::RoundEven< double >::nearbyintdiretamente se não desejar um número inteiro. @DanielWolf note que a função simples é implementada usando +0.5, que tem problemas como os apresentados por aka.nice
stijn

6

Você pode arredondar para n dígitos de precisão com:

double round( double x )
{
const double sd = 1000; //for accuracy to 3 decimal places
return int(x*sd + (x<0? -0.5 : 0.5))/sd;
}

4
A menos que seu compilador tamanho int padrão é 1024 bits, isso não vai ser preciso para grande dupla ...
aka.nice

Eu acho que isso é aceitável quando é usado: se o seu valor duplo for 1.0 e + 19, arredondar para 3 lugares não faz sentido.
Carl

3
com certeza, mas a pergunta é para uma rodada genérica e você não pode controlar como ela será usada. Não há razão para a ronda falhar onde o teto e o chão não o fariam.
aka.nice

Isso tem um comportamento indefinido para argumentos fora do intervalo de int. (Na prática, no x86, fora-de-gama valores FP fará CVTTSD2SIproduto0x80000000 como o padrão de bits de número inteiro, ou seja INT_MIN, que irá então ser convertido de volta a double.
Pedro Cordes

5

Atualmente, não deve ser um problema usar um compilador C ++ 11 que inclua uma biblioteca matemática C99 / C ++ 11. Mas então a pergunta se torna: qual função de arredondamento você escolhe?

O C99 / C ++ 11 round()geralmente não é a função de arredondamento desejada . Ele usa um modo de arredondamento descolado que se afasta de 0 como um desempate em casos intermediários ( +-xxx.5000). Se você deseja especificamente esse modo de arredondamento, ou está direcionando uma implementação C ++ onde round()é mais rápida que rint(), use-a (ou emule seu comportamento com uma das outras respostas desta pergunta, que a levou ao valor nominal e reproduziu cuidadosamente essa específica comportamento de arredondamento.)

round()O arredondamento é diferente do arredondamento padrão IEEE754 para o modo mais próximo, mesmo com um desempate . O número mais próximo evita o viés estatístico na magnitude média dos números, mas faz um viés em relação aos números pares.

Existem duas funções de arredondamento da biblioteca matemática que usam o modo de arredondamento padrão atual: std::nearbyint()e std::rint(), ambas adicionadas no C99 / C ++ 11, estão disponíveis a qualquer momento std::round(). A única diferença é que nearbyintnunca gera FE_INEXACT.

Prefira rint()por motivos de desempenho : o gcc e o clang incorporam-no com mais facilidade, mas o gcc nunca alinha nearbyint()(mesmo com -ffast-math)


gcc / clang para x86-64 e AArch64

Coloquei algumas funções de teste no Compiler Explorer de Matt Godbolt , onde você pode ver a saída source + asm (para vários compiladores). Para saber mais sobre como ler a saída do compilador, consulte esta seção de perguntas e respostas , e a conversa de Matt no CppCon2017: “O que meu compilador fez por mim ultimamente? Desaparafusando a Tampa do Compilador ” ,

No código FP, geralmente é uma grande vitória incorporar pequenas funções. Especialmente em não-Windows, onde a convenção de chamada padrão não possui registros preservados, portanto o compilador não pode manter nenhum valor de FP nos registros XMM entre a call. Portanto, mesmo se você realmente não conhece asm, ainda pode ver facilmente se é apenas uma chamada final para a função de biblioteca ou se está alinhada com uma ou duas instruções matemáticas. Qualquer coisa que inclua uma ou duas instruções é melhor que uma chamada de função (para esta tarefa específica no x86 ou ARM).

No x86, qualquer coisa alinhada ao SSE4.1 roundsdpode se auto-vetorizar com o SSE4.1 roundpd(ou AVX vroundpd). (As conversões FP-> inteiras também estão disponíveis no formato SIMD compactado, exceto o número inteiro FP-> 64 bits, que requer o AVX512.)

  • std::nearbyint():

    • x86 clang: alinha para um único insn com -msse4.1.
    • x86 gcc: alinha apenas para um único insn com -msse4.1 -ffast-mathe apenas no gcc 5.4 e versões anteriores . Posteriormente, o gcc nunca o inline (talvez eles não tenham percebido que um dos bits imediatos pode suprimir a exceção inexata? É isso que o clang usa, mas o gcc mais antigo usa o mesmo imediato do que rintquando o inline)
    • AArch64 gcc6.3: alinha para um único insn por padrão.
  • std::rint:

    • x86 clang: alinha para um único insn com -msse4.1
    • x86 gcc7: alinha para um único insn com -msse4.1. (Sem SSE4.1, inline a várias instruções)
    • x86 gcc6.xe versões anteriores: alinha para um único insn com -ffast-math -msse4.1.
    • AArch64 gcc: inlines para um único insn por padrão
  • std::round:

    • x86 clang: não embutido
    • x86 gcc: alinha várias instruções com -ffast-math -msse4.1, exigindo duas constantes de vetor.
    • AArch64 gcc: alinha uma única instrução (suporte HW para este modo de arredondamento, bem como o padrão IEEE e a maioria dos outros.)
  • std::floor/ std::ceil/std::trunc

    • x86 clang: alinha para um único insn com -msse4.1
    • x86 gcc7.x: alinha para um único insn com -msse4.1
    • x86 gcc6.xe versões anteriores: alinha para um único insn com -ffast-math -msse4.1
    • AArch64 gcc: linhas por padrão para uma única instrução

Arredondamento para int/ long/ long long:

Você tem duas opções aqui: use lrint(como rintmas retorna long, ou long longpara llrint), ou use uma função de arredondamento FP-> FP e depois converta para um tipo inteiro da maneira normal (com truncamento). Alguns compiladores otimizam uma maneira melhor que a outra.

long l = lrint(x);

int  i = (int)rint(x);

Observe que int i = lrint(x)converte floatou double-> longprimeiro e, em seguida, trunca o número inteiro para int. Isso faz a diferença para números inteiros fora do intervalo: Comportamento indefinido em C ++, mas bem definido para as instruções x86 FP -> int (que o compilador emitirá, a menos que veja o UB em tempo de compilação enquanto faz propagação constante, então é permissão para criar código que quebre se alguma vez for executado).

No x86, uma conversão de número inteiro FP-> que excede o número inteiro produz INT_MINou LLONG_MIN(um padrão de bits 0x8000000ou o equivalente a 64 bits, apenas com o conjunto de bits de sinal). A Intel chama isso de valor "número inteiro indefinido". (Veja a cvttsd2sientrada manual , a instrução SSE2 que converte (com truncagem) escalar dupla de inteiro assinado. Ele está disponível com 32-bit ou destino inteiro de 64 bits (no modo de 64 bits apenas). Há também um cvtsd2si(converso com arredondamento atual ), que é o que gostaríamos que o compilador emitisse, mas infelizmente o gcc e o clang não farão isso sem -ffast-math.

unsignedLembre-se também de que FP para / de int / long é menos eficiente em x86 (sem AVX512). A conversão para 32 bits não assinados em uma máquina de 64 bits é bem barata; basta converter em assinado e truncado de 64 bits. Mas, caso contrário, é significativamente mais lento.

  • x86 clang com / sem -ffast-math -msse4.1: (int/long)rintalinha para roundsd/ cvttsd2si. (otimização perdida para cvtsd2si). lrintnão está alinhado.

  • x86 gcc6.xe versões anteriores sem -ffast-math: de nenhuma maneira

  • x86 gcc7 without -ffast-math: (int/long)rintarredonda e converte separadamente (com 2 instruções totais do SSE4.1 está ativada, caso contrário, com um monte de código indicado para rintsem roundsd). lrintnão inline.
  • x86 gcc com -ffast-math : todas as formas alinhadas a cvtsd2si(ideal) , sem necessidade de SSE4.1.

  • AArch64 gcc6.3 sem -ffast-math: (int/long)rintlinhas para 2 instruções. lrintnão alinha

  • AArch64 gcc6.3 com -ffast-math: (int/long)rintcompila para uma chamada para lrint. lrintnão inline. Pode ser uma otimização perdida, a menos que as duas instruções que recebemos -ffast-mathsejam muito lentas.

TODO: O ICC e o MSVC também estão disponíveis no Godbolt, mas não observei a saída deles para isso. edições bem-vindas ... Além disso: seria mais útil decompor primeiro o compilador / versão e depois a função nele? A maioria das pessoas não alterna os compiladores com base em quão bem eles compilam o arredondamento inteiro FP-> FP ou FP->.
Peter Cordes

2
+1 por recomendar rint()onde é uma opção viável, o que geralmente é o caso. Eu acho que o nome round()implica para alguns programadores que é isso que eles querem, enquanto rint()parece misterioso. Observe que round()não usa um modo de arredondamento "descolado": o arredondamento para o mais próximo é o modo de arredondamento oficial IEEE-754 (2008). É curioso que nearbyint()não fique alinhado, já que é basicamente o mesmo rint()e deve ser idêntico sob -ffast-mathcondições. Isso me parece inseto.
Njuffa 17/11

4

Cuidado com floor(x+0.5). Aqui está o que pode acontecer com números ímpares no intervalo [2 ^ 52,2 ^ 53]:

-bash-3.2$ cat >test-round.c <<END

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

int main() {
    double x=5000000000000001.0;
    double y=round(x);
    double z=floor(x+0.5);
    printf("      x     =%f\n",x);
    printf("round(x)    =%f\n",y);
    printf("floor(x+0.5)=%f\n",z);
    return 0;
}
END

-bash-3.2$ gcc test-round.c
-bash-3.2$ ./a.out
      x     =5000000000000001.000000
round(x)    =5000000000000001.000000
floor(x+0.5)=5000000000000002.000000

Este é http://bugs.squeak.org/view.php?id=7134 . Use uma solução como a do @konik.

Minha própria versão robusta seria algo como:

double round(double x)
{
    double truncated,roundedFraction;
    double fraction = modf(x, &truncated);
    modf(2.0*fraction, &roundedFraction);
    return truncated + roundedFraction;
}

Outro motivo para evitar o piso (x + 0,5) é apresentado aqui .


2
Estou interessado em saber sobre os votos negativos. É porque o empate é resolvido longe de zero, e não até o mais próximo?
aka.nice

1
Nota: a especificação C diz "arredondando os casos a meio caminho do zero, independentemente da direção de arredondamento atual". Portanto, o arredondamento sem considerar o ímpar / par é compatível.
chux - Restabelece Monica 3/15

4

Se você deseja converter a doublesaída de sua round()função em an int, as soluções aceitas dessa pergunta serão parecidas com:

int roundint(double r) {
  return (int)((r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5));
}

Isso gera cerca de 8,88 ns na minha máquina quando transmitido em valores aleatórios uniformemente.

O que segue abaixo é funcionalmente equivalente, tanto quanto eu posso dizer, mas tem 2,48 ns na minha máquina, para uma vantagem significativa de desempenho:

int roundint (double r) {
  int tmp = static_cast<int> (r);
  tmp += (r-tmp>=.5) - (r-tmp<=-.5);
  return tmp;
}

Entre as razões para o melhor desempenho está a ramificação ignorada.


Isso tem um comportamento indefinido para argumentos fora do intervalo de int. (Na prática, no x86, fora-de-gama valores FP fará CVTTSD2SIproduto0x80000000 como o padrão de bits de número inteiro, ou seja INT_MIN, que irá então ser convertido de volta a double.
Pedro Cordes

2

Não há necessidade de implementar nada, por isso não sei por que tantas respostas envolvem definições, funções ou métodos.

Em C99

Temos o seguinte ee cabeçalho <tgmath.h> para macros genéricas de tipo.

#include <math.h>
double round (double x);
float roundf (float x);
long double roundl (long double x);

Se você não pode compilar isso, provavelmente deixou a biblioteca de matemática. Um comando semelhante a este funciona em todos os compiladores C que tenho (vários).

gcc -lm -std=c99 ...

Em C ++ 11

Temos as seguintes sobrecargas e adicionais em #include <cmath> que dependem do ponto flutuante de precisão dupla IEEE.

#include <math.h>
double round (double x);
float round (float x);
long double round (long double x);
double round (T x);

Também existem equivalentes no namespace std .

Se você não pode compilar isso, pode estar usando a compilação C em vez de C ++. O comando básico a seguir não produz erros nem avisos com g ++ 6.3.1, x86_64-w64-mingw32-g ++ 6.3.0, clang-x86_64 ++ 3.8.0 e comunidade Visual C ++ 2015.

g++ -std=c++11 -Wall

Com a Divisão Ordinal

Ao dividir dois números ordinais, onde T é curto, int, longo ou outro ordinal, a expressão de arredondamento é essa.

T roundedQuotient = (2 * integerNumerator + 1)
    / (2 * integerDenominator);

Precisão

Não há dúvida de que imprecisões de aparência estranha aparecem em operações de ponto flutuante, mas isso é apenas quando os números aparecem e pouco tem a ver com o arredondamento.

A fonte não é apenas o número de dígitos significativos na mantissa da representação IEEE de um número de ponto flutuante, está relacionada ao nosso pensamento decimal como seres humanos.

Dez é o produto de cinco e dois, e 5 e 2 são relativamente primos. Portanto, os padrões de ponto flutuante IEEE não podem ser representados perfeitamente como números decimais para todas as representações digitais binárias.

Isso não é um problema com os algoritmos de arredondamento. É a realidade matemática que deve ser considerada durante a seleção de tipos e o design de cálculos, entrada de dados e exibição de números. Se um aplicativo exibir os dígitos que mostram esses problemas de conversão binário decimal, o aplicativo estará expressando visualmente a precisão que não existe na realidade digital e deve ser alterada.


1
"Não sei por que tantas respostas envolvem definições, funções ou métodos." Dê uma olhada quando foi solicitado - o C ++ 11 ainda não saiu. ;)
jaggedSpire

@jaggedSpire, bem, dê um joinha, se achar apropriado, porque todas as respostas de alta pontuação são obsoletas e enganosas no contexto dos compiladores mais usados ​​atualmente.
FauChristian # 03:

2

Função double round(double)com o uso da modffunção:

double round(double x)
{
    using namespace std;

    if ((numeric_limits<double>::max() - 0.5) <= x)
        return numeric_limits<double>::max();

    if ((-1*std::numeric_limits<double>::max() + 0.5) > x)
        return (-1*std::numeric_limits<double>::max());

    double intpart;
    double fractpart = modf(x, &intpart);

    if (fractpart >= 0.5)
        return (intpart + 1);
    else if (fractpart >= -0.5)
        return intpart;
    else
        return (intpart - 1);
    }

Para ser compilado, são necessários "math.h" e "limites". A função funciona de acordo com o seguinte esquema de arredondamento:

  • rodada de 5.0 é 5.0
  • a rodada de 3,8 é 4,0
  • a rodada de 2,3 é 2,0
  • ronda de 1,5 é 2,0
  • ronda de 0,501 é 1,0
  • ronda de 0,5 é 1,0
  • a rodada de 0,499 é 0,0
  • rodada de 0,01 é 0,0
  • a rodada de 0,0 é 0,0
  • a rodada de -0,01 é -0,0
  • a rodada de -0,499 é -0,0
  • a rodada de -0,5 é -0,0
  • a rodada de -0,501 é -1,0
  • a rodada de -1,5 é -1,0
  • a rodada de -2,3 é -2,0
  • a rodada de -3,8 é -4,0
  • a rodada de -5,0 é -5,0

2
Esta é uma boa solução. Não tenho certeza de que o arredondamento de -1,5 a -1,0 seja padrão, mas esperaria -2,0 por simetria. Também não vejo a ponta do guarda principal, os dois primeiros se pudessem ser removidos.
aka.nice

2
Eu verifiquei na norma ISO / IEC 10967-2, open-std.org/jtc1/sc22/wg11/docs/n462.pdf e no apêndice B.5.2.4, a função de arredondamento deve ser simétrica, arredondamento_F (x) = neg_F (rounding_F (neg_F (x)))
aka.nice

Este vai ser lento em comparação com C ++ 11 rint()ou nearbyint(), mas se você realmente não pode usar um compilador que fornece uma função de arredondamento adequada, e você precisa de precisão mais de desempenho ...
Peter Cordes

1

Se você precisar compilar código em ambientes que suportam o padrão C ++ 11, mas também precisar compilar o mesmo código em ambientes que não o suportam, use uma macro de função para escolher entre std :: round () e uma função personalizada para cada sistema. Basta passar -DCPP11ou /DCPP11para o compilador compatível com C ++ 11 (ou usar suas macros de versão internas) e criar um cabeçalho como este:

// File: rounding.h
#include <cmath>

#ifdef CPP11
    #define ROUND(x) std::round(x)
#else    /* CPP11 */
    inline double myRound(double x) {
        return (x >= 0.0 ? std::floor(x + 0.5) : std::ceil(x - 0.5));
    }

    #define ROUND(x) myRound(x)
#endif   /* CPP11 */

Para um exemplo rápido, consulte http://ideone.com/zal709 .

Isso aproxima std :: round () em ambientes que não são compatíveis com C ++ 11, incluindo a preservação do bit de sinal para -0.0. No entanto, isso pode causar um leve impacto no desempenho e provavelmente terá problemas com o arredondamento de certos valores conhecidos de ponto flutuante do "problema", como 0,49999999999999994 ou valores semelhantes.

Como alternativa, se você tiver acesso a um compilador compatível com C ++ 11, poderá simplesmente pegar std :: round () do <cmath>cabeçalho e usá-lo para criar seu próprio cabeçalho que define a função, se ainda não estiver definido. Observe que essa pode não ser uma solução ideal, especialmente se você precisar compilar para várias plataformas.


1

Com base na resposta do Kalaxy, a seguir é uma solução de modelo que arredonda qualquer número de ponto flutuante para o tipo inteiro mais próximo com base no arredondamento natural. Ele também gera um erro no modo de depuração se o valor estiver fora do intervalo do tipo inteiro, servindo assim aproximadamente como uma função de biblioteca viável.

    // round a floating point number to the nearest integer
    template <typename Arg>
    int Round(Arg arg)
    {
#ifndef NDEBUG
        // check that the argument can be rounded given the return type:
        if (
            (Arg)std::numeric_limits<int>::max() < arg + (Arg) 0.5) ||
            (Arg)std::numeric_limits<int>::lowest() > arg - (Arg) 0.5)
            )
        {
            throw std::overflow_error("out of bounds");
        }
#endif

        return (arg > (Arg) 0.0) ? (int)(r + (Arg) 0.5) : (int)(r - (Arg) 0.5);
    }

1
Como indiquei na minha resposta, adicionar 0.5não funciona em todos os casos. Embora pelo menos você lide com a questão do estouro, evite comportamentos indefinidos.
Shafik Yaghmour

1

Conforme apontado nos comentários e outras respostas, a biblioteca padrão ISO C ++ não adicionou round() até o ISO C ++ 11, quando essa função foi acessada por referência à biblioteca matemática padrão ISO C99.

Para operandos positivos em [½, ub ] round(x) == floor (x + 0.5), onde ub é 2 23 para floatquando mapeado para IEEE-754 (2008) binary32e 2 52 para doublequando é mapeado para IEEE-754 (2008) binary64. Os números 23 e 52 correspondem ao número de bits de mantissa armazenados nesses dois formatos de ponto flutuante. Para operandos positivos em [+0, ½) round(x) == 0e para operandos positivos em ( ub , + ∞] round(x) == x. Como a função é simétrica em relação ao eixo x, argumentos negativos xpodem ser manipulados de acordo comround(-x) == -round(x) .

Isso leva ao código compacto abaixo. Compila em um número razoável de instruções da máquina em várias plataformas. Eu observei o código mais compacto nas GPUs, onde my_roundf()requer cerca de uma dúzia de instruções. Dependendo da arquitetura do processador e da cadeia de ferramentas, essa abordagem baseada em ponto flutuante pode ser mais rápida ou mais lenta que a implementação baseada em número inteiro do newlib referenciado em uma resposta diferente .

Testei my_roundf()exaustivamente a roundf()implementação newlib usando o compilador Intel versão 13, com ambos /fp:stricte /fp:fast. Eu também verifiquei se a versão newlib corresponde à roundf()na mathimfbiblioteca do compilador Intel. Testes exaustivos não são possíveis para precisão dupla round(), no entanto, o código é estruturalmente idêntico à implementação de precisão única.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>

float my_roundf (float x)
{
    const float half = 0.5f;
    const float one = 2 * half;
    const float lbound = half;
    const float ubound = 1L << 23;
    float a, f, r, s, t;
    s = (x < 0) ? (-one) : one;
    a = x * s;
    t = (a < lbound) ? x : s;
    f = (a < lbound) ? 0 : floorf (a + half);
    r = (a > ubound) ? x : (t * f);
    return r;
}

double my_round (double x)
{
    const double half = 0.5;
    const double one = 2 * half;
    const double lbound = half;
    const double ubound = 1ULL << 52;
    double a, f, r, s, t;
    s = (x < 0) ? (-one) : one;
    a = x * s;
    t = (a < lbound) ? x : s;
    f = (a < lbound) ? 0 : floor (a + half);
    r = (a > ubound) ? x : (t * f);
    return r;
}

uint32_t float_as_uint (float a)
{
    uint32_t r;
    memcpy (&r, &a, sizeof(r));
    return r;
}

float uint_as_float (uint32_t a)
{
    float r;
    memcpy (&r, &a, sizeof(r));
    return r;
}

float newlib_roundf (float x)
{
    uint32_t w;
    int exponent_less_127;

    w = float_as_uint(x);
    /* Extract exponent field. */
    exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;
    if (exponent_less_127 < 23) {
        if (exponent_less_127 < 0) {
            /* Extract sign bit. */
            w &= 0x80000000;
            if (exponent_less_127 == -1) {
                /* Result is +1.0 or -1.0. */
                w |= ((uint32_t)127 << 23);
            }
        } else {
            uint32_t exponent_mask = 0x007fffff >> exponent_less_127;
            if ((w & exponent_mask) == 0) {
                /* x has an integral value. */
                return x;
            }
            w += 0x00400000 >> exponent_less_127;
            w &= ~exponent_mask;
        }
    } else {
        if (exponent_less_127 == 128) {
            /* x is NaN or infinite so raise FE_INVALID by adding */
            return x + x;
        } else {
            return x;
        }
    }
    x = uint_as_float (w);
    return x;
}

int main (void)
{
    uint32_t argi, resi, refi;
    float arg, res, ref;

    argi = 0;
    do {
        arg = uint_as_float (argi);
        ref = newlib_roundf (arg);
        res = my_roundf (arg);
        resi = float_as_uint (res);
        refi = float_as_uint (ref);
        if (resi != refi) { // check for identical bit pattern
            printf ("!!!! arg=%08x  res=%08x  ref=%08x\n", argi, resi, refi);
            return EXIT_FAILURE;
        }
        argi++;
    } while (argi);
    return EXIT_SUCCESS;
}

Fiz uma edição para evitar assumir que inttem mais de 16 bits de largura. É claro que ainda assume que floaté o IEEE754 binário de 4 bytes32. Um C ++ 11 static_assertou talvez uma macro #ifdef/ #errorpoderia verificar isso. (Mas é claro que se o C ++ 11 estiver disponível, você deve usar std::roundou, para o modo de arredondamento atual, usar std::rintalinhamentos agradáveis ​​com gcc e clang).
Peter Cordes

Entre, gcc -ffast-math -msse4.1alinha std::round()para um add( AND(x, L1), OR(x,L2)e, em seguida, um roundsd. isto é, implementa roundcom bastante eficiência em termos de rint. Mas não há razão para fazer isso manualmente na fonte C ++, porque se você possui std::rint()ou std::nearbyint()também possui std::round(). Veja minha resposta para obter um link godbolt e um resumo do que está alinhado ou não com diferentes versões do gcc / clang.
Peter Cordes

@PeterCordes Estou bem ciente de como implementar de forma round()eficiente em termos de rint()(quando o último está operando no modo arredondado para o mais próximo ou até mais): implementei isso para a biblioteca matemática padrão da CUDA. No entanto, essa pergunta parecia perguntar como implementar round()com o C ++ anterior ao C ++ 11, portanto rint(), não estaria disponível, apenas floor()e ceil().
Njuffa

@PeterCordes Desculpe, eu errei. round()é facilmente sintetizado rint()no modo arredondar para zero , também conhecido como trunc(). Não deveria ter respondido antes do primeiro café.
Njuffa

1
@PeterCordes Concordo que é provável que o OP não precise do comportamento de arredondamento específico de round(); a maioria dos programadores simplesmente não está ciente da distinção entre round()vs rint()com arredondado para o mais próximo, mesmo onde o último geralmente é fornecido diretamente pelo hardware e, portanto, mais eficiente; Eu expliquei isso no CUDA Programming Guide para conscientizar os programadores: "A maneira recomendada de arredondar um operando de ponto flutuante de precisão única para um número inteiro, com o resultado sendo um número de ponto flutuante de precisão única rintf(), não é roundf()".
Njuffa

0

Eu uso a seguinte implementação de round in asm para arquitetura x86 e C ++ específico do MS VS:

__forceinline int Round(const double v)
{
    int r;
    __asm
    {
        FLD     v
        FISTP   r
        FWAIT
    };
    return r;
}

UPD: para retornar valor duplo

__forceinline double dround(const double v)
{
    double r;
    __asm
    {
        FLD     v
        FRNDINT
        FSTP    r
        FWAIT
    };
    return r;
}

Resultado:

dround(0.1): 0.000000000000000
dround(-0.1): -0.000000000000000
dround(0.9): 1.000000000000000
dround(-0.9): -1.000000000000000
dround(1.1): 1.000000000000000
dround(-1.1): -1.000000000000000
dround(0.49999999999999994): 0.000000000000000
dround(-0.49999999999999994): -0.000000000000000
dround(0.5): 0.000000000000000
dround(-0.5): -0.000000000000000

O valor do resultado deve ser o valor do ponto flutuante com precisão dupla.
truthseeker

@ truthseeker: Sim, eu tive que ver o tipo de valor de retorno necessário. OK, consulte "UPD".
Aleksey F.

Esperamos que o compilador esteja alinhado rint()ou nearbyint()com uma roundsdinstrução SSE4.1 ou uma frndintinstrução x87 , que será muito mais rápida que as duas viagens de ida e volta de armazenamento / recarga necessárias para usar esse asm inline nos dados em um registro. O MSVC inline asm é muito difícil para agrupar instruções únicas, como frndintporque não há como obter a entrada em um registro. Usá-lo no final de uma função com o resultado em st(0)pode ser confiável como uma maneira de retornar a saída; aparentemente isso é seguro para eaxnúmeros inteiros, mesmo quando ele enfatiza a função que contém o asm.
Peter Cordes

@PeterCordes Otimizações modernas são bem-vindas. No entanto, não pude usar o SSE4.1, pois ele não existia naquele momento. Meu objetivo era fornecer a implementação mínima da rodada, que poderia funcionar mesmo nas antigas famílias Intel P3 ou P4 a partir dos anos 2000.
Aleksey F.

O P3 nem sequer tem SSE2, portanto, o compilador já estará usando x87 para doublee, portanto, poderá emitir- frndintse para rint(). Se o seu compilador estiver usando SSE2, devolver um doublede um registro XMM para x87 e voltar pode não valer a pena.
Peter Cordes

0

A melhor maneira de arredondar um valor flutuante por "n" casas decimais é o seguinte no tempo O (1): -

Temos que arredondar o valor em 3 lugares, ou seja, n = 3.Então,

float a=47.8732355;
printf("%.3f",a);

-4
// Convert the float to a string
// We might use stringstream, but it looks like it truncates the float to only
//5 decimal points (maybe that's what you want anyway =P)

float MyFloat = 5.11133333311111333;
float NewConvertedFloat = 0.0;
string FirstString = " ";
string SecondString = " ";
stringstream ss (stringstream::in | stringstream::out);
ss << MyFloat;
FirstString = ss.str();

// Take out how ever many decimal places you want
// (this is a string it includes the point)
SecondString = FirstString.substr(0,5);
//whatever precision decimal place you want

// Convert it back to a float
stringstream(SecondString) >> NewConvertedFloat;
cout << NewConvertedFloat;
system("pause");

Pode ser uma maneira suja e ineficiente de conversão, mas diabos, funciona lol. E é bom, porque se aplica ao flutuador real. Não apenas afetando visualmente a saída.


Isso é hilariamente ineficiente e também trunca (sempre descartando os dígitos finais) em vez de arredondar para o mais próximo.
Peter Cordes

-6

Eu fiz isso:

#include <cmath.h>

using namespace std;

double roundh(double number, int place){

    /* place = decimal point. Putting in 0 will make it round to whole
                              number. putting in 1 will round to the
                              tenths digit.
    */

    number *= 10^place;
    int istack = (int)floor(number);
    int out = number-istack;
    if (out < 0.5){
        floor(number);
        number /= 10^place;
        return number;
    }
    if (out > 0.4) {
        ceil(number);
        number /= 10^place;
        return number;
    }
}

3
Você não quis dizer pow (10, lugar) em vez do operador binário ^ em 10 ^ lugar? 10 ^ 2 na minha máquina me dá 8 !! No entanto, no meu Mac 10.7.4 e gcc, o código não funciona, retornando o valor original.
Pete855217
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.