Necesito una función de redondeo de coma flotante simple, por lo tanto:
double round(double);
round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1
Puedo encontrar ceil()
y floor()
en matemáticas.h, pero no round()
.
¿Está presente en la biblioteca estándar de C ++ con otro nombre, o falta?
c++
floating-point
rounding
Roddy
fuente
fuente
std::cout << std::fixed << std::setprecision(0) << -0.9
, por ejemplo.round
está disponible desde C ++ 11 in<cmath>
. Desafortunadamente, si está en Microsoft Visual Studio, todavía falta: connect.microsoft.com/VisualStudio/feedback/details/775474/…round
tiene muchas advertencias. Antes de C ++ 11, el estándar dependía de C90 que no incluíaround
. C ++ 11 se basa en C99, que sí tiene,round
pero también, como señalé, incluyetrunc
cuáles tienen diferentes propiedades y pueden ser más apropiadas según la aplicación. La mayoría de las respuestas también parecen ignorar que un usuario puede desear devolver un tipo integral que tenga aún más problemas.Respuestas:
No hay round () en la biblioteca estándar de C ++ 98. Sin embargo, puedes escribir uno tú mismo. La siguiente es una implementación de redondeo a la mitad :
La razón probable por la que no hay una función redonda en la biblioteca estándar de C ++ 98 es que, de hecho, se puede implementar de diferentes maneras. Lo anterior es una forma común, pero hay otras como redondear a par , que es menos sesgada y, en general, mejor si va a hacer mucho redondeo; Sin embargo, es un poco más complejo de implementar.
fuente
Boost ofrece un conjunto simple de funciones de redondeo.
Para obtener más información, consulte la documentación de Boost .
Editar : Desde C ++ 11, hay
std::round
,std::lround
ystd::llround
.fuente
floor(value + 0.5)
enfoque ingenuo !floor(value + 0.5)
.floor(value + 0.5)
no es nada ingenuo, sino que depende del contexto y la naturaleza. de valores que desea redondear!El estándar C ++ 03 se basa en el estándar C90 para lo que el estándar llama la Biblioteca de C estándar que se cubre en el borrador del estándar C ++ 03 (el borrador de estándar disponible públicamente más cercano a C ++ 03 es N1804 ) sección
1.2
Referencias normativas :Si vamos a la documentación de C para round, lround, llround en cppreference , podemos ver que round y las funciones relacionadas son parte de C99 y, por lo tanto, no estarán disponibles en C ++ 03 o anteriores.
En C ++ 11 esto cambia ya que C ++ 11 se basa en el borrador del estándar C99 para la biblioteca estándar C y, por lo tanto, proporciona std :: round y para los tipos de retorno integral std :: lround, std :: llround :
Otra opción también de C99 sería std :: trunc que:
Si necesita admitir aplicaciones que no sean C ++ 11, su mejor opción sería utilizar boost round, iround, lround, llround o boost trunc .
Rodar tu propia versión de round es difícil
Rodar el tuyo probablemente no valga la pena ya que es más difícil de lo que parece: redondear el flotador al entero más cercano, parte 1 , Redondear el flotador al entero más cercano, parte 2 y Redondear el flotador al entero más cercano, parte 3 explica:
Por ejemplo, un rol común que su implementación usa
std::floor
y agrega0.5
no funciona para todas las entradas:Una entrada por la que fallará es
0.49999999999999994
( ver en vivo ).Otra implementación común implica convertir un tipo de punto flotante a un tipo integral, que puede invocar un comportamiento indefinido en el caso en que la parte integral no se pueda representar en el tipo de destino. Podemos ver esto en el borrador de la sección estándar de C ++
4.9
Conversiones flotantes integrales que dice ( énfasis mío ):Por ejemplo:
Teniendo en cuenta
std::numeric_limits<unsigned int>::max()
es4294967295
entonces la siguiente llamada:causará desbordamiento ( véalo en vivo ).
Podemos ver cuán difícil es realmente esto al ver esta respuesta a la forma concisa de implementar round () en C? que hace referencia a la versión newlibs de ronda flotante de precisión simple. Es una función muy larga para algo que parece simple. Parece poco probable que alguien sin un conocimiento profundo de las implementaciones de coma flotante pueda implementar correctamente esta función:
Por otro lado, si ninguna de las otras soluciones son utilizables, newlib podría ser una opción, ya que es una implementación bien probada.
fuente
round(-0.0)
. La especificación C no parece especificar. Esperaría-0.0
como resultado.std::rint()
menudo es preferiblestd::round()
cuando C ++ 11 está disponible por razones numéricas y de rendimiento. Utiliza el modo de redondeo actual, a diferenciaround()
del modo especial. Puede ser mucho más eficiente en x86, donderint
puede alinearse con una sola instrucción. (gcc y clang hacen eso incluso sin-ffast-math
godbolt.org/g/5UsL2e , mientras que solo clang incluye el casi equivalentenearbyint()
) ARM tiene soporte de instrucción únicaround()
, pero en x86 solo puede alinearse con múltiples instrucciones, y solo con-ffast-math
Vale la pena señalar que si desea un resultado entero del redondeo, no necesita pasarlo por el techo o el piso. Es decir,
fuente
Está disponible desde C ++ 11 en cmath (según http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf )
Salida:
fuente
lround
yllround
para resultados integraleslrint
para usar el modo de redondeo actual en lugar delround
funky tiebreak lejos de cero.Por lo general, se implementa como
floor(value + 0.5)
.Editar: y probablemente no se llama redondear, ya que hay al menos tres algoritmos de redondeo que conozco: redondear a cero, redondear al entero más cercano y redondeo del banquero. Estás pidiendo redondear al entero más cercano.
fuente
Hay 2 problemas que estamos viendo:
Las conversiones de redondeo significan redondeo ± flotante / doble al piso más cercano / techo flotante / doble. Puede que tu problema termine aquí. Pero si se espera que devuelva Int / Long, debe realizar una conversión de tipo y, por lo tanto, el problema de "desbordamiento" podría afectar su solución. Entonces, verifique si hay errores en su función
de: http://www.cs.tut.fi/~jkorpela/round.html
fuente
LONG_MIN-0.5
eLONG_MAX+0.5
introduce complicaciones ya que las matemáticas pueden no ser exactas.LONG_MAX
puede exceder ladouble
precisión para la conversión exacta. Es probable que deseeassert(x < LONG_MAX+0.5);
(<vs <=), ya queLONG_MAX+0.5
puede ser exactamente representable y(x)+0.5
puede tener un resultado exacto delLONG_MAX+1
cual falla ellong
lanzamiento. Otros problemas de esquina también.round(double)
, ya hay una función de biblioteca matemática estándar con ese nombre (en C ++ 11), por lo que es confusa. Úselostd::lrint(x)
si está disponible.Un cierto tipo de redondeo también se implementa en Boost:
Tenga en cuenta que esto solo funciona si realiza una conversión de entero.
fuente
boost:numeric::RoundEven< double >::nearbyint
directamente si no desea un entero. @DanielWolf tenga en cuenta que la función simple se implementa usando +0.5, que tiene problemas según lo establecido por aka.nicePuede redondear a n dígitos de precisión con:
fuente
int
. (En la práctica en x86, los valores de FP fuera de rango harán que seCVTTSD2SI
produzca0x80000000
como el patrón de bits entero, es decirINT_MIN
, que luego se convertirá de nuevodouble
.En estos días no debería ser un problema utilizar un compilador C ++ 11 que incluye una biblioteca matemática C99 / C ++ 11. Pero entonces la pregunta es: ¿qué función de redondeo eliges?
C99 / C ++ 11 a
round()
menudo no es realmente la función de redondeo que desea . Utiliza un modo de redondeo funky que se aleja de 0 como un desempate en casos a mitad de camino (+-xxx.5000
). Si desea específicamente ese modo de redondeo, o está apuntando a una implementación de C ++ donderound()
es más rápido querint()
, entonces úselo (o emule su comportamiento con una de las otras respuestas sobre esta pregunta que lo tomó al pie de la letra y reprodujo cuidadosamente ese específico comportamiento de redondeo.)round()
El redondeo es diferente del redondeo predeterminado IEEE754 al modo más cercano, incluso como un desempate . El más cercano, incluso evita el sesgo estadístico en la magnitud promedio de los números, pero lo hace hacia los números pares.Hay dos funciones de redondeo de la biblioteca matemática que utilizan el modo de redondeo predeterminado actual:
std::nearbyint()
ystd::rint()
ambas se agregaron en C99 / C ++ 11, por lo que están disponibles en cualquier momentostd::round()
. La única diferencia es quenearbyint
nunca aumenta FE_INEXACT.Prefiero
rint()
por razones de rendimiento : gcc y clang lo alinean más fácilmente, pero gcc nunca se alineanearbyint()
(incluso con-ffast-math
)gcc / clang para x86-64 y AArch64
Puse algunas funciones de prueba en el Explorador de compiladores de Matt Godbolt , donde puede ver la fuente + salida asm (para múltiples compiladores). Para obtener más información sobre la lectura de la salida del compilador, consulte estas preguntas y respuestas , y la charla de Matt CppCon2017: “¿Qué ha hecho mi compilador por mí últimamente? Desatornillando la tapa del compilador " ,
En el código FP, generalmente es una gran victoria incorporar pequeñas funciones en línea. Especialmente en no Windows, donde la convención de llamadas estándar no tiene registros de llamadas preservadas, por lo que el compilador no puede mantener ningún valor de FP en los registros XMM a través de a
call
. Entonces, incluso si realmente no conoce asm, aún puede ver fácilmente si es solo una llamada a la función de la biblioteca o si está alineado con una o dos instrucciones matemáticas. Cualquier cosa que se alinee con una o dos instrucciones es mejor que una llamada de función (para esta tarea en particular en x86 o ARM).En x86, todo lo que esté en línea con SSE4.1
roundsd
puede auto-vectorizarse con SSE4.1roundpd
(o AVXvroundpd
). (Las conversiones enteras FP-> también están disponibles en forma SIMD empaquetada, excepto para el entero FP-> 64 bits que requiere AVX512).std::nearbyint()
:-msse4.1
.-msse4.1 -ffast-math
, y solo en gcc 5.4 y anteriores . Más tarde, gcc nunca lo alinea (¿tal vez no se dieron cuenta de que uno de los bits inmediatos puede suprimir la excepción inexacta? Eso es lo que usa el sonido metálico, pero el gcc más antiguo usa el mismo inmediato querint
cuando lo hace)std::rint
:-msse4.1
-msse4.1
. (Sin SSE4.1, en línea con varias instrucciones)-ffast-math -msse4.1
.std::round
:-ffast-math -msse4.1
, que requieren dos constantes vectoriales.std::floor
/std::ceil
/std::trunc
-msse4.1
-msse4.1
-ffast-math -msse4.1
Redondeo a
int
/long
/long long
:Tiene dos opciones aquí: usar
lrint
(comorint
pero devuelvelong
, olong long
parallrint
), o usar una función de redondeo FP-> FP y luego convertir a un tipo entero de la manera normal (con truncamiento). Algunos compiladores optimizan una forma mejor que la otra.Tenga en cuenta que
int i = lrint(x)
conviertefloat
odouble
->long
primero, y luego trunca el entero aint
. Esto hace una diferencia para los enteros fuera de rango: Comportamiento indefinido en C ++, pero bien definido para las instrucciones x86 FP -> int (que el compilador emitirá a menos que vea el UB en tiempo de compilación mientras hace propagación constante, entonces es permitido hacer código que se rompe si alguna vez se ejecuta).En x86, una conversión de entero FP-> que desborda el entero produce
INT_MIN
oLLONG_MIN
(un patrón de bits0x8000000
o el equivalente de 64 bits, con solo el conjunto de bits de signo). Intel llama a esto el valor "entero indefinido". (Consulte lacvttsd2si
entrada manual , la instrucción SSE2 que convierte (con truncamiento) el doble escalar a entero con signo. Está disponible con un destino entero de 32 o 64 bits (solo en modo de 64 bits). También hay uncvtsd2si
(convertir con redondeo actual modo), que es lo que nos gustaría que emitiera el compilador, pero desafortunadamente gcc y clang no lo harán sin él-ffast-math
.También tenga en cuenta que FP to / from
unsigned
int / long es menos eficiente en x86 (sin AVX512). La conversión a 32 bits sin firmar en una máquina de 64 bits es bastante barata; simplemente convierta a 64 bits firmado y truncado. Pero por lo demás es significativamente más lento.x86 clang con / sin
-ffast-math -msse4.1
: en(int/long)rint
línea aroundsd
/cvttsd2si
. (se perdió la optimización paracvtsd2si
).lrint
no está en línea en absoluto.x86 gcc6.xy versiones anteriores sin
-ffast-math
: de ninguna manera en línea-ffast-math
:(int/long)rint
redondea y convierte por separado (con 2 instrucciones totales de SSE4.1 está habilitado, de lo contrario con un montón de código en línea pararint
sinroundsd
).lrint
no está en líneax86 gcc con
-ffast-math
: todas las formas en línea acvtsd2si
(óptimo) , sin necesidad de SSE4.1.AArch64 gcc6.3 sin
-ffast-math
: en(int/long)rint
línea a 2 instrucciones.lrint
no está en línea-ffast-math
:(int/long)rint
compila a una llamada alrint
.lrint
no está en línea Esta puede ser una optimización perdida a menos que las dos instrucciones que recibimos-ffast-math
sean muy lentas.fuente
rint()
dónde es una opción factible, que suele ser el caso. Supongo que el nombreround()
implica para algunos programadores que esto es lo que quieren, aunquerint()
parece misterioso. Tenga en cuenta queround()
no utiliza un modo de redondeo "funky": el redondeo al empate más cercano es un modo de redondeo oficial IEEE-754 (2008). Es curioso quenearbyint()
no se alinee, dado que es en gran medida igualrint()
y debe ser idéntico bajo-ffast-math
condiciones. Eso me parece un error.Cuidado con
floor(x+0.5)
. Esto es lo que puede suceder para números impares en el rango [2 ^ 52,2 ^ 53]:Esta es http://bugs.squeak.org/view.php?id=7134 . Use una solución como la de @konik.
Mi propia versión robusta sería algo así como:
Aquí se da otra razón para evitar el piso (x + 0.5) .
fuente
Si finalmente desea convertir la
double
salida de suround()
función en anint
, las soluciones aceptadas de esta pregunta se verán así:Esto registra alrededor de 8.88 ns en mi máquina cuando se pasa en valores aleatorios uniformes.
Lo que sigue es funcionalmente equivalente, por lo que puedo decir, pero registra 2.48 ns en mi máquina, para una ventaja de rendimiento significativa:
Entre las razones para un mejor rendimiento está la ramificación omitida.
fuente
int
. (En la práctica en x86, los valores de FP fuera de rango harán que seCVTTSD2SI
produzca0x80000000
como el patrón de bits entero, es decirINT_MIN
, que luego se convertirá de nuevodouble
.No hay necesidad de implementar nada, así que no estoy seguro de por qué tantas respuestas involucran definiciones, funciones o métodos.
En C99
Tenemos lo siguiente y el encabezado <tgmath.h> para macros de tipo genérico.
Si no puede compilar esto, probablemente haya dejado de lado la biblioteca matemática. Un comando similar a este funciona en cada compilador de C que tengo (varios).
En C ++ 11
Tenemos las siguientes sobrecargas adicionales en #include <cmath> que se basan en el punto flotante de precisión doble IEEE.
También hay equivalentes en el espacio de nombres estándar .
Si no puede compilar esto, puede estar utilizando la compilación C en lugar de C ++. El siguiente comando básico no produce errores ni advertencias con g ++ 6.3.1, x86_64-w64-mingw32-g ++ 6.3.0, clang-x86_64 ++ 3.8.0 y Visual C ++ 2015 Community.
Con la división ordinal
Al dividir dos números ordinales, donde T es corto, int, largo u otro ordinal, la expresión de redondeo es esta.
Exactitud
No hay duda de que aparecen inexactitudes de aspecto extraño en las operaciones de coma flotante, pero esto es solo cuando aparecen los números y tiene poco que ver con el redondeo.
La fuente no es solo el número de dígitos significativos en la mantisa de la representación IEEE de un número de coma flotante, está relacionado con nuestro pensamiento decimal como humanos.
Diez es el producto de cinco y dos, y 5 y 2 son relativamente primos. Por lo tanto, los estándares de coma flotante IEEE no pueden representarse perfectamente como números decimales para todas las representaciones digitales binarias.
Esto no es un problema con los algoritmos de redondeo. Es la realidad matemática la que debe considerarse durante la selección de tipos y el diseño de cálculos, entrada de datos y visualización de números. Si una aplicación muestra los dígitos que muestran estos problemas de conversión decimal-binario, entonces la aplicación está expresando visualmente una precisión que no existe en la realidad digital y debe cambiarse.
fuente
Función
double round(double)
con el uso de lamodf
función:Para ser compilado limpio, incluye "math.h" y "límites" son necesarios. La función funciona de acuerdo con el siguiente esquema de redondeo:
fuente
rint()
onearbyint()
, pero si realmente no puede usar un compilador que proporciona una función de redondeo adecuada, y necesita más precisión que rendimiento ...Si necesita poder compilar código en entornos que admiten el estándar C ++ 11, pero también necesita poder compilar ese mismo código en entornos que no lo admiten, puede usar una macro de función para elegir entre estándar :: round () y una función personalizada para cada sistema. Simplemente pase
-DCPP11
o/DCPP11
al compilador compatible con C ++ 11 (o use sus macros de versión incorporadas), y cree un encabezado como este:Para un ejemplo rápido, consulte http://ideone.com/zal709 .
Esto se aproxima a std :: round () en entornos que no son compatibles con C ++ 11, incluida la preservación del bit de signo para -0.0. Sin embargo, puede causar un ligero impacto en el rendimiento y es probable que tenga problemas para redondear ciertos valores de punto flotante "problemáticos" conocidos, como 0.49999999999999994 o valores similares.
Alternativamente, si tiene acceso a un compilador compatible con C ++ 11, puede tomar std :: round () de su
<cmath>
encabezado y usarlo para crear su propio encabezado que defina la función si aún no está definida. Sin embargo, tenga en cuenta que esta puede no ser una solución óptima, especialmente si necesita compilar para múltiples plataformas.fuente
Según la respuesta de Kalaxy, la siguiente es una solución con plantilla que redondea cualquier número de coma flotante al tipo entero más cercano en función del redondeo natural. También arroja un error en modo de depuración si el valor está fuera del rango del tipo entero, lo que sirve aproximadamente como una función de biblioteca viable.
fuente
0.5
no funciona en todos los casos. Aunque al menos lidias con el problema de desbordamiento, evitas comportamientos indefinidos.Como se señaló en los comentarios y otras respuestas, la biblioteca estándar ISO C ++ no se agregó
round()
hasta ISO C ++ 11, cuando esta función se activó por referencia a la biblioteca matemática estándar ISO C99.Para operandos positivos en [½, ub ]
round(x) == floor (x + 0.5)
, donde ub es 2 23 parafloat
cuando se asigna a IEEE-754 (2008)binary32
y 2 52 paradouble
cuando se asigna a IEEE-754 (2008)binary64
. Los números 23 y 52 corresponden al número de bits de mantisa almacenados en estos dos formatos de punto flotante. Para operandos positivos en [+0, ½)round(x) == 0
, y para operandos positivos en ( ub , + ∞]round(x) == x
. Como la función es simétrica con respecto al eje x, los argumentos negativosx
se pueden manejar de acuerdo conround(-x) == -round(x)
.Esto lleva al código compacto a continuación. Se compila en un número razonable de instrucciones de máquina en varias plataformas. Observé el código más compacto en las GPU, donde
my_roundf()
requiere alrededor de una docena de instrucciones. Dependiendo de la arquitectura del procesador y la cadena de herramientas, este enfoque basado en coma flotante podría ser más rápido o más lento que la implementación basada en enteros de newlib referenciada en una respuesta diferente .He probado
my_roundf()
exhaustivamente frente a la newlibroundf()
aplicación mediante Intel versión del compilador 13, tanto con/fp:strict
y/fp:fast
. También verifiqué que la versión newlib coincida con laroundf()
de lamathimf
biblioteca del compilador Intel. Las pruebas exhaustivas no son posibles para la precisión dobleround()
, sin embargo, el código es estructuralmente idéntico a la implementación de precisión simple.fuente
int
tiene más de 16 bits de ancho. Por supuesto, todavía supone quefloat
es un binario IEEE754 de 4 bytes32. Un C ++ 11static_assert
o tal vez una macro#ifdef
/#error
podría verificar eso. (Pero, por supuesto, si C ++ 11 está disponible, debe usarlostd::round
, o para el uso actual del modo de redondeostd::rint
que se alinea muy bien con gcc y clang).gcc -ffast-math -msse4.1
líneastd::round()
a unaadd( AND(x, L1), OR(x,L2)
, y luego aroundsd
. es decir, se implementa de manera bastante eficienteround
en términos derint
. Pero no hay razón para hacer esto manualmente en la fuente C ++, porque si tienestd::rint()
ostd::nearbyint()
también tienestd::round()
. Vea mi respuesta para un enlace de godbolt y un resumen de lo que está en línea o no con diferentes versiones de gcc / clang.round()
eficiente en términos derint()
(cuando este último está operando en modo redondo a más cercano o par): implementé eso para la biblioteca matemática estándar de CUDA. Sin embargo, esta pregunta parecía preguntar cómo implementarround()
con C ++ antes de C ++ 11, porrint()
lo que tampoco estaría disponible, solofloor()
yceil()
.round()
se sintetiza fácilmente a partirrint()
de ronda a cero modo, akatrunc()
. No debería haber respondido antes del primer café.round()
; la mayoría de los programadores simplemente no son conscientes de la distinción entreround()
vsrint()
con redondeo a par más cercano, donde este último generalmente es proporcionado directamente por el hardware y, por lo tanto, más eficiente; Lo expliqué en la Guía de programación de CUDA para que los programadores conozcan: "La forma recomendada de redondear un operando de punto flotante de precisión simple a un entero, con el resultado de que es un número de punto flotante de precisión simple esrintf()
, noroundf()
".Utilizo la siguiente implementación de round in asm para la arquitectura x86 y C ++ específico de MS VS:
UPD: para devolver el valor doble
Salida:
fuente
rint()
onearbyint()
con unaroundsd
instrucción SSE4.1 o unafrndint
instrucción x87 , que será mucho más rápido que los dos viajes de ida y vuelta de almacenamiento / recarga necesarios para usar este asm en línea en los datos de un registro. El asm en línea de MSVC apesta bastante para envolver instrucciones individuales, comofrndint
porque no hay forma de obtener la entrada en un registro. Usarlo al final de una función con el resultadost(0)
puede ser confiable como una forma de devolver la salida; aparentemente eso es seguro paraeax
enteros, incluso cuando alinea la función que contiene el asm.double
, y por lo tanto debe ser capaz de emitirfrndint
en sí pararint()
. Si su compilador está utilizando SSE2,double
puede que no valga la pena rebotar un registro XMM a x87 y viceversa.La mejor manera de redondear un valor flotante por "n" decimales, es como sigue con el tiempo en O (1): -
Tenemos que redondear el valor en 3 lugares, es decir, n = 3. Entonces,
fuente
Puede ser una manera sucia ineficiente de conversión, pero diablos, funciona jajaja. Y es bueno, porque se aplica al flotador real. No solo afecta la salida visualmente.
fuente
Hice esto:
fuente