rodada () para flutuar em C ++
-
20-08-2019 - |
Pergunta
Eu preciso de uma simples função de arredondamento de ponto flutuante, assim:
double round(double);
round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1
posso encontrar ceil()
e floor()
na math.h - mas não round()
É apresentar na biblioteca padrão C ++ sob outro nome, ou é falta ??
Solução
Não há nenhum round () no C ++ 98 biblioteca padrão. Você pode escrever um você mesmo embora. O seguinte é uma implementação do round-meia-se :
double round(double d)
{
return floor(d + 0.5);
}
A provável razão não há nenhuma função redonda no C ++ 98 biblioteca padrão é que ele pode de fato ser implementado de maneiras diferentes. A descrição acima é uma maneira comum, mas existem outros, como round-a-even , que é menos tendencioso e geralmente melhor se você estiver indo para fazer um monte de arredondamento; é um pouco mais complexo de implementar embora.
Outras dicas
impulso oferece um conjunto simples de arredondamento funções.
#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 obter mais informações, consulte o documentação impulso .
Editar : Desde C ++ 11, há std::round href="http://en.cppreference.com/w/cpp/numeric/math/round">, std::lround
, e std::llround
.
O padrão C ++ 03 se baseia no padrão C90 para o que as chamadas padrão do Padrão C Library que é coberta no projecto C ++ 03 standard ( mais próxima disponível publicamente projecto de norma para C ++ 03 é N1804 ) seção 1.2
referências normativas :
A biblioteca descrito na cláusula 7 da norma ISO / IEC 9899: 1990 cláusula e 7 de ISO / IEC 9899 / Amd.1: 1995 é daqui em diante chamado o padrão C Biblioteca. 1)
Se formos para o documentação C para a segunda rodada, lround, llround em cppreference podemos ver que volta ??em> e funções relacionadas são parte da C99 e, portanto, não estará disponível em C ++ 03 ou antes.
Em C ++ 11 este mudanças desde C ++ 11 depende do projecto de norma C99 para C biblioteca padrão e, portanto, fornece std :: rodada e para tipos de retorno integrais std ::, 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 de C99 seria std :: trunc que :
Calcula mais próximo inteiro não maior em magnitude do 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 a utilização de boost rodada, iround, lround, llround ou boost trunc .
Rolando sua própria versão da rodada é difícil
Rolling seu próprio é provavelmente não vale a pena o esforço como mais difícil do que parece: arredondamento float para número inteiro mais próximo, parte 1 , Completando float para número inteiro mais próximo, parte 2 e Completando float para número inteiro mais próximo, parte 3 explicar:
Por exemplo, um rolo comum sua implementação usando std::floor
e adicionando 0.5
não funciona para todas as entradas:
double myround(double d)
{
return std::floor(d + 0.5);
}
Uma entrada esta falhará para é 0.49999999999999994
, ( vê-lo ao vivo ).
Outra aplicação comum envolve lançando um tipo de ponto flutuante para um tipo integral, que pode invocar um comportamento indefinido no caso em que a parte integral não pode ser representado no tipo de destino. Podemos ver isso a partir do projecto de C ++ 4.9
seção padrão Floating-integrais conversões que diz ( grifo meu ):
Um prvalue de um tipo de ponto flutuante pode ser convertido para um prvalue de um tipo inteiro. Os truncados de conversão; isto é, a parte fracionária é descartado. O comportamento é indefinido se o valor truncado não pode ser representado no tipo de destino. [...]
Por exemplo:
float myround(float f)
{
return static_cast<float>( static_cast<unsigned int>( f ) ) ;
}
std::numeric_limits<unsigned int>::max()
Dado é 4294967295
em seguida, a seguinte chamada:
myround( 4294967296.5f )
irá causar excesso, ( vê-lo viver ) .
Podemos ver quão difícil isso realmente é, olhando para esta resposta para Concise maneira de implementar round () em C? que fazem referência a newlibs versão de precisão simples bóia redonda. É um tempo muito longo função de algo que parece simples. Parece improvável que qualquer pessoa sem conhecimento íntimo de implementações de ponto flutuante poderia implementar corretamente essa 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 são utilizáveis ?? newlib poderia ser uma opção, uma vez que é uma implementação bem testado.
Pode valer a pena notar que, se você queria um resultado inteiro do arredondamento não é necessário passá-lo através de qualquer ceil ou no chão. I.,
int round_int( double r ) {
return (r > 0.0) ? (r + 0.5) : (r - 0.5);
}
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;
}
Output:
round(0.5): 1
round(-0.5): -1
round(1.4): 1
round(-1.4): -1
round(1.6): 2
round(-1.6): -2
Geralmente é implementado como floor(value + 0.5)
.
Edit: e é, provavelmente, não é chamado rodada uma vez que existem pelo menos três algoritmos de arredondamento que eu conheço: rodada para zero, volta para número inteiro mais próximo, e arredondamento do banqueiro. Você está pedindo rodada para número inteiro mais próximo.
Existem 2 problemas que estamos olhando:
- arredondamento conversões
- conversão de tipo.
conversões arredondamento dizer arredondamento ± float / double ao piso mais próximo / float ceil / double. Podem ser seus fins problema aqui. Mas se você é esperado para retornar Int / Long, você precisa executar conversão de tipo e, portanto, problema "Estouro" pode acertar a sua solução. Então, faça um cheque de erro em 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))
Um certo tipo de arredondamento também é implementado em 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;
}
Note que isto só funciona se você fizer uma conversão para inteiro.
Você poderia rodada de n precisão dígitos 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;
}
Se você quer finalmente para converter a saída double
da sua função round()
a um int
, então as soluções aceitas de esta questão será algo parecido com:
int roundint(double r) {
return (int)((r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5));
}
Este relógios em torno de 8.88 ns na minha máquina quando passado em valores uniformemente aleatórios.
A seguir é funcionalmente equivalente, tanto quanto eu posso dizer, mas relógios em 2.48 ns na minha máquina, para uma vantagem de desempenho significativa:
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 é o ignorada ramificação.
Estes dias não deve ser um problema para usar um C ++ 11 compilador que inclui uma biblioteca / C ++ 11 de matemática C99. Mas, então, a pergunta é: qual o arredondamento função você escolhe
C99 / C ++ 11 round()
muitas vezes não é realmente a função de arredondamento você quiser . Ele usa um modo de funk de arredondamento que rodadas de distância de 0 como um tie-break em casos half-way (+-xxx.5000
). Se você quer especificamente que o modo de arredondamento, ou você está alvejando uma implementação C ++ onde round()
é mais rápido que rint()
, em seguida, usá-lo (ou emular seu comportamento com uma das outras respostas sobre esta questão que tomaram pelo valor de face e cuidadosamente reproduzidas que específica arredondamento comportamento.)
arredondamento round()
é diferente do IEEE754 padrão rodada para o modo mais próximo com mesmo como um tie-break . Mais próximo, até mesmo evita viés estatístico na magnitude média de números, mas faz viés para números pares.
Existem duas funções biblioteca de matemática de arredondamento que usam o padrão atual modo de arredondamento: std::nearbyint()
e std::rint()
, ambos adicionados em C99 / C ++ 11, para que eles estejam disponíveis a qualquer std::round()
tempo é. A única diferença é que nearbyint
não levanta FE_INEXACT.
Prefere rint()
por motivos de desempenho : gcc e clang tanto linha-lo mais facilmente, mas gcc não inlines nearbyint()
(mesmo com -ffast-math
)
gcc / clang para x86-64 e AArch64
Eu coloquei alguns funções de teste em Matt Godbolt Compiler Explorer , onde você pode ver fonte + saída asm (para vários compiladores). Para mais sobre a leitura de saída do compilador, consulte este Q & A e de Matt CppCon2017 Discussão: “O que tem a minha Compiler feito por mim ultimamente? Unbolting tampa do Compiler”,
No código FP, geralmente é uma grande vitória para inline pequenas funções. Especialmente sobre a não-Windows, onde a convenção de chamada padrão não tem registos preservados-chamada, de modo que o compilador não pode manter todos os valores PF em registros XMM através de uma call
. Assim, mesmo se você realmente não sei asm, você ainda pode ver facilmente se é apenas um rabo-chamada para a função de biblioteca ou se inlined a uma ou duas instruções matemáticas. Qualquer coisa que inlines para uma ou duas instruções é melhor do que uma chamada de função (para esta tarefa particular em x86 ou ARM).
Em x86, qualquer coisa que inlines para SSE4.1 roundsd
pode auto-vectorize com SSE4.1 roundpd
(ou vroundpd
AVX). (FP-> conversões inteiros também estão disponíveis na forma SIMD embalado, excepto para FP-> inteiro de 64-bit que requer AVX512.)
-
std::nearbyint()
:- x86 clang:. Inlines para um único insn com
-msse4.1
- x86 gcc: inlines para um único insn apenas com
-msse4.1 -ffast-math
, e só no gcc 5.4 e anterior . Mais tarde gcc não inlines (talvez eles não perceberam que um dos bits imediatos podem suprimir a exceção inexata? Isso é o que os usos clang, mas mais velhos gcc usa o mesmo imediata, pararint
quando o faz em linha-lo) - AArch64 gcc6.3:. Inlines para um único insn por padrão
- x86 clang:. Inlines para um único insn com
-
std::rint
:- x86 clang: inlines para um único insn com
-msse4.1
- gcc7 x86: inlines para um único insn com
-msse4.1
. (Sem SSE4.1, inlines para várias instruções) - x86 gcc6.x e anterior:. Inlines para um único insn com
-ffast-math -msse4.1
- AArch64 gcc: inlines para um único insn por padrão
- x86 clang: inlines para um único insn com
-
std::round
:- x86 clang: não em linha
- x86 gcc:. Inlines para várias instruções com
-ffast-math -msse4.1
, exigindo duas constantes vetor - AArch64 gcc: (. Suporte HW para este modo de arredondamento, bem como padrão IEEE e mais outros) inlines para uma única instrução
-
std::floor
/std::ceil
/std::trunc
- x86 clang: inlines para um único insn com
-msse4.1
- x86 gcc7.x: inlines para um único insn com
-msse4.1
- x86 gcc6.x e anterior: inlines para um único insn com
-ffast-math -msse4.1
- AArch64 gcc: inlines por padrão para uma única instrução
- x86 clang: inlines para um único insn com
arredondamento para int
/ long
/ long long
:
Você tem duas opções aqui: uso lrint
(como rint
mas retorna long
, ou long long
para llrint
), ou usar um FP-> FP função de arredondamento e depois converter para um inteiro digite o caminho normal (com truncagem). Alguns compiladores otimizar uma maneira melhor do que o outro.
long l = lrint(x);
int i = (int)rint(x);
Note que converte int i = lrint(x)
float
ou double
-> long
primeiro, e depois trunca o inteiro para int
. Isso faz uma diferença para inteiros out-of-range: comportamento indefinido em C ++, mas bem definido para o x86 FP -> instruções int (que o compilador irá emitir menos que vê a UB em tempo de compilação ao fazer propagação constante, então é permissão para fazer código que quebra se alguma vez executado).
Em x86, um FP-> intEger conversão que transborda o número inteiro produz INT_MIN
ou LLONG_MIN
(um bit-padrão de 0x8000000
ou o 64-bit equivalente, com apenas o conjunto sinal bits). Intel chama este o valor "inteiro indefinido". (Veja o cvttsd2si
entrada manual, a instrução SSE2 que converte (com truncagem) escalar dupla para assinado inteiro. Ele está disponível com 32-bit ou destino inteiro de 64 bits (no modo de 64 bits apenas). Há também uma cvtsd2si
(converso com o modo de arredondamento atual), que é o que nós gostaríamos que o compilador para emitir, mas infelizmente gcc e clang não vai fazer isso sem -ffast-math
.
Também cuidado com que FP de / para int unsigned
/ longo é menos eficiente em x86 (sem AVX512). A conversão para 32 bits sem sinal em uma máquina de 64 bits é muito barato; apenas converter a 64-bit assinado e truncado. Mas caso contrário, é significativamente mais lento.
-
x86 clang com / sem
-ffast-math -msse4.1
:(int/long)rint
inlines pararoundsd
/cvttsd2si
. (Perdeu otimização paracvtsd2si
).lrint
não embutido em tudo. -
x86 gcc6.x e anteriores sem
-ffast-math
: nem maneira inlines - gcc7 x 86 sem
-ffast-math
: rodadas(int/long)rint
e convertidos separadamente (com 2 instruções totais de SSE4.1 é activado, de outra forma com um grupo de código embutido pararint
semroundsd
).lrint
não em linha. -
x86 gcc com
-ffast-math
:. todas as formas inline paracvtsd2si
(ideal) , não há necessidade de SSE4.1 -
AArch64 gcc6.3 sem
-ffast-math
: inlines(int/long)rint
2 instruções.lrint
não em linha - gcc6.3 AArch64 com
-ffast-math
: compila(int/long)rint
para uma chamada paralrint
.lrint
não inline. Esta pode ser uma otimização perdeu a menos que as duas instruções que recebemos sem-ffast-math
são muito lento.
Cuidado com floor(x+0.5)
. Aqui está o que pode acontecer por 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
Esta é http://bugs.squeak.org/view.php?id = 7134 . Use uma solução como a de @konik.
A 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;
}
Não há necessidade de implementar qualquer coisa, então eu não tenho certeza por que tantas respostas envolvem define, funções ou métodos.
Em C99
Nós temos o seguinte e e cabeçalho
#include <math.h>
double round (double x);
float roundf (float x);
long double roundl (long double x);
Se você não pode compilar este, você provavelmente já deixou de fora a biblioteca de matemática. Um comando semelhante a este obras em cada compilador C Eu tenho (vários).
gcc -lm -std=c99 ...
Em C ++ 11
Temos as seguintes e adicionais sobrecargas em #include
#include <math.h>
double round (double x);
float round (float x);
long double round (long double x);
double round (T x);
Há equivalentes no std namespace também.
Se você não pode compilar isso, você pode estar usando compilação C em vez de C ++. O seguinte comando básica produz nem erros nem avisos com g ++ 6.3.1, x86_64-w64-mingw32-g ++ 6.3.0, clang-x86_64 ++ 3.8.0, e Visual C ++ 2015 comunitário.
g++ -std=c++11 -Wall
Com ordinal Divisão
Ao dividir dois números ordinais, onde T é curto, int, long ou outro ordinais, a expressão arredondamento é isso.
T roundedQuotient = (2 * integerNumerator + 1)
/ (2 * integerDenominator);
Precisão
Não há dúvida de que estranhos imprecisões procurando aparecer em operações de ponto flutuante, mas isso é apenas quando os números aparecem, e tem pouco a ver com arredondamento.
A fonte não é apenas o número de dígitos significativos no mantissa da representação IEEE de um número de ponto flutuante, que está relacionado com o nosso pensamento decimal como seres humanos.
Ten é o produto de cinco e dois, e 5 e 2 são relativamente primos. Portanto, os padrões IEEE ponto flutuante não pode ser representado perfeitamente como números decimais para todas as representações digitais binários.
Este não é um problema com os algoritmos de arredondamento. É a realidade matemática que deve ser considerado durante a seleção de tipos e o projeto de cálculos, entrada de dados e exibição de números. Se um aplicativo exibe os dígitos que mostram esses problemas de conversão decimais-binário, em seguida, o aplicativo é visualmente expressar precisão que não existe na realidade digital e deve ser alterado.
double round(double)
função com o uso da função modf
:
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 compilação limpa, inclui "math.h" e "limites" são necessárias. A função funciona de acordo com um esquema de arredondamento seguinte:
- rodada de 5.0 é de 5,0
- rodada de 3.8 é 4.0
- rodada de 2.3 é de 2,0
- rodada de 1,5 é de 2,0
- rodada de 0,501 é 1,0
- rodada de 0,5 é de 1,0
- rodada de 0,499 é 0,0
- rodada de 0,01 é de 0,0
- rodada de 0.0 é de 0,0
- rodada de -0.01 é -0.0
- rodada de -0,499 é -0.0
- rodada de -0.5 é -0.0
- rodada de -0,501 é -1.0
- rodada de -1.5 é -1.0
- rodada de -2.3 é -2.0
- rodada de -3.8 é -4.0
- rodada de -5.0 é -5.0
Se você precisa ser capaz de compilar o código em ambientes que suportam o C ++ 11 standard, mas também precisa ser capaz de compilar esse mesmo código em ambientes que não apoiá-lo, você pode usar uma macro função para escolher entre std :: round () e uma função personalizada para cada sistema. Basta passar -DCPP11
ou /DCPP11
para o compilador C ++ 11-compliant (ou use a sua built-in macros versão), e fazer 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 .
Isto aproxima std :: round () em ambientes que não são C ++ 11-compliant, incluindo a preservação do bit de sinal para -0,0. Isso pode causar uma pequena queda de performance, no entanto, e provavelmente vai ter problemas com arredondamento certo "problema" conhecido valores de ponto flutuante, como 0.49999999999999994 ou valores semelhantes.
Como alternativa, se você tiver acesso a um compilador 11-compliant C ++, você pode simplesmente pegar std :: round () a partir de seu cabeçalho <cmath>
, e usá-lo para fazer o seu próprio cabeçalho que define a função, se ainda não estiver definiram. Note que isso pode não ser a solução ideal, no entanto, especialmente se você precisa compilar para múltiplas plataformas.
Com base na resposta de Kalaxy, o seguinte é uma solução que modelada rodadas qualquer número de ponto flutuante para o número inteiro mais próximo tipo baseado em arredondamento natural. Ele também gera um erro no modo de depuração se o valor está fora do intervalo do tipo inteiro, servindo aproximadamente como uma função da 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);
}
Como apontado em comentários e outras respostas, biblioteca padrão ISO C ++ não adicionar round()
até ISO C ++ 11, quando esta função foi puxado por referência à biblioteca de matemática padrão ISO C99.
Para operandos positivos em [½, ub ] round(x) == floor (x + 0.5)
, onde ub é 2 23 para float
quando mapeado para IEEE-754 (2008 ) binary32
e 2 52 para double
quando é mapeado para-754 IEEE (2008) binary64
. Os números 23 e 52 correspondem ao número de armazenado bits de mantissa nestes dois formatos de ponto flutuante. Para operandos positivos em [0, ½) round(x) == 0
, e para operandos positivas na ( ub , + 8] round(x) == x
. À medida que a função é simétrica em torno do eixo-x, argumentos negativos x
podem ser tratadas de acordo para round(-x) == -round(x)
.
Isso leva ao código compacto abaixo. Ele compila em um número razoável de instruções de máquina em várias plataformas. Observei o código mais compacto em GPUs, onde my_roundf()
requer cerca de uma dúzia de instruções. Dependendo da arquitectura do processador e conjunto de ferramentas, esta abordagem baseia-ponto flutuante pode ser mais rápido ou mais lento do que a implementação baseada no número inteiro de newlib referenciados em um diferente resposta .
Eu testei my_roundf()
exaustivamente contra a implementação roundf()
newlib usando Intel compilador versão 13, com ambos /fp:strict
e /fp:fast
. Eu também verificado que a versão newlib corresponde ao roundf()
na biblioteca mathimf
do compilador Intel. testes exaustivos não é possível para round()
de precisão dupla, no entanto, o código é estruturalmente idêntica à implementação precisão simples.
#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;
}
Eu uso o seguinte implementação da rodada em asm para a arquitetura x86 e MS VS específica C ++:
__forceinline int Round(const double v)
{
int r;
__asm
{
FLD v
FISTP r
FWAIT
};
return r;
}
UPD: para retornar o valor double
__forceinline double dround(const double v)
{
double r;
__asm
{
FLD v
FRNDINT
FSTP r
FWAIT
};
return r;
}
Output:
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
A melhor maneira de arredondamento um valor flutuante por "n" casas decimais, é o seguinte com em O (1) tempo: -
Temos que completam o valor por 3 lugares ou seja n = 3.So,
float a=47.8732355;
printf("%.3f",a);
// 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 ineficiente de conversão, mas que diabo, ele funciona lol. E é bom, porque se aplica à bóia real. Não apenas afetar a saída visual.
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;
}
}