vectores variable tipos resueltos punteros programacion matrices llenar imprimir ejercicios ejemplos dobles datos como arreglos c floating-point type-conversion implicit-conversion

tipos - Resultado no intuitivo de la asignación de un número de precisión doble a una variable int en C



vectores en c++ ejercicios resueltos (4)

¿Podría alguien darme una explicación de por qué obtengo dos números diferentes, resp. 14 y 15, como una salida del siguiente código?

#include <stdio.h> int main() { double Vmax = 2.9; double Vmin = 1.4; double step = 0.1; double a =(Vmax-Vmin)/step; int b = (Vmax-Vmin)/step; int c = a; printf("%d %d",b,c); // 14 15, why? return 0; }

Espero obtener 15 en ambos casos, pero parece que me faltan algunos fundamentos del lenguaje.

No estoy seguro de si es relevante pero estaba haciendo la prueba en CodeBlocks. Sin embargo, si escribo las mismas líneas de código en algún compilador en línea ( este, por ejemplo ), obtengo una respuesta de 15 para las dos variables impresas.


... por qué tengo dos números diferentes ...

Aparte de los problemas de punto flotante habituales, las rutas de cálculo a c se llegan de diferentes maneras. c se calcula guardando primero el valor como double a .

double a =(Vmax-Vmin)/step; int b = (Vmax-Vmin)/step; int c = a;

C permite que las matemáticas de punto flotante intermedio se calculen utilizando tipos más anchos. Compruebe el valor de FLT_EVAL_METHOD en <float.h> .

A excepción de la asignación y el lanzamiento (que eliminan todo el rango y la precisión adicionales), ...

-1 indeterminable;

0 evalúa todas las operaciones y constantes solo al rango y precisión del tipo;

1 evalúo operaciones y constantes de tipo float y double al rango y precisión del tipo double , evalúe operaciones long double y constantes al rango y precisión del tipo long double ;

2 evalúe todas las operaciones y constantes para el rango y la precisión del tipo long double .

C11dr §5.2.4.2.2 9

OP informó 2

Al guardar el cociente en double a = (Vmax-Vmin)/step; , la precisión es forzada a double mientras que int b = (Vmax-Vmin)/step; Se podría computar tanto long double como el long double .

Esta diferencia sutil se debe a que (Vmax-Vmin)/step (calculado tal vez como el long double ) se guarda como un double frente a un long double . Uno como 15 (o justo arriba), y el otro justo debajo de 15. El truncamiento int amplifica esta diferencia a 15 y 14.

En otro compilador, los resultados pueden haber sido los mismos debido a FLT_EVAL_METHOD < 2 u otras características de punto flotante.

La conversión a int desde un número de punto flotante es grave, con números cerca de un número entero. A menudo es mejor round() o lround() . La mejor solución depende de la situación.


De hecho, esta es una pregunta interesante, esto es lo que sucede precisamente en su hardware. Esta respuesta proporciona los cálculos exactos con la precisión de los flotadores de double precisión IEEE, es decir, una mantisa de 52 bits más un bit implícito. Para más detalles sobre la representación, vea el artículo de wikipedia .

Ok, entonces primero debes definir algunas variables:

double Vmax = 2.9; double Vmin = 1.4; double step = 0.1;

Los valores respectivos en binario serán

Vmax = 10.111001100110011001100110011001100110011001100110011 Vmin = 1.0110011001100110011001100110011001100110011001100110 step = .00011001100110011001100110011001100110011001100110011010

Si cuenta los bits, verá que he dado el primer bit que se establece más 52 bits a la derecha. Esta es exactamente la precisión con la que su computadora almacena un double . Tenga en cuenta que el valor del step se ha redondeado.

Ahora haces algunos cálculos en estos números. La primera operación, la resta, da como resultado el resultado preciso:

10.111001100110011001100110011001100110011001100110011 - 1.0110011001100110011001100110011001100110011001100110 -------------------------------------------------------- 1.1000000000000000000000000000000000000000000000000000

Luego divides por step , que ha sido redondeado por tu compilador:

1.1000000000000000000000000000000000000000000000000000 / .00011001100110011001100110011001100110011001100110011010 -------------------------------------------------------- 1110.1111111111111111111111111111111111111111111111111100001111111111111

Debido al redondeo del step , el resultado es un poco inferior a 15 . A diferencia de antes, no lo he redondeado inmediatamente, porque ahí es precisamente donde ocurren las cosas interesantes: su CPU puede almacenar números de punto flotante de mayor precisión que un double , por lo que el redondeo no se realiza de inmediato.

Entonces, cuando convierte el resultado de (Vmax-Vmin)/step directamente a un int , su CPU simplemente corta los bits después del punto fraccionario (así es como los estándares de lenguaje definen la conversión double -> int implícita):

1110.1111111111111111111111111111111111111111111111111100001111111111111 cutoff to int: 1110

Sin embargo, si primero almacena el resultado en una variable de tipo doble, se produce el redondeo:

1110.1111111111111111111111111111111111111111111111111100001111111111111 rounded: 1111.0000000000000000000000000000000000000000000000000 cutoff to int: 1111

Y este es precisamente el resultado que tienes.


La respuesta "simple" es que esos números aparentemente simples 2.9, 1.4 y 0.1 están representados internamente como punto flotante binario, y en binario, el número 1/10 se representa como la fracción binaria de repetición infinita 0.00011001100110011 ... [ 2] . (Esto es análogo a la forma en que 1/3 en decimal termina siendo 0.333333333 ...). Convertido de nuevo a decimal, esos números originales terminan siendo cosas como 2.8999999999, 1.3999999999 y 0.0999999999. Y cuando haces cálculos adicionales con ellos, esos .0999999999 tienden a proliferar.

Y luego, el problema adicional es que la ruta por la que calcula algo, ya sea que lo almacene en variables intermedias de un tipo en particular, o lo calcule "todo a la vez", lo que significa que el procesador podría usar registros internos con mayor precisión que el tipo double - puede terminar haciendo una diferencia significativa.

La conclusión es que cuando convierte un respaldo double en un int , casi siempre desea redondear , no truncar. Lo que sucedió aquí fue que (en efecto) una ruta de cálculo le dio 15.0000000001 que se truncó hasta 15, mientras que la otra le dio 14.999999999 que se truncó hasta 14.

Vea también la pregunta 14.4a en la lista de Preguntas Frecuentes .


Se analiza un problema equivalente en el análisis de los programas de C para FLT_EVAL_METHOD == 2 .

Si FLT_EVAL_METHOD==2 :

double a =(Vmax-Vmin)/step; int b = (Vmax-Vmin)/step; int c = a;

calcula b al evaluar una expresión long double luego truncarla en un int , mientras que para c está evaluando desde long double , truncándola al double y luego a la int .

Por lo tanto, ambos valores no se obtienen con el mismo proceso, y esto puede llevar a resultados diferentes porque los tipos flotantes no proporcionan la aritmética exacta habitual.