round () para flotante en C ++

232

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?

Roddy
fuente
1
Si solo desea generar el número como un número redondeado, parece que puede hacerlo std::cout << std::fixed << std::setprecision(0) << -0.9, por ejemplo.
Frank
44
Proteger esto ... Los nuevos usuarios con nuevos y brillantes esquemas de redondeo deben leer primero las respuestas existentes.
Shog9
12
roundestá disponible desde C ++ 11 in <cmath>. Desafortunadamente, si está en Microsoft Visual Studio, todavía falta: connect.microsoft.com/VisualStudio/feedback/details/775474/…
Alessandro Jacopson
3
Como señalo en mi respuesta, rodar la tuya roundtiene muchas advertencias. Antes de C ++ 11, el estándar dependía de C90 que no incluía round. C ++ 11 se basa en C99, que sí tiene, roundpero también, como señalé, incluye trunccuá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.
Shafik Yaghmour
2
@uvts_cvs esto no parece ser un problema con la última versión de Visual Studio, míralo en vivo .
Shafik Yaghmour

Respuestas:

144

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 :

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

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.

Andreas Magnusson
fuente
53
Esto no maneja los números negativos correctamente. La respuesta de litb es correcta.
Usuario registrado
39
@InnerJoin: Sí, maneja los números negativos de manera diferente a la respuesta de litb, pero eso no lo hace "incorrecto".
Roddy
39
Agregar 0.5 antes de truncar no puede redondear al entero más cercano para varias entradas, incluyendo 0.49999999999999994. Ver blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Pascal Cuoq
10
@ Sergi0: No hay "correcto" e "incorrecto" porque hay más de una definición de redondeo que decide lo que sucede en el punto medio. Verifique sus hechos antes de emitir un juicio.
Jon
16
@MuhammadAnnaqeeb: Tienes razón, las cosas han mejorado enormemente desde el lanzamiento de C ++ 11. Esta pregunta fue formulada y respondida en otro momento cuando la vida era dura y las alegrías eran pocas. Permanece aquí como una oda a los héroes que vivieron y lucharon en aquel entonces y para esas pobres almas que aún no pueden usar las herramientas modernas.
Andreas Magnusson
96

Boost ofrece un conjunto simple de funciones de redondeo.

#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 obtener más información, consulte la documentación de Boost .

Editar : Desde C ++ 11, hay std::round, std::lroundystd::llround .

Daniel Wolf
fuente
2
Ya estaba usando boost en mi proyecto, +1 para esto, ¡mucho mejor que usar el floor(value + 0.5)enfoque ingenuo !
Gustavo Maciel
@GustavoMaciel Sé que llego un poco tarde al juego, pero la implementación de impulso es floor(value + 0.5).
n. 'pronombres' m.
En realidad no: github.com/boostorg/math/blob/develop/include/boost/math/… 4 años después, también me gustaría decir que floor(value + 0.5)no es nada ingenuo, sino que depende del contexto y la naturaleza. de valores que desea redondear!
Gustavo Maciel
84

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 :

La biblioteca descrita en la cláusula 7 de ISO / IEC 9899: 1990 y la cláusula 7 de ISO / IEC 9899 / Amd.1: 1995 se denomina en adelante Biblioteca estándar C. 1)

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 :

#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 ;
}

Otra opción también de C99 sería std :: trunc que:

Calcula el entero más cercano no mayor en magnitud 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 ;

}

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::floory agrega 0.5no funciona para todas las entradas:

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

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 ):

Un prvalue de un tipo de coma flotante se puede convertir en un prvalue de un tipo entero. La conversión se trunca; es decir, la parte fraccionaria se descarta. El comportamiento no está definido si el valor truncado no se puede representar en el tipo de destino. [...]

Por ejemplo:

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

Teniendo en cuenta std::numeric_limits<unsigned int>::max()es 4294967295entonces la siguiente llamada:

myround( 4294967296.5f ) 

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:

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 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.

Shafik Yaghmour
fuente
55
@downvoter por favor explique qué se puede mejorar? La gran mayoría de las respuestas aquí son simplemente incorrectas, ya que intentan lanzar su propia ronda que todas fallan de una forma u otra. Si falta algo en mi explicación, avíseme.
Shafik Yaghmour
1
Buena respuesta completa, especialmente la parte inferior a 0.5. Otro nicho: round(-0.0). La especificación C no parece especificar. Esperaría -0.0como resultado.
chux - Restablece a Mónica el
3
@chux interesante, y el estándar IEEE 754-2008 especifica que el redondeo conserva signos de ceros e infinitos (ver 5.9).
Ruslan
1
@ Shafik esta es una gran respuesta. Nunca pensé que incluso el redondeo es una operación no trivial.
Ruslan
1
Quizás valga la pena mencionar que a std::rint()menudo es preferible std::round()cuando C ++ 11 está disponible por razones numéricas y de rendimiento. Utiliza el modo de redondeo actual, a diferencia round()del modo especial. Puede ser mucho más eficiente en x86, donde rintpuede 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 equivalente nearbyint()) ARM tiene soporte de instrucción única round(), pero en x86 solo puede alinearse con múltiples instrucciones, y solo con-ffast-math
Peter Cordes
71

Vale la pena señalar que si desea un resultado entero del redondeo, no necesita pasarlo por el techo o el piso. Es decir,

int round_int( double r ) {
    return (r > 0.0) ? (r + 0.5) : (r - 0.5); 
}
kalaxy
fuente
3
Sin embargo, no da el resultado esperado para 0.49999999999999994 (bueno, dependiendo de lo que espere, por supuesto, pero 0 me parece más razonable que 1)
stijn
@stijn Buena captura. Descubrí que agregar el sufijo literal doble largo a mis constantes solucionó su problema de ejemplo, pero no sé si hay otros ejemplos de precisión que no captaría.
kalaxy
1
por cierto, si agrega 0.49999999999999994 en lugar de 0.5, funciona bien tanto para 0.49999999999999994 como para 5000000000000001.0 como entrada. Sin embargo, no estoy seguro de si está bien para todos los valores, y no pude encontrar ninguna referencia que indique que esta es la solución definitiva.
stijn
1
@stijn Está bien para todos los valores, si no le importa en qué dirección se redondean los valores exactamente entre dos enteros. Sin pensarlo, lo probaría por análisis de casos con los siguientes casos: 0 <= d <0.5, 0.5 <= d <1.5, 1.5 <= d <2 ^ 52, d> = 2 ^ 52. También probé exhaustivamente el caso de precisión simple.
Pascal Cuoq
3
Según 4.9 [conv.fpint], "El comportamiento no está definido si el valor truncado no se puede representar en el tipo de destino". Entonces, esto es un poco peligroso. Otras respuestas SO describen cómo hacer esto de manera robusta.
Tony Delroy
41

Está disponible desde C ++ 11 en cmath (según 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;
}

Salida:

round(0.5):  1
round(-0.5): -1
round(1.4):  1
round(-1.4): -1
round(1.6):  2
round(-1.6): -2
schibum
fuente
1
también hay lroundy llroundpara resultados integrales
sp2danny
@ sp2danny: o mejor, lrintpara usar el modo de redondeo actual en lugar del roundfunky tiebreak lejos de cero.
Peter Cordes
27

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.

MSN
fuente
1
Es bueno hacer la distinción entre diferentes versiones de 'redondo'. Es bueno saber cuándo elegir cuál, también.
xtofl
55
De hecho, existen diferentes algoritmos de redondeo que pueden hacer afirmaciones razonables de ser "correctos". Sin embargo, el piso (valor + 0.5) no es uno de estos. Para algunos valores, como 0.49999997f o el doble equivalente, la respuesta es simplemente incorrecta: se redondeará a 1.0 cuando todos estén de acuerdo en que debería ser cero. Vea esta publicación para más detalles: blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Bruce Dawson
14

Hay 2 problemas que estamos viendo:

  1. conversiones de redondeo
  2. conversión de tipo

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

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))

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

Sangeet
fuente
El uso LONG_MIN-0.5e LONG_MAX+0.5 introduce complicaciones ya que las matemáticas pueden no ser exactas. LONG_MAXpuede exceder la doubleprecisión para la conversión exacta. Es probable que desee assert(x < LONG_MAX+0.5); (<vs <=), ya que LONG_MAX+0.5puede ser exactamente representable y (x)+0.5puede tener un resultado exacto del LONG_MAX+1cual falla el longlanzamiento. Otros problemas de esquina también.
chux - Restablece a Mónica el
No llame a su funció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. Úselo std::lrint(x)si está disponible.
Peter Cordes
11

Un cierto tipo de redondeo también se implementa en 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;
}

Tenga en cuenta que esto solo funciona si realiza una conversión de entero.

Philipp
fuente
2
Boost también ofrece un conjunto de funciones de redondeo simples; mira mi respuesta
Daniel Wolf
También puede usar boost:numeric::RoundEven< double >::nearbyintdirectamente 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.nice
stijn
6

Puede redondear a n dígitos de precisión con:

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;
}
Carl
fuente
44
A menos que su compilador por defecto de tamaño int a 1024 bits, esto no va a ser exacta para doble enorme ...
aka.nice
Creo que es aceptable dado cuándo se usará: si su valor doble es 1.0 e + 19, redondear a 3 lugares no tiene sentido.
Carl
3
claro, pero la pregunta es para una ronda genérica, y no puedes controlar cómo se usará. No hay razón para que la ronda falle donde el techo y el piso no lo harían.
aka.nice
Esto tiene un comportamiento indefinido para los argumentos fuera del rango de int. (En la práctica en x86, los valores de FP fuera de rango harán que se CVTTSD2SIproduzca0x80000000 como el patrón de bits entero, es decir INT_MIN, que luego se convertirá de nuevo double.
Peter Cordes
5

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 ++ donde round()es más rápido que rint(), 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()y std::rint()ambas se agregaron en C99 / C ++ 11, por lo que están disponibles en cualquier momento std::round(). La única diferencia es que nearbyintnunca aumenta FE_INEXACT.

Prefiero rint()por razones de rendimiento : gcc y clang lo alinean más fácilmente, pero gcc nunca se alinea nearbyint()(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 roundsdpuede auto-vectorizarse con SSE4.1 roundpd(o AVX vroundpd). (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():

    • Clang x86: en línea a una única entrada con -msse4.1.
    • x86 gcc: se alinea a una única entrada solo con -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 que rintcuando lo hace)
    • AArch64 gcc6.3: en línea a un solo insn por defecto.
  • std::rint:

    • x86 clang: en línea a un solo insn con -msse4.1
    • x86 gcc7: se alinea a una única entrada con -msse4.1. (Sin SSE4.1, en línea con varias instrucciones)
    • x86 gcc6.xy versiones anteriores: en línea a una única entrada con -ffast-math -msse4.1.
    • AArch64 gcc: en línea a un solo insn por defecto
  • std::round:

    • Clang x86: no está en línea
    • x86 gcc: en línea con múltiples instrucciones -ffast-math -msse4.1, que requieren dos constantes vectoriales.
    • AArch64 gcc: en línea con una sola instrucción (soporte HW para este modo de redondeo, así como el predeterminado IEEE y la mayoría de los demás).
  • std::floor/ std::ceil/std::trunc

    • x86 clang: en línea a un solo insn con -msse4.1
    • x86 gcc7.x: en línea a un solo insn con -msse4.1
    • x86 gcc6.xy versiones anteriores: en línea a un solo insn con -ffast-math -msse4.1
    • AArch64 gcc: en línea por defecto a una sola instrucción

Redondeo a int/ long/ long long:

Tiene dos opciones aquí: usar lrint(como rintpero devuelve long, o long longpara llrint), 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.

long l = lrint(x);

int  i = (int)rint(x);

Tenga en cuenta que int i = lrint(x)convierte floato double-> longprimero, y luego trunca el entero a int. 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_MINo LLONG_MIN(un patrón de bits 0x8000000o el equivalente de 64 bits, con solo el conjunto de bits de signo). Intel llama a esto el valor "entero indefinido". (Consulte la cvttsd2sientrada 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 un cvtsd2si(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 unsignedint / 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)rintlínea a roundsd/ cvttsd2si. (se perdió la optimización para cvtsd2si). lrintno está en línea en absoluto.

  • x86 gcc6.xy versiones anteriores sin -ffast-math: de ninguna manera en línea

  • x86 gcc7 sin -ffast-math: (int/long)rintredondea 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 para rintsin roundsd). lrintno está en línea
  • x86 gcc con -ffast-math : todas las formas en línea a cvtsd2si(óptimo) , sin necesidad de SSE4.1.

  • AArch64 gcc6.3 sin -ffast-math: en (int/long)rintlínea a 2 instrucciones. lrintno está en línea

  • AArch64 gcc6.3 con -ffast-math: (int/long)rintcompila a una llamada a lrint. lrintno está en línea Esta puede ser una optimización perdida a menos que las dos instrucciones que recibimos -ffast-mathsean muy lentas.
Peter Cordes
fuente
TODO: ICC y MSVC también están disponibles en Godbolt, pero no he visto su salida para esto. ediciones bienvenidas ... Además: ¿sería más útil desglosar primero por compilador / versión y luego por función dentro de eso? La mayoría de las personas no van a cambiar compiladores en función de lo bien que compilan FP-> FP o FP-> redondeo entero.
Peter Cordes
2
+1 por recomendar rint()dónde es una opción factible, que suele ser el caso. Supongo que el nombre round()implica para algunos programadores que esto es lo que quieren, aunque rint()parece misterioso. Tenga en cuenta que round()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 que nearbyint()no se alinee, dado que es en gran medida igual rint()y debe ser idéntico bajo -ffast-mathcondiciones. Eso me parece un error.
njuffa
4

Cuidado con floor(x+0.5). Esto es lo que puede suceder para números impares en el rango [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 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:

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

Aquí se da otra razón para evitar el piso (x + 0.5) .

aka.nice
fuente
2
Me interesa saber acerca de los votos negativos. ¿Es porque el empate se resuelve lejos de cero en lugar de ser el más cercano?
aka.nice
1
Nota: la especificación C dice "redondeando casos a mitad de camino lejos de cero, independientemente de la dirección de redondeo actual", por lo que el redondeo sin tener en cuenta par / impar es compatible.
chux - Restablece a Mónica el
4

Si finalmente desea convertir la doublesalida de su round()función en an int, las soluciones aceptadas de esta pregunta se verán así:

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

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:

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

Entre las razones para un mejor rendimiento está la ramificación omitida.

dshin
fuente
Esto tiene un comportamiento indefinido para los argumentos fuera del rango de int. (En la práctica en x86, los valores de FP fuera de rango harán que se CVTTSD2SIproduzca0x80000000 como el patrón de bits entero, es decir INT_MIN, que luego se convertirá de nuevo double.
Peter Cordes
2

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.

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

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).

gcc -lm -std=c99 ...

En C ++ 11

Tenemos las siguientes sobrecargas adicionales en #include <cmath> que se basan en el punto flotante de precisión doble IEEE.

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

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.

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

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.

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

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.

FauChristian
fuente
1
"No estoy seguro de por qué tantas respuestas involucran definiciones, funciones o métodos". Echa un vistazo a cuándo se le preguntó: C ++ 11 aún no estaba disponible. ;)
jaggedSpire
@jaggedSpire, dame un visto bueno entonces, si crees que es apropiado, porque todas las respuestas de puntaje alto son obsoletas y engañosas en el contexto de los compiladores más utilizados en la actualidad.
FauChristian
2

Función double round(double)con el uso de la modffunción:

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 limpio, incluye "math.h" y "límites" son necesarios. La función funciona de acuerdo con el siguiente esquema de redondeo:

  • ronda de 5.0 es 5.0
  • ronda de 3.8 es 4.0
  • ronda de 2.3 es 2.0
  • ronda de 1.5 es 2.0
  • ronda de 0.501 es 1.0
  • ronda de 0.5 es 1.0
  • ronda de 0.499 es 0.0
  • ronda de 0.01 es 0.0
  • ronda de 0.0 es 0.0
  • ronda de -0.01 es -0.0
  • ronda de -0.499 es -0.0
  • ronda de -0.5 es -0.0
  • ronda de -0.501 es -1.0
  • ronda de -1.5 es -1.0
  • ronda de -2.3 es -2.0
  • ronda de -3.8 es -4.0
  • ronda de -5.0 es -5.0
Konik
fuente
2
Esta es una buena solución. Sin embargo, no estoy seguro de que el redondeo de -1.5 a -1.0 sea estándar, esperaría -2.0 por simetría. Además, no veo el punto de la guardia principal, los dos primeros si podrían eliminarse.
aka.nice
2
Revisé el estándar ISO / IEC 10967-2, open-std.org/jtc1/sc22/wg11/docs/n462.pdf y del apéndice B.5.2.4, la función de redondeo debe ser simétrica, redondeando_F (x) = neg_F (rounding_F (neg_F (x)))
aka.nice
Esto va a ser lento en comparación con C ++ 11 rint()o nearbyint(), pero si realmente no puede usar un compilador que proporciona una función de redondeo adecuada, y necesita más precisión que rendimiento ...
Peter Cordes
1

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 -DCPP11o /DCPP11al compilador compatible con C ++ 11 (o use sus macros de versión incorporadas), y cree un encabezado 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 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.

Justin Time - Restablece a Monica
fuente
1

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.

    // 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);
    }
cuant
fuente
1
Como señalé en mi respuesta, agregar 0.5no funciona en todos los casos. Aunque al menos lidias con el problema de desbordamiento, evitas comportamientos indefinidos.
Shafik Yaghmour
1

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 para floatcuando se asigna a IEEE-754 (2008) binary32y 2 52 para doublecuando 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 negativos xse pueden manejar de acuerdo con round(-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 newlib roundf()aplicación mediante Intel versión del compilador 13, tanto con /fp:stricty /fp:fast. También verifiqué que la versión newlib coincida con la roundf()de la mathimfbiblioteca del compilador Intel. Las pruebas exhaustivas no son posibles para la precisión doble round(), sin embargo, el código es estructuralmente idéntico a la implementación de precisión simple.

#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;
}
njuffa
fuente
Hice una edición para evitar suponer que inttiene más de 16 bits de ancho. Por supuesto, todavía supone que floates un binario IEEE754 de 4 bytes32. Un C ++ 11 static_asserto tal vez una macro #ifdef/ #errorpodría verificar eso. (Pero, por supuesto, si C ++ 11 está disponible, debe usarlo std::round, o para el uso actual del modo de redondeo std::rintque se alinea muy bien con gcc y clang).
Peter Cordes
Por cierto, en gcc -ffast-math -msse4.1línea std::round()a una add( AND(x, L1), OR(x,L2), y luego a roundsd. es decir, se implementa de manera bastante eficiente rounden términos de rint. Pero no hay razón para hacer esto manualmente en la fuente C ++, porque si tiene std::rint()o std::nearbyint()también tiene std::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.
Peter Cordes
@PeterCordes Soy muy consciente de cómo implementar de manera round()eficiente en términos de rint()(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 implementar round()con C ++ antes de C ++ 11, por rint()lo que tampoco estaría disponible, solo floor()y ceil().
njuffa
@ PeterCordes Lo siento, hablé mal. round()se sintetiza fácilmente a partir rint()de ronda a cero modo, aka trunc(). No debería haber respondido antes del primer café.
njuffa
1
@PeterCordes Estoy de acuerdo en que es probable que OP no necesite el comportamiento de redondeo específico de round(); la mayoría de los programadores simplemente no son conscientes de la distinción entre round()vs rint()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 es rintf(), no roundf()".
njuffa
0

Utilizo la siguiente implementación de round in asm para la arquitectura x86 y C ++ específico de MS VS:

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

UPD: para devolver el valor doble

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

Salida:

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
Aleksey F.
fuente
El valor del resultado debe ser un valor de coma flotante con doble precisión.
buscador de la verdad el
@ truthseeker: Sí, tuve que ver el tipo requerido de valor de retorno. OK, ver "UPD".
Aleksey F.
Es de esperar que el compilador esté en línea rint()o nearbyint()con una roundsdinstrucción SSE4.1 o una frndintinstrucció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, como frndintporque no hay forma de obtener la entrada en un registro. Usarlo al final de una función con el resultado st(0)puede ser confiable como una forma de devolver la salida; aparentemente eso es seguro para eaxenteros, incluso cuando alinea la función que contiene el asm.
Peter Cordes
@PeterCordes Las optimizaciones modernas son bienvenidas. Sin embargo, no pude usar SSE4.1 ya que no existía en ese momento. Mi propósito era proporcionar la implementación mínima de ronda que podría funcionar incluso en las antiguas familias Intel P3 o P4 de 2000.
Aleksey F.
P3 no tiene ni siquiera SSE2, por lo que el compilador ya se va a utilizar para x87 double, y por lo tanto debe ser capaz de emitir frndinten sí para rint(). Si su compilador está utilizando SSE2, doublepuede que no valga la pena rebotar un registro XMM a x87 y viceversa.
Peter Cordes
0

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,

float a=47.8732355;
printf("%.3f",a);
Parveen Kumar
fuente
-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");

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.

Puntilla
fuente
Esto es hilarantemente ineficiente, y también se trunca (desechando siempre los dígitos finales) en lugar de redondear al más cercano.
Peter Cordes
-6

Hice esto:

#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;
    }
}
Peter Mortensen
fuente
3
¿No quiso decir pow (10, lugar) en lugar del operador binario ^ en 10 ^ lugar? 10 ^ 2 en mi máquina me da 8 !! Sin embargo, en mi Mac 10.7.4 y gcc, el código no funciona, devolviendo el valor original.
Pete855217