¿Por qué agregar un flotador pequeño a un flotador grande simplemente elimina el pequeño?

4 minutos de lectura

Di que tengo:

float a = 3            // (gdb) p/f a   = 3
float b = 299792458    // (gdb) p/f b   = 299792448

luego

float sum = a + b      // (gdb) p/f sum = 299792448

Creo que tiene algo que ver con el cambio de mantisa. ¿Alguien puede explicar exactamente lo que está pasando? 32 bits

  • Es posible que desee intentar cambiar de flotante a doble, si se trata de un problema de límites de precisión. Recuerde, los flotadores redondean; si no desea ese comportamiento, quédese con ints o longs o use uno de los paquetes de precisión extendida.

    – keshlam

    5 de marzo de 2014 a las 1:21

  • Entonces, estoy preguntando sobre la mecánica del redondeo.

    – mharris7190

    5 de marzo de 2014 a las 1:22

  • Imagina que los flotantes estuvieran basados ​​en diez y que la mantisa tuviera solo tres dígitos. 99900 es entonces 999*10^2. Ahora agregue 3: 99903. Pero la mantisa es corta -> redondeo. Lo mismo para la base 2, pero ahora también vemos efectos funky en las conversiones, porque imprimimos en decimal.

    – usuario3125367

    5 de marzo de 2014 a las 1:25

  • aquí tienes una explicación detallada de lo que está pasando o, en otras palabras, cómo se almacena un flotante en 32 bits. en.wikipedia.org/wiki/Single-precision_floating-point_format

    – Pablo

    5 de marzo de 2014 a las 1:29

  • Relacionado: ¿Por qué agregar grande a pequeño en punto flotante introduce más error? para un caso similar donde el error de redondeo es grande en comparación con el operando más pequeño, pero no igual.

    – Peter Cordes

    8 de noviembre de 2021 a las 21:20


avatar de usuario
chris mcgrath

flotantes de 32 bits sólo tiene 24 bits de precisión. Por lo tanto, un flotador no puede contener b exactamente: hace el mejor trabajo que puede al establecer un exponente y una mantisa para acercarse lo más posible1. (El representable más cercano float a la constante en la fuente; el modo de redondeo de FP predeterminado es “más cercano”.)

Cuando consideras la representación de punto flotante de b y ae intente agregarlos, la operación de suma cambiará el número pequeño ala mantisa hacia abajo mientras trata de igualar bexponente de , hasta el punto en que el valor (3) se cae al final y te queda 0. Por lo tanto, el operador de suma termina sumando coma flotante cero a b. (Esta es una simplificación excesiva; los bits bajos aún pueden afectar el redondeo si hay una superposición parcial de mantisas).

En general, el resultado de la suma de precisión infinita tiene que redondearse al más cercano float con el modo de redondeo FP actual, y resultó ser igual a b.

Ver también ¿Por qué agregar grande a pequeño en punto flotante introduce más error? para los casos en los que el número cambia un poco, pero con un gran error de redondeo, por ejemplo, usando cifras significativas decimales como una forma de ayudar a entender el redondeo binario flotante.


Nota al pie 1: Para números tan grandes, los dos flotadores más cercanos están separados por 32. Sonido moderno incluso advierte sobre el redondeo de un int en la fuente a un float que representa un valor diferente. A menos que ya lo escriba como una constante flotante o doble (como 299792458.0f), en cuyo caso el redondeo ocurre sin previo aviso.

Por eso los más pequeños a valor que redondeará sum hasta 299792480.0f en lugar de bajar a 299792448.0f es alrededor de 16.000001 para eso b valor que redondeado a 299792448.0f. ejemplo ejecutable en el explorador del compilador Godbolt.

El modo de redondeo FP predeterminado redondea al más cercano con mantisa par como desempate. 16.0 va exactamente a la mitad y, por lo tanto, redondea a un patrón de bits de 0x4d8ef3c2, no hasta 0x4d8ef3c3. https://www.h-schmidt.net/FloatConverter/IEEE754.html. Cualquier cosa ligeramente mayor que 16 se redondea hacia arriba, porque el redondeo se preocupa por el resultado de precisión infinita. no lo hace Realmente cambiar bits antes de agregar, eso fue una simplificación excesiva. El flotante más cercano a 16.000001 tiene solo el bit bajo establecido en su mantisa, patrón de bits 0x41800001. En realidad se trata de 1.0000001192092896 x 24o 16.0000019… Mucho más pequeño y se redondearía exactamente a 16.0f y sería <= 1 ULP (unidad en el último lugar) de bque no cambiaría b porque bLa mantisa de ya está pareja.


Si evita el redondeo anticipado utilizando double a,bel valor más pequeño que puede agregar que redondea hacia arriba 299792480.0f en lugar de bajar a 299792448.0f Cuando tu lo hagas float sum = a+b es sobre a=6.0000001;lo cual tiene sentido porque el valor entero …58 permanece como ...58.0 en lugar de redondear a ...48.0fes decir, el error de redondeo en float b = ...58 era -10, entonces a puede ser mucho más pequeño.

Sin embargo, esta vez hay dos pasos de redondeo, con a+b redondeando al más cercano double si esa suma no es exacta, entonces eso double redondeando a un float. (O si FLT_EVAL_METHOD == 2, como compilación C para punto flotante x87 de 80 bits en x86 de 32 bits, el + el resultado se redondearía a 80 bits long doubleluego a float.)

  • ¿Hay 23 o 24 de precisión?

    – mharris7190

    5 de marzo de 2014 a las 1:33

  • 23 almacenados, 1 implícito, total 24.

    – usuario3125367

    5 de marzo de 2014 a las 1:39

  • ¿Este comportamiento está garantizado por estándar en C/C++?

    – TStancek

    24 de mayo de 2018 a las 7:35

  • @TStancek: Más o menos, dependiendo de FLT_EVAL_METHODpara implementaciones C/C++ que prometen IEEE-754 float. Aunque a diferencia de lo que especifica ISO C, GCC puede mantener una precisión adicional incluso en declaraciones, no solo dentro de una sola expresión, cuando se crea para objetivos como x86 de 32 bits con x87 FP (en lugar de SSE/SSE2).

    – Peter Cordes

    8 de noviembre de 2021 a las 22:38

  • @Chris: hice una edición significativa de esta respuesta, más de lo que pretendía escribir cuando comencé a editar. Inicialmente, solo iba a agregar un breve ejemplo para mostrar que “desplazar los bits hacia afuera” al alinear las mantisas no es exactamente lo que sucede; los bits bajos siguen siendo importantes para el redondeo. Pero explicar los detalles de eso se convirtió en una gran sección. Si desea recortar esto, hágamelo saber y puedo mover lo que escribí a una nueva respuesta. (Si desea mantenerlo en su respuesta, eso es genial).

    – Peter Cordes

    8 de noviembre de 2021 a las 22:59

Los números de punto flotante tienen una precisión limitada. Si estás usando un float, solo estás usando 32 bits. Sin embargo, algunos de esos bits están reservados para definir el exponente, por lo que realmente solo tiene 23 bits para usar. El número que proporciona es demasiado grande para esos 23 bits, por lo que se ignoran los últimos dígitos.

Para hacer esto un poco más intuitivo, suponga que todos los bits excepto 2 se reservaron para el exponente. Entonces podemos representar 0, 1, 2 y 3 sin problemas, pero luego tenemos que incrementar el exponente. Ahora necesitamos representar del 4 al 16 con solo 2 bits. Entonces, los números que se pueden representar estarán algo dispersos: 4 y 5 no estarán allí. Entonces, 4+1 = 4.

Todo lo que realmente necesita saber sobre la mecánica del redondeo es que el resultado que obtiene es el flotante más cercano a la respuesta correcta (con algunas reglas adicionales que deciden qué hacer si la respuesta correcta es exactamente entre dos flotadores). Da la casualidad de que el número más pequeño que agregó es menos de la mitad de la distancia entre dos flotadores en esa escala, por lo que el resultado es indistinguible del número más grande que agregó. Esto es correcto, dentro de los límites de precisión de flotación. Si desea una mejor respuesta, use un tipo de datos de mayor precisión, como double.

Otro punto de vista: Principio del casillero

float se codifica comúnmente usando 32 bits. Por lo tanto, sólo alrededor de 232 diferentes valores pueden ser codificados exactamente.
299792458 es no uno de ellos.

Comúnmente un float se codifica como un racional diádico con un significado de 24 bits multiplicado por alguna potencia de 2.

float b = 299792458;
// b typically takes on the closest representable float: 299792480.0
printf("%f\n", b); --> "299792448.000000"

El siguiente flotador representable más grande es 299792480.0 o 32 de distancia.


Agregar 299792448.0 + 3.0 es 299792451.0, pero eso tampoco se puede codificar exactamente como un float. Según el modo de redondeo actual (redondear al más cercano), el sum es entonces de nuevo 299792448.0.

float a = 3;
float sum = a + b
printf("%f\n", sum); --> "299792448.000000"

Tenido a = 17; entonces la suma 299792448.0 + 17.0 es 299792465.0 se habría redondeado a 299792480.0.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad