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?
c++
floating-point
rounding
Roddy
fonte
fonte
std::cout << std::fixed << std::setprecision(0) << -0.9
, por exemplo.round
está 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/...round
rol tem muitas ressalvas. Antes do C ++ 11, o padrão contava com o C90, que não incluíaround
. O C ++ 11 conta com o C99, que possui,round
mas também como observei,trunc
que 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.Respostas:
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 :
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.
fonte
O Boost oferece um conjunto simples de funções de arredondamento.
Para mais informações, consulte a documentação do Boost .
Editar : Como C ++ 11, existem
std::round
,std::lround
estd::llround
.fonte
floor(value + 0.5)
abordagem ingênua !floor(value + 0.5)
.floor(value + 0.5)
não é nada ingênuo, mas depende do contexto e da natureza dos valores que você deseja arredondar!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 :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 :
Outra opção também do C99 seria std :: trunc, que:
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::floor
e adicionando0.5
não funciona para todas as entradas: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 ):Por exemplo:
Dado
std::numeric_limits<unsigned int>::max()
é4294967295
então a seguinte chamada: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:
Por outro lado, se nenhuma das outras soluções for utilizável, newlib pode ser uma opção, pois é uma implementação bem testada.
fonte
round(-0.0)
. C spec não parece especificar. Eu esperaria-0.0
como resultado.std::rint()
geralmente é preferívelstd::round()
quando o C ++ 11 está disponível por razões numéricas e de desempenho. Ele usa o modo de arredondamento atual, diferenteround()
do modo especial. Pode ser muito mais eficiente no x86, onderint
pode 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 equivalentenearbyint()
) O ARM tem suporte para instruções únicasround()
, mas no x86 ele pode apenas alinhar com várias instruções e somente com-ffast-math
Pode ser interessante notar que, se você deseja um resultado inteiro do arredondamento, não precisa passar pelo teto ou pelo piso. Ou seja,
fonte
Está disponível desde C ++ 11 em cmath (de acordo com http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf )
Resultado:
fonte
lround
ellround
por resultados integraislrint
para usar o modo de arredondamento atual, em vez doround
tie-break longe de zero.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.
fonte
Existem 2 problemas que estamos analisando:
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
from: http://www.cs.tut.fi/~jkorpela/round.html
fonte
LONG_MIN-0.5
eLONG_MAX+0.5
introduzir complicações, pois a matemática pode não ser exata.LONG_MAX
pode exceder adouble
precisão para a conversão exata. Provavelmente, desejaassert(x < LONG_MAX+0.5);
(<vs <=), poisLONG_MAX+0.5
pode ser exatamente representável e(x)+0.5
pode ter um resultado exato doLONG_MAX+1
qual falha nalong
conversão. Outros problemas de canto também.round(double)
, já existe uma função de biblioteca matemática padrão com esse nome (no C ++ 11), por isso é confusa. Usestd::lrint(x)
se estiver disponível.Um certo tipo de arredondamento também é implementado no Boost:
Observe que isso funciona apenas se você fizer uma conversão para inteiro.
fonte
boost:numeric::RoundEven< double >::nearbyint
diretamente 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.niceVocê pode arredondar para n dígitos de precisão com:
fonte
int
. (Na prática, no x86, fora-de-gama valores FP faráCVTTSD2SI
produto0x80000000
como o padrão de bits de número inteiro, ou sejaINT_MIN
, que irá então ser convertido de volta adouble
.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 ++ onderound()
é mais rápida querint()
, 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()
estd::rint()
, ambas adicionadas no C99 / C ++ 11, estão disponíveis a qualquer momentostd::round()
. A única diferença é quenearbyint
nunca gera FE_INEXACT.Prefira
rint()
por motivos de desempenho : o gcc e o clang incorporam-no com mais facilidade, mas o gcc nunca alinhanearbyint()
(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
roundsd
pode se auto-vetorizar com o SSE4.1roundpd
(ou AVXvroundpd
). (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()
:-msse4.1
.-msse4.1 -ffast-math
e 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 querint
quando o inline)std::rint
:-msse4.1
-msse4.1
. (Sem SSE4.1, inline a várias instruções)-ffast-math -msse4.1
.std::round
:-ffast-math -msse4.1
, exigindo duas constantes de vetor.std::floor
/std::ceil
/std::trunc
-msse4.1
-msse4.1
-ffast-math -msse4.1
Arredondamento para
int
/long
/long long
:Você tem duas opções aqui: use
lrint
(comorint
mas retornalong
, oulong long
parallrint
), 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.Observe que
int i = lrint(x)
convertefloat
oudouble
->long
primeiro e, em seguida, trunca o número inteiro paraint
. 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_MIN
ouLLONG_MIN
(um padrão de bits0x8000000
ou o equivalente a 64 bits, apenas com o conjunto de bits de sinal). A Intel chama isso de valor "número inteiro indefinido". (Veja acvttsd2si
entrada 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 umcvtsd2si
(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
.unsigned
Lembre-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)rint
alinha pararoundsd
/cvttsd2si
. (otimização perdida paracvtsd2si
).lrint
não está alinhado.x86 gcc6.xe versões anteriores sem
-ffast-math
: de nenhuma maneira-ffast-math
:(int/long)rint
arredonda e converte separadamente (com 2 instruções totais do SSE4.1 está ativada, caso contrário, com um monte de código indicado pararint
semroundsd
).lrint
não inline.x86 gcc com
-ffast-math
: todas as formas alinhadas acvtsd2si
(ideal) , sem necessidade de SSE4.1.AArch64 gcc6.3 sem
-ffast-math
:(int/long)rint
linhas para 2 instruções.lrint
não alinha-ffast-math
:(int/long)rint
compila para uma chamada paralrint
.lrint
não inline. Pode ser uma otimização perdida, a menos que as duas instruções que recebemos-ffast-math
sejam muito lentas.fonte
rint()
onde é uma opção viável, o que geralmente é o caso. Eu acho que o nomeround()
implica para alguns programadores que é isso que eles querem, enquantorint()
parece misterioso. Observe queround()
não usa um modo de arredondamento "descolado": o arredondamento para o mais próximo é o modo de arredondamento oficial IEEE-754 (2008). É curioso quenearbyint()
não fique alinhado, já que é basicamente o mesmorint()
e deve ser idêntico sob-ffast-math
condições. Isso me parece inseto.Cuidado com
floor(x+0.5)
. Aqui está o que pode acontecer com números ímpares no intervalo [2 ^ 52,2 ^ 53]: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:
Outro motivo para evitar o piso (x + 0,5) é apresentado aqui .
fonte
Se você deseja converter a
double
saída de suaround()
função em anint
, as soluções aceitas dessa pergunta serão parecidas com: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:
Entre as razões para o melhor desempenho está a ramificação ignorada.
fonte
int
. (Na prática, no x86, fora-de-gama valores FP faráCVTTSD2SI
produto0x80000000
como o padrão de bits de número inteiro, ou sejaINT_MIN
, que irá então ser convertido de volta adouble
.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.
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).
Em C ++ 11
Temos as seguintes sobrecargas e adicionais em #include <cmath> que dependem do ponto flutuante de precisão dupla IEEE.
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.
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.
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.
fonte
Função
double round(double)
com o uso damodf
função:Para ser compilado, são necessários "math.h" e "limites". A função funciona de acordo com o seguinte esquema de arredondamento:
fonte
rint()
ounearbyint()
, 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 ...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
-DCPP11
ou/DCPP11
para o compilador compatível com C ++ 11 (ou usar suas macros de versão internas) e criar um cabeçalho como este: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.fonte
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.
fonte
0.5
não funciona em todos os casos. Embora pelo menos você lide com a questão do estouro, evite comportamentos indefinidos.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 parafloat
quando mapeado para IEEE-754 (2008)binary32
e 2 52 paradouble
quando é 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) == 0
e para operandos positivos em ( ub , + ∞]round(x) == x
. Como a função é simétrica em relação ao eixo x, argumentos negativosx
podem 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 aroundf()
implementação newlib usando o compilador Intel versão 13, com ambos/fp:strict
e/fp:fast
. Eu também verifiquei se a versão newlib corresponde àroundf()
namathimf
biblioteca do compilador Intel. Testes exaustivos não são possíveis para precisão duplaround()
, no entanto, o código é estruturalmente idêntico à implementação de precisão única.fonte
int
tem mais de 16 bits de largura. É claro que ainda assume quefloat
é o IEEE754 binário de 4 bytes32. Um C ++ 11static_assert
ou talvez uma macro#ifdef
/#error
poderia verificar isso. (Mas é claro que se o C ++ 11 estiver disponível, você deve usarstd::round
ou, para o modo de arredondamento atual, usarstd::rint
alinhamentos agradáveis com gcc e clang).gcc -ffast-math -msse4.1
alinhastd::round()
para umadd( AND(x, L1), OR(x,L2)
e, em seguida, umroundsd
. isto é, implementaround
com bastante eficiência em termos derint
. Mas não há razão para fazer isso manualmente na fonte C ++, porque se você possuistd::rint()
oustd::nearbyint()
também possuistd::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.round()
eficiente em termos derint()
(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 implementarround()
com o C ++ anterior ao C ++ 11, portantorint()
, não estaria disponível, apenasfloor()
eceil()
.round()
é facilmente sintetizadorint()
no modo arredondar para zero , também conhecido comotrunc()
. Não deveria ter respondido antes do primeiro café.round()
; a maioria dos programadores simplesmente não está ciente da distinção entreround()
vsrint()
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 únicarintf()
, não éroundf()
".Eu uso a seguinte implementação de round in asm para arquitetura x86 e C ++ específico do MS VS:
UPD: para retornar valor duplo
Resultado:
fonte
rint()
ounearbyint()
com umaroundsd
instrução SSE4.1 ou umafrndint
instruçã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, comofrndint
porque não há como obter a entrada em um registro. Usá-lo no final de uma função com o resultado emst(0)
pode ser confiável como uma maneira de retornar a saída; aparentemente isso é seguro paraeax
números inteiros, mesmo quando ele enfatiza a função que contém o asm.double
e, portanto, poderá emitir-frndint
se pararint()
. Se o seu compilador estiver usando SSE2, devolver umdouble
de um registro XMM para x87 e voltar pode não valer a pena.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,
fonte
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.
fonte
Eu fiz isso:
fonte