resueltos punto online normalizado mantisa flotante exponente ejercicios ejemplos comandos codigos basicos c++ floating-point d

online - El ciclo de maximización del punto flotante no finaliza en D, funciona en C++



punto flotante ieee 754 ejemplos (2)

Tengo dos programas similares, uno en C ++ y otro en D.

La compilación está en Windows7 64 bits, en binarios de 64 bits.

Versión de C ++, VS 2013:

#include <iostream> #include <string> int main(int argc, char* argv[]) { float eps = 1.0f; float f = 0.0f; while (f + eps != f) f += 1.0f; std::cout << "eps = " + std::to_string(eps) + ", max_f = " + std::to_string(f) << std::endl; return 0; }

Versión D, DMD v2.066.1:

import std.stdio; import std.conv; int main(string[] argv) { float eps = 1.0f; float f = 0.0f; while (f + eps != f) f += 1.0f; writeln("eps = " ~ to!string(eps) ~ ", max_f = " ~ to!string(f)); return 0; }

La versión de C ++ funciona como se esperaba y encuentra que f + e == f cuando f = 16777216.

Pero la versión D cuelga para siempre. Cuando pongo breakpoint veo que en la versión D f también 16777216 (después de funcionar por un tiempo) y Watch window (uso VisualD) muestra que (f + e! = F) es ''falso'' por lo que el ciclo debe terminar pero es no es el caso durante el tiempo de ejecución.

Creo que Assembly podría dar la respuesta, pero no soy muy bueno con eso.

Soy nuevo en D, así que debería ser el caso que utilicé mal el lenguaje / compilador (compilado con DMD solo como ''dmd test.d'' sin opciones adicionales y también desde VS con VisualD con opciones predeterminadas). ¿Alguna idea de lo que podría estar mal con la versión D del programa? ¡Gracias!

Desmontaje:

C ++:

000000013F7D1410 mov rax,rsp 000000013F7D1413 push rbp 000000013F7D1414 lea rbp,[rax-5Fh] 000000013F7D1418 sub rsp,0E0h 000000013F7D141F mov qword ptr [rbp+17h],0FFFFFFFFFFFFFFFEh 000000013F7D1427 mov qword ptr [rax+8],rbx 000000013F7D142B movaps xmmword ptr [rax-18h],xmm6 000000013F7D142F xorps xmm1,xmm1 float eps = 1.0f; float f = 0.0f; 000000013F7D1432 movss xmm6,dword ptr [__real@3f800000 (013F7D67E8h)] 000000013F7D143A nop word ptr [rax+rax] f += 1.0f; 000000013F7D1440 addss xmm1,xmm6 while (f + eps != f) 000000013F7D1444 movaps xmm0,xmm1 000000013F7D1447 addss xmm0,xmm6 000000013F7D144B ucomiss xmm0,xmm1 000000013F7D144E jp main+30h (013F7D1440h) 000000013F7D1450 jne main+30h (013F7D1440h)

RE:

000000013F761002 mov ebp,esp 000000013F761004 sub rsp,50h { float eps = 1.0f; 000000013F761008 xor eax,eax 000000013F76100A mov dword ptr [rbp-50h],eax 000000013F76100D movss xmm0,dword ptr [rbp-50h] 000000013F761012 movss dword ptr [f],xmm0 float f = 0.0f; while (f + eps != f) f += 1.0f; 000000013F761017 movss xmm1,dword ptr [__NULL_IMPORT_DESCRIPTOR+1138h (013F7C3040h)] 000000013F76101F movss xmm2,dword ptr [f] 000000013F761024 addss xmm2,xmm1 000000013F761028 movss dword ptr [f],xmm2 000000013F76102D fld dword ptr [f] 000000013F761030 fadd dword ptr [__NULL_IMPORT_DESCRIPTOR+1138h (013F7C3040h)] 000000013F761036 fld dword ptr [f] 000000013F761039 fucomip st,st(1) 000000013F76103B fstp st(0) 000000013F76103D jne D main+17h (013F761017h) 000000013F76103F jp D main+17h (013F761017h)

Resumen

Acepte la respuesta de harold de que el comportamiento del programa se debe al uso mixto de FPU y SSE.

A continuación, se incluye un resumen de lo que sucede en el fragmento de ensamblado D. De hecho, el ciclo se ejecutará para siempre.

SSE se comporta estrictamente de acuerdo con IEEE-754 cuando f alcanza 16777216.0 y agregamos 1.0 a este valor (f + = 1.0f) aún obtenemos 16777216.0 en el registro xmm2, luego lo almacenamos en la memoria.

(f + eps! = f) expresión se calcula en la FPU. Dado que los registros de FPU tienen suficiente precisión (f + eps) da como resultado 16777217.0. Si almacenamos este resultado en la memoria en la variable flotante, obtendríamos el valor esperado 16777216.0 (ya que 16777217.0 no se representa como float). Y (f + eps! = F) sería ''falso'' y el bucle terminaría. Pero no almacenamos ningún número en la memoria y realizamos una comparación en la FPU (ya que tenemos ambos operandos). Significa que comparamos un número que se calcula estrictamente de acuerdo con IEEE-754 (f) y otro que se calcula con una precisión de 80 bits (f + eps). 16777216.0! = 16777217.0 y el bucle se ejecuta para siempre.

No soy un experto en esta área, pero para mí parece que hacer punto flotante con instrucciones SSE es más sólido, como se demostró en la versión C ++ del programa.

Actualizar

Tuve una discusión sobre el foro D http://forum.dlang.org/thread/[email protected]

Resultó que el programa se comporta correctamente, de acuerdo con la especificación del lenguaje, los cálculos intermedios se pueden realizar con mayor precisión.

La implementación robusta para cualquier compilador D es:

import std.stdio; int main() { const float eps = 1.0f; const float step = 1.0; float f = 0.0f; float fPlusEps = f + eps; while (f != fPlusEps) { f += step; fPlusEps = f + eps; } writeln("eps = ", eps, ", max_f = ", f); return 0; }


Código FPU y SSE mixto, eso es ... realmente extraño. No veo absolutamente ninguna razón para implementarlo de esta manera.

Pero lo han hecho, y el resultado es que f + eps != f se evalúa con una precisión extendida de 80 bits, mientras que
f += 1.0f se evalúa usando flotadores de 32 bits.

Eso significa que el ciclo nunca puede terminar, ya que f dejará de subir antes del valor que hace
f + eps != f falso (que, en precisión de 80 bits, es enorme) se alcanza.


Intentar romper un bucle con! = O == con valores de coma flotante es buscar problemas.

El comportamiento diferente probablemente no se deba a que el flotador duplique el compilador de conversión de punto flotante interno de 80 bits que puede adoptar al pasar valores a la FPU.

Al extender la mantisa, en particular, algunos compiladores u optimizadores pueden decidir dejar el bit menos significativo "al azar" en lugar de ponerlo a cero. Así que 1.0f , cuando se le da a la FPU puede convertirse en 1.000000000000000000000012134432 que, de acuerdo con un float , la precisión sigue siendo 1.0 , pero cuando 1.000000000000000000000012134432 y 1.000000000000000000000089544455 (las dos colas son aleatorias) son comparados por la FPU, se ven diferentes.

Debe verificar cómo el compilador de C ++ y D trata la extensión / reducción de coma flotante y, finalmente, configura los conmutadores apropiados: si los dos compiladores no son del mismo fabricante, es probable que hayan elegido diferentes opciones para sus respectivos valores predeterminados.