static_cast español dynamic_cast c++ casting floating-point

c++ - español - dynamic_cast



¿Se puede optimizar un static_cast<flote> de doble, asignado para duplicar? (4)

Me topé con una función que creo que es innecesaria, y en general me asusta:

float coerceToFloat(double x) { volatile float y = static_cast<float>(x); return y; }

Que luego se utiliza de esta manera:

// double x double y = coerceToFloat(x);

¿Es esto diferente de hacer esto?

double y = static_cast<float>(x);

La intención parece ser simplemente reducir el doble a una precisión simple. Huele a algo escrito de extrema paranoia.


Algunos compiladores tienen este concepto de "precisión extendida", donde los dobles llevan consigo más de 64 bits de datos. Esto da como resultado cálculos de punto flotante que no coinciden con el estándar IEEE.

El código anterior podría ser un intento de evitar que los indicadores de precisión extendidos en el compilador eliminen la pérdida de precisión. Estas banderas violan explícitamente las suposiciones de precisión de los valores de punto flotante y de dobles. Parece plausible que no lo harían en una variable volatile .


Independientemente de si se permite o no un reparto de este tipo, se realiza y la asignación volátil impide que suceda.

Por ejemplo, MSVC compilando para 32 bits (usando x87) con /Ox /fp:fast :

_x$ = 8 ; size = 8 float uselessCast(double) PROC ; uselessCast fld QWORD PTR _x$[esp-4] ret 0 float uselessCast(double) ENDP ; uselessCast _y$ = 8 ; size = 4 _x$ = 8 ; size = 8 float coerceToFloat(double) PROC ; coerceToFloat fld QWORD PTR _x$[esp-4] fstp DWORD PTR _y$[esp-4] fld DWORD PTR _y$[esp-4] ret 0 float coerceToFloat(double) ENDP

Where uselessCast es el siguiente y coerceToFloat como en la pregunta.

float uselessCast(double x) { return static_cast<float>(x); }

De manera similar, GCC y Clang con -O3 -ffast-math -m32 -mfpmath=387

uselessCast(double): fld QWORD PTR [esp+4] ret coerceToFloat(double): sub esp, 20 fld QWORD PTR [esp+24] fstp DWORD PTR [esp+12] fld DWORD PTR [esp+12] add esp, 20 ret

Godbolt enlace para todo lo anterior.

Por supuesto, puede argumentar que con /fp:fast or -ffast-math no debería esperar nada de la aritmética de punto flotante de todos modos, pero puede necesitarlo y aún así poder descartar el exceso de precisión.


Siguiendo con el comentario de @NathanOliver, los compiladores pueden hacer cálculos de punto flotante con mayor precisión de la que requieren los tipos de operandos. Por lo general, en x86 significa que hacen todo como valores de 80 bits, porque es el más eficiente en el hardware. Solo cuando se almacena un valor debe revertirse a la precisión real del tipo. Y aún así, la mayoría de los compiladores de forma predeterminada realizarán optimizaciones que violan esta regla, porque forzar ese cambio en la precisión ralentiza las operaciones de punto flotante. La mayoría de las veces está bien, porque la precisión adicional no es perjudicial. Si eres un stickler, puedes usar un interruptor de línea de comandos para forzar al compilador a cumplir con esa regla de almacenamiento, y podrías ver que tus cálculos de punto flotante son significativamente más lentos.

En esa función, marcar la variable volatile le dice al compilador que no puede evitar almacenar ese valor; eso, a su vez, significa que tiene que reducir la precisión del valor entrante para que coincida con el tipo en el que se está almacenando. Por lo tanto, la esperanza es que esto obligue al truncamiento.

Y, no, escribir una conversión en lugar de llamar a esa función no es lo mismo, porque el compilador (en su modo no conforme) puede omitir la asignación a y si determina que puede generar un mejor código sin almacenar el valor, y También puede omitir el truncamiento. Tenga en cuenta que el objetivo es ejecutar los cálculos de punto flotante lo más rápido posible, y tener que lidiar con las estrictas reglas sobre la reducción de la precisión para los valores intermedios solo ralentiza las cosas.

En la mayoría de los casos, lo que necesitan las aplicaciones de punto flotante serias es correr las cosas de forma plana omitiendo truncamientos intermedios. La regla que requiere el truncamiento en el almacenamiento es más una esperanza que un requisito realista.

En una nota al margen, Java originalmente requería que todas las matemáticas de punto flotante se hicieran con la precisión exacta requerida por los tipos involucrados. Puede hacerlo en el hardware de Intel indicándole que no extienda los tipos de fp a 80 bits. Esto fue recibido con fuertes quejas de los que hacen números porque eso hace que los cálculos sean mucho más lentos. Java pronto cambió a la noción de fp "estricto" y fp "no estricto", y el procesamiento de números serios no es estricto, es decir, lo hace tan rápido como lo admite el hardware. Las personas que entienden a fondo las matemáticas de punto flotante (que no me incluyen) quieren velocidad y saben cómo hacer frente a las diferencias en la precisión que resultan.


static_cast<float>(x) es necesario para eliminar cualquier exceso de precisión, lo que produce un float . Mientras que el estándar C ++ generalmente permite que las implementaciones retengan el exceso de precisión de punto flotante en las expresiones, esa precisión debe ser eliminada por los operadores de conversión y asignación.

La licencia para usar una mayor precisión se encuentra en el borrador 13 de la cláusula N4659 de C ++, párrafo 13:

Los valores de los operandos flotantes y los resultados de las expresiones flotantes se pueden representar con mayor precisión y rango que los requeridos por el tipo; Los tipos no se cambian por lo tanto. 64

La nota 64 dice:

Los operadores de conversión y asignación aún deben realizar sus conversiones específicas como se describe en 8.4, 8.2.9 y 8.18.