Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

172
Vistas
La conversión doble a int sin firmar en Win32 se está truncando a 2,147,483,648

Compilando el siguiente código:

 double getDouble() { double value = 2147483649.0; return value; } int main() { printf("INT_MAX: %u\n", INT_MAX); printf("UINT_MAX: %u\n", UINT_MAX); printf("Double value: %f\n", getDouble()); printf("Direct cast value: %u\n", (unsigned int) getDouble()); double d = getDouble(); printf("Indirect cast value: %u\n", (unsigned int) d); return 0; }

Salidas (MSVC x86):

 INT_MAX: 2147483647 UINT_MAX: 4294967295 Double value: 2147483649.000000 Direct cast value: 2147483648 Indirect cast value: 2147483649

Salidas (MSVC x64):

 INT_MAX: 2147483647 UINT_MAX: 4294967295 Double value: 2147483649.000000 Direct cast value: 2147483649 Indirect cast value: 2147483649

En la documentación de Microsoft no se menciona el valor máximo de entero con signo en las conversiones de double a unsigned int .

Todos los valores por encima de INT_MAX se truncan a 2147483648 cuando es el retorno de una función.

Estoy usando Visual Studio 2019 para construir el programa. Esto no sucede en gcc .

¿Estoy haciendo algo mal? ¿Hay alguna forma segura de convertir double a unsigned int ?

over 4 years ago · Santiago Trujillo
3 Respuestas
Responde la pregunta

0

Un error del compilador...

Desde el ensamblado proporcionado por @anastaciu, el código de transmisión directa llama a __ftol2_sse , que parece convertir el número en un largo con signo. El nombre de la rutina es ftol2_sse porque esta es una máquina habilitada para sse, pero el flotante está en un registro de punto flotante x87.

 ; Line 17 call _getDouble call __ftol2_sse push eax push OFFSET ??_C@_0BH@GDLBDFEH@Direct?5cast?5value?3?5?$CFu?6@ call _printf add esp, 8

El reparto indirecto, por otro lado, no

 ; Line 18 call _getDouble fstp QWORD PTR _d$[ebp] ; Line 19 movsd xmm0, QWORD PTR _d$[ebp] call __dtoui3 push eax push OFFSET ??_C@_0BJ@HCKMOBHF@Indirect?5cast?5value?3?5?$CFu?6@ call _printf add esp, 8

que extrae y almacena el valor doble en la variable local, luego lo carga en un registro SSE y llama a __dtoui3 , que es una rutina de conversión de doble a int sin firmar...

El comportamiento del elenco directo no se ajusta a C89; ni se ajusta a ninguna revisión posterior, incluso C89 dice explícitamente que:

La operación de resto que se realiza cuando un valor de tipo integral se convierte en un tipo sin signo no necesita realizarse cuando un valor de tipo flotante se convierte en un tipo sin signo. Por lo tanto, el rango de valores portátiles es [0, Utype_MAX + 1) .


Creo que el problema podría ser una continuación de esto desde 2005 : solía haber una función de conversión llamada __ftol2 que probablemente habría funcionado para este código, es decir, habría convertido el valor en un número con signo -2147483647, que habría producido el resultado correcto cuando se interpreta como un número sin signo.

Desafortunadamente __ftol2_sse no es un reemplazo directo para __ftol2 , ya que, en lugar de simplemente tomar los bits de valor menos significativos tal como están, señalaría el error fuera de rango devolviendo LONG_MIN / 0x80000000 , que, interpretado como unsigned long aquí no es en absoluto lo que se esperaba. El comportamiento de __ftol2_sse sería válido para signed long con signo, ya que la conversión de un valor doble a > LONG_MAX a signed long con signo tendría un comportamiento indefinido.

over 4 years ago · Santiago Trujillo Denunciar

0

Siguiendo la respuesta de @AnttiHaapala , probé el código usando la optimización /Ox y descubrí que esto eliminará el error ya que __ftol2_sse ya no se usa:

 //; 17 : printf("Direct cast value: %u\n", (unsigned int)getDouble()); push -2147483647 //; 80000001H push OFFSET $SG10116 call _printf //; 18 : double d = getDouble(); //; 19 : printf("Indirect cast value: %u\n", (unsigned int)d); push -2147483647 //; 80000001H push OFFSET $SG10117 call _printf add esp, 28 //; 0000001cH

Las optimizaciones getdouble() y agregaron una evaluación de expresión constante, eliminando así la necesidad de una conversión en tiempo de ejecución, lo que hizo que el error desapareciera.

Solo por curiosidad, hice algunas pruebas más, a saber, cambiar el código para forzar la conversión de flotante a int en tiempo de ejecución. En este caso el resultado sigue siendo correcto, el compilador, con optimización, usa __dtoui3 en ambas conversiones:

 //; 19 : printf("Direct cast value: %u\n", (unsigned int)getDouble(d)); movsd xmm0, QWORD PTR _d$[esp+24] add esp, 12 //; 0000000cH call __dtoui3 push eax push OFFSET $SG9261 call _printf //; 20 : double db = getDouble(d); //; 21 : printf("Indirect cast value: %u\n", (unsigned int)db); movsd xmm0, QWORD PTR _d$[esp+20] add esp, 8 call __dtoui3 push eax push OFFSET $SG9262 call _printf

Sin embargo, al evitar la inserción, __declspec(noinline) double getDouble(){...} recuperará el error:

 //; 17 : printf("Direct cast value: %u\n", (unsigned int)getDouble(d)); movsd xmm0, QWORD PTR _d$[esp+76] add esp, 4 movsd QWORD PTR [esp], xmm0 call _getDouble call __ftol2_sse push eax push OFFSET $SG9261 call _printf //; 18 : double db = getDouble(d); movsd xmm0, QWORD PTR _d$[esp+80] add esp, 8 movsd QWORD PTR [esp], xmm0 call _getDouble //; 19 : printf("Indirect cast value: %u\n", (unsigned int)db); call __ftol2_sse push eax push OFFSET $SG9262 call _printf

Se llama a __ftol2_sse en ambas conversiones y la salida es 2147483648 en ambas situaciones, las sospechas de @zwol eran correctas.


Detalles de la compilación:

  • Usando la línea de comando:
 cl /permissive- /GS /analyze- /W3 /Gm- /Ox /sdl /D "WIN32" program.c
  • En estudio visual:

    • Deshabilitar RTC en Proyecto -> Propiedades -> Generación de código y configurar las comprobaciones básicas de tiempo de ejecución por defecto .

    • Habilitación de la optimización en Proyecto -> Propiedades -> Optimización y configuración de Optimización en /Ox .

    • Con depurador en modo x86 .

over 4 years ago · Santiago Trujillo Denunciar

0

Nadie ha mirado el asm para __ftol2_sse de MS.

A partir del resultado, podemos inferir que probablemente se convirtió de x87 a int / long firmado (ambos tipos de 32 bits en Windows), en lugar de convertirse de forma segura a uint32_t .

x86 FP -> instrucciones de enteros que desbordan el resultado entero no solo envuelven/truncan: producen lo que Intel llama "entero indefinido" cuando el valor exacto no se puede representar en el destino: conjunto de bits alto, otros bits claros. es decir 0x80000000 .

(O si la excepción no válida de FP no está enmascarada, se dispara y no se almacena ningún valor. Pero en el entorno de FP predeterminado, todas las excepciones de FP están enmascaradas. Es por eso que para los cálculos de FP puede obtener un NaN en lugar de una falla).

Eso incluye instrucciones x87 como fistp (usando el modo de redondeo actual) e instrucciones SSE2 como cvttsd2si eax, xmm0 (usando truncamiento hacia 0, eso es lo que significa la t adicional).

Por lo tanto, es un error compilar double -> conversión unsigned en una llamada a __ftol2_sse .


Nota al margen / tangente:

En x86-64, FP -> uint32_t se puede compilar en cvttsd2si rax, xmm0 , convirtiéndolo en un destino firmado de 64 bits, produciendo el uint32_t que desea en la mitad inferior (EAX) del destino entero.

Es C y C ++ UB si el resultado está fuera del rango 0..2 ^ 32-1, por lo que está bien que los valores positivos o negativos enormes dejen la mitad inferior de RAX (EAX) cero del patrón de bits indefinido entero. (A diferencia de las conversiones de entero-> entero, la reducción del módulo del valor no está garantizada. ¿Está definido en el estándar C el comportamiento de lanzar un doble negativo a int sin signo? Comportamiento diferente en ARM vs. x86 . Para ser claros, nada en la pregunta es un comportamiento indefinido o incluso definido por la implementación. Solo estoy señalando que si tiene FP->int64_t, puede usarlo para implementar de manera eficiente FP->uint32_t. Eso incluye x87 fistp que puede escribir un destino entero de 64 bits incluso en modo de 32 y 16 bits, a diferencia de las instrucciones SSE2 que solo pueden manejar directamente números enteros de 64 bits en modo de 64 bits.

over 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda