18/09/2019, 02:34:23 am *
Bienvenido(a), Visitante. Por favor, ingresa o regístrate.
¿Perdiste tu email de activación?

Ingresar con nombre de usuario, contraseña y duración de la sesión
Noticias: Homenaje a NUMERARIUS
 
 
Páginas: 1 [2]   Ir Abajo
  Imprimir  
Autor Tema: Enigma de programación: ¿un número distinto a sí mismo?  (Leído 4929 veces)
0 Usuarios y 1 Visitante están viendo este tema.
Abdulai
Moderador Global
Pleno*
*

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 2.235


Ver Perfil
« Respuesta #20 : 29/01/2013, 10:19:17 pm »

Prosiguiendo con los test :sonrisa:

Las pruebas anteriores eran con GCC 4.4.1 bajo WinXP y siempre me salía VERDADERO. Las tuyas veo que eran bajo Win7.

Ahora repito lo mismo con GCC 4.6.1 pero bajo Linux y sale FALSO.  Por fin!  :sonrisa:



 :¿eh?:  Pero... es la versión de gcc o el SO?

* Clip2.jpg (8.31 KB - descargado 295 veces.)
En línea
argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #21 : 29/01/2013, 10:23:56 pm »

El Windows 7 no tiene nada que ver.

En WinXP, de una PC algo viejita, también me da FALSO.

Creo que la versión del compilador es la clave del asunto.

De hecho, aunque yo creo estar corriendo la 4.7.1, es posible que sea la 4.6.1, o un rato cada una, no sé.

No creo que tenga nada que ver el sistema operativo.

Si en Linux te dio FALSO, entonces en WinXP también te tiene que dar falso (aunque con la misma versión del compilador).

En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #22 : 29/01/2013, 10:29:07 pm »



 :¿eh?:  Pero... es la versión de gcc o el SO?


La pregunta adecuada sería, suponiendo que el compilador actúa bien, ¿por qué entonces da ese resultado?

¿Y si la versión 4.6.1 y siguientes están bien? ¿Acaso la versión 4.4.1 está mal cuando da VERDADERO?

A lo mejor la versión 4.4.1 también funciona correctamente con este programita test.
No importa que den resultados distintos...


Antes de explicar la cuestión, creo que habría que hacer una lista de posibles culpables, a ver qué es lo que está pasando.

¿Quiénes son todos los actores que están involucrados desde la escritura del programa fuente en C, pasando por la compilación, hasta llegar a la ejecución del programa?
En línea

Abdulai
Moderador Global
Pleno*
*

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 2.235


Ver Perfil
« Respuesta #23 : 29/01/2013, 11:48:43 pm »

La respuesta en cierto modo ya la habías dado con las constantes FLT_EVAL_METHOD y FLT_ROUNDS.

Buscando al respecto encuentro:

http://en.cppreference.com/w/cpp/types/climits/FLT_EVAL_METHOD

FLT_EVAL_METHOD   Defined in header <cfloat>
      
#define FLT_EVAL_METHOD /* implementation defined */ (since C++11)

Specifies the precision in which all floating-point arithmetic operations are done.

Value    Explanation
negative values except -1    implementation-defined behavior
-1    the default precision is not known
0    all operations and constants evaluate in the range and precision of the type used. Additionally, float_t and double_t are equivalent to float and double respectively
1    all operations and constants evaluate in the range and precision of double. Additionally, both float_t and double_t are equivalent to double
2    all operations and constants evaluate in the range and precision of long double. Additionally, both float_t and double_t are equivalent to long double


y

http://en.cppreference.com/w/cpp/types/climits/FLT_ROUNDS


FLT_ROUNDS        Defined in header <cfloat>
      
#define FLT_ROUNDS /* implementation defined */     (since C++11)

Specifies the rounding direction of floating-point arithmetic operations. Equal to std::float_round_style
Value    Explanation
-1    the default rounding direction is not known
0    toward zero
1    to nearest
2    towards positive infinity
3    towards negative infinity


Que según esta referencia es a partir de la revisión de 2011 (C++11) y la versión de GCC 4.4.1 es anterior. 

Lo que no entiendo es si compila con los mismos valores y no dá error, ¿Por que dá VERDADERO?   Porque al fin y al cabo, se "debería" dar el mismo tratamiento a x y la constante 1.1F

En línea
argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #24 : 30/01/2013, 12:37:27 am »


Lo que no entiendo es si compila con los mismos valores y no dá error, ¿Por que dá VERDADERO?   Porque al fin y al cabo, se "debería" dar el mismo tratamiento a x y la constante 1.1F


Es que esta creencia es la trampa en la que todos podemos caer, si no estudiamos más a fondo cómo está definido el lenguaje C.

He estado estudiando el estándar, y no veo en ninguna parte a que obligue a interpretar que esos dos valores den IGUALES.

La clave está en la constante FLT_EVAL_METHOD, pero no tanto en cuánto vale, sino en lo que significa,
y en toda la historia que está detrás.

En línea

Abdulai
Moderador Global
Pleno*
*

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 2.235


Ver Perfil
« Respuesta #25 : 30/01/2013, 12:58:34 am »


Y con esas opciones de de evaluación ¿Cómo deberían declararse los las variables y constantes float para que la comparación de VERDADERO? 
Que al fin y al cabo, si uno tiene que escribir una comparación de ese estilo, la intención es que si los floats son iguales la comparación de VERDADERO.


Una pregunta, ya que desconozco estas nuevas opciones:  En la asignación directa de un float/double a un int/char ¿Se mantiene el truncamiento o hay una opción de compilación para que redondee al más cercano?
En línea
argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #26 : 30/01/2013, 01:37:18 am »


Y con esas opciones de de evaluación ¿Cómo deberían declararse los las variables y constantes float para que la comparación de VERDADERO? 
Que al fin y al cabo, si uno tiene que escribir una comparación de ese estilo, la intención es que si los floats son iguales la comparación de VERDADERO.


Creo que lo que soluciona la cuestión de la igualdad, al menos, (aunque no se solucionan otro tipo de problemas más puntillosos),
es hacer un cast al tipo float.

Y cuando usamos la variable x, que está declarada de tipo float,
al asignarle un valor a x, previamente se hace un cast a float.

Esto ocurre así porque, recordemos, el "=" es un "operador en C" (o C++),
y entonces valen las reglas de casting para operadores binarios en C.

Para los operadores de asignación, el resultado del lado derecho hace un cast respecto el tipo declarado del lazo izquierdo. En ese proceso, algo se pierde en el camino (o quizá no), y entonces es por eso que digo que el cast a float soluciona la situación.

Fijate que a la constante 1.1F en ningún momento le hice nada, sino que la puse "en crudo".

La comparación (x == (float) 1.1F) tiene que dar VERDADERO.
_________________


Una pregunta, ya que desconozco estas nuevas opciones:  En la asignación directa de un float/double a un int/char ¿Se mantiene el truncamiento o hay una opción de compilación para que redondee al más cercano?

En esto el estándar es muy explícito y claro:

Cita
When a finite value of real floating type is converted to an integer type other than _Bool,
the fractional part is discarded (i.e., the value is truncated toward zero). If the value of
the integral part cannot be represented by the integer type, the behavior is undefined.

O sea, lo que se hace es truncar la parte entera, siempre.
No hay ninguna "opción de redondeo".
Es truncamiento, y punto.

Así, 3.9 se convierte al entero 3.
Y -3.9 se convierte a -3.
Eso no cambia, mientras estés dentro del estándar.
Si no, es que el compilador directamente está mal y hay que tirarlo a la basura.

Las "opciones de redondeo" de FLT_ROUNDS son para valores punto flotante, y sólo en el caso en que no se pueda decidir si el redondeo es hacia arriba o abajo.
(O sea, los punto flotante siempre redondean al valor más cercano posible, y sólo hay incertidumbre cuando los valores más cercanos están a la misma distancia por arriba y por abajo).

Te pedí verificar la constante FLT_ROUNDS, pero no es lo que importa a la hora de entender por qué (x == 1.1F) da FALSO.

Lo que importa es FLT_EVAL_METHODS.

__________________

Aún así, aunque la regla de truncamiento es muy clara, fijate que:

3.9999 va a ser truncado dando el valor correcto: 3.

Pero:

3.999999999999999999999999999999999999999999999999999999999

te va a dar 4, incorrecto.

 :lengua_afuera:

¿Por qué?


#include <stdio.h>
int main (void) {
  printf("%i\n", (int) 3.9999);
  printf("%i\n", (int) 3.99999999999999999999999999999999999999999);
  getchar();
}
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #27 : 30/01/2013, 02:16:23 am »

Solución del misterio:

No voy a postergar más el asunto.
La explicación es esta:

El problema está en la arquitectura de los procesadores, y en el hecho de que el estándar C99 no obliga a los procesadores a tratar de un modo u otro los cálculos de punto flotante, sino que acepta la mayoría de las máquinas reales que hoy día compilan programas en C.

La constante FLT_EVAL_METHOD lo que hace es "captar" cómo la combinación de:

compilador+sistema ambiente+arquitectura de hardware+procesador

hará los cálculos intermedios en punto flotante.

El valor de 2 que obtenemos nosotros es el más probable, porque es el que corresponde a la mayoría de las computadoras de escritorio (procesadores x86).
Por eso yo estaba 99% seguro de que les tenía que dar lo mismo que a mí.

No es un valor que uno pueda cambiar "a lo bruto", sino que viene por defecto en el sistema,
y hay que ver la forma apropiada de cambiarlo, si es que se puede.
Es más: no conviene hacerlo, porque todas las librerías matemáticas están optimizadas para el valor por defecto de FLT_EVAL_METHODS == 2.

Lo que pasa es esto:

La mayoría de los procesadores de hoy en día contienen un coprocesador matemático (creo que hoy en día va todo junto en el mismo chip, pero no les sabría decir).
Lo que hace es mantener los últimos valores de punto flotante memorizados en el registro del procesador (creo que no son más de 3 los registros usados para esto).

Esos registros son de 80 bits de precisión (o mantisa), que corresponden a lo que en nuestros compiladores "se ve" como el tipo long double, que ocupa 12 bytes.

Los datos de tipo float ocupan 4 bytes, y tienen 24 bits de precisión binaria, y no caben ahí bits extra.
O sea que internamente un float no se almacena como un long double.
No hay manera de hacerlo.

Sin embargo, "por un momentito" al menos, durante el tiempo que un valor float es "calculado",
se guarda en el registro del procesador.
En ese momento se guarda en formato de alta precisión, 80 bits, o sea, como un long double.

Cuando uno declara una variable x de tipo float,
eso es una posición de memoria de 4 bytes, para albergar un float.

Pero esa posición de memoria normalmente no estará en el registro del procesador,
sino en la memoria RAM.

Y entonces x sólo tiene 4 bytes.

Entonces ahí no hay modo de almacenar la precisión extra de 80 bits.

_________________

La pregunta acá sería ¿cuál es el dichoso cálculo intermedio que supuestamente hicimos?
Después de todo, no calculamos nada, sino que 1.1F es una constante literal.
Bueno, pero 1.1F está en decimal, y tiene que almacenarse en binario.

Para hacer eso, hay que hacer un cambio de base, que implica la aplicación de un algoritmo.
A veces esos algoritmos vienen dados por el compilador, y a veces el compilador confía directamente en el procesador.
Si el cálculo lo hace el procesador, entonces lo hará con precisión de 80 bits.

Y así el 1.1F en binario, por más que le hayamos puesto el sufijo "F" de float, parece que al procesador no le interesa, porque "por un rato" está preocupado en conservar la máxima precisión de 80 bits.
Esto lo hace así por considerarlo un "cálculo intermedio".

Luego expliqué que haciendo un cast como: (float) 1.1F,
supuestamente el problema se arregla.
Esto se va a arreglar solamente si al realizar ese cast, el resultado intermedio se almacena en la memoria RAM.

En cambio, si se almacena el resultado del cast en un regsitro del procesador (que no sé si eso es posible),
honestamente no sé lo que ocurre en ese caso (pienso que sería lo mismo que no haber hecho ningún cast).

_________________

Así que la solución definitiva al problema sería:

* Poner todas las cantidades de punto flotante que nos interesen, almacenadas en variables.

* Asegurarse de que todas las variables se almacenen en RAM, y no en registros de la CPU.

Para lograr esto último quizá haya que quitar algunas opciones de optimización del compilador.
Pero no sé si recomendar eso.
Quizá lo mejor sea poner la opción de línea de comandos: -ffloat-store.

_________________

Data del estándar C99: Sección 6.3.1.4 en Estándar C99.

Sobre las opciones del compilador, por ejemplo ver: http://tigcc.ticalc.org/doc/comopts.html#SEC44

En general, creo que hay una intención de los compiladores a ajustarse a los estándares.

En particular, el estándar de C intenta ajustarse a otros estándares internacionales ya aceptados,
como son el estándar de representación de caracteres en otros idiomas,
el estándar de operadores de un lenguaje de programación,
y el estándar de representación de números de punto flotante.

Una primera aproximación a este estándar se puede ver en Wikipedia: http://en.wikipedia.org/wiki/IEEE_floating_point.


Entre otros detalles, es que el compilador de GCC aún no se ajusta del todo a estos estándares,
y no porque no quiera, sino porque es difícil: hay que hacer ajustes que sirvan para todas las arquitecturas de hardware más o menos importantes que hay en el mundo.

En línea

Abdulai
Moderador Global
Pleno*
*

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 2.235


Ver Perfil
« Respuesta #28 : 30/01/2013, 04:56:33 pm »

Mirando el desensamblado del código generado por el GCC 4.6.1, lo que hizo fué ignorar olímpicamente el sufijo 'F' y escribir en memoria lo que se le cantó.
De entrada lo encuentro desprolijo, ya que si te ignora un sufijo, debe emitir el Warning correspondiente.


Cuando compiló sin el cast, para hacer la comparación, al registro del procesador matemático lo cargó mediante un fld TBYTE PTR ds:offset.
O sea, con un long double de 80bits previamente escrito en memoria por el compilador.

Mientras que con el cast, lo cargó con fld DWORD PTR ds:offset.
O sea, un float almacenado en memoria que el procesador matemático internamente extiende a long double (80bits)

En ambos casos a la variable x se inicializó con una constante float (32bits) y cargada mediante fld DWORD PTR ds:offset


¿Que está pasando?
Que el criterio de redondeo es diferente cuando la extensión a long double la hace el compilador a cuando la hace el procesador matemático. Salvo por supuesto casos particulares (como 1.125F o 1.25F)


Esto lo veo como un bug bastante grande, ya que no tiene por qué importar cuando y quién hace la extensión.

En línea
argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #29 : 30/01/2013, 10:25:29 pm »

No estoy de acuerdo conque el problema sean los criterios de redondeo.

Si el problema fuera ese, entonces la diferencia (float) 1.1F - 1.1F tendría que dar un valor dentro del rango representable por el tipo float.

Pero no, da un valor bastante menor que FLT_EPSILON, y eso quiere decir que el resultado de ese cálculo no es un float, pues en precisión float el resultado tiene que ser 0, si los redondeos son iguales, o FLT_EPSILON, si los redondeos son distintos.

(O sea, el redondeo se hace en el último dígito binario).

_____________________

Parece que tenés razón en que ignora el sufijo "F".
Pero después te voy a decir por qué me parece que el problema es más profundo que eso.

En principio, los resultados obtenidos no me parece que sean incompatibles con una constante

FLT_EVAL_METHOD == 2

Aún así, puede que el compilador esté haciendo algo mal con los sufijos...

_________


A mí me parece que el problema no es de redondeo,
sino de mezclar distintas precisiones.

Cuando cargás un valor float en memoria, el redondeo se hace hasta la precisión de 24 bits del float, porque no hay más remedio.
No hay manera de almacenar más información ahí (en el DWORD).

En cambio, si calculás el valor de 1.1 con una precisión de 80 bits, el redondeo se hace a 80 bits.

Los redondeos son diferentes debido a la mera precisión de cada formato de punto flotante,
uno de 24 bits y otro de 80.

Por otro lado, el valor de 1.1F se tiene que pasar a binario, y eso sí o sí lleva a mantener guardado como resultado intermedio de cálculo en el registro del procesador un valor con una precisión mayor, de 80 bits, al menos por un rato.
Olvidemos por un rato el sufijo "F".
El cálculo de pasar 1.1 a binario se hace en precisión long double.

Eso es lo que significa, o al menos parece permitir, la constante FLT_EVAL_METHOD == 2:
Los cálculos intermedios se hacen en precisión long double.

Si el registro del procesador obtiene un número binario con la precisión de 80 bits para 1.1,
eso es lógicamente un valor distinto al obtenido redondeando 1.1 a la precisión de 24 bits, y luego almacenado temporalmente en el registro, de nuevo con 80 bits.

En cualquier caso, el procesador almacena todo en sus registros de 80 bits.
Si un valor del registro es tomado de un float de la RAM, tendrá la precisión de 24 bits, rellenando con bits 0 el resto.
Si el valor ha sido calculado previamente, y se ha guardado en el registro, o se han guardado copias de esos valores del registro en sitios temporales de la RAM, entonces esa precisión de 80 bits se ha mantenido.

Es decir, el compilador bien puede elegir guardar en memoria RAM copias de los valores guardados en el registro.
No creo que haya una regla que impida que el compilador haga eso.
Y yo sospecho que ha pasado eso en lo que vos mostrás.

El compilador no tiene sus propios criterios de redondeo,
y si los tuviera, las diferencias serían, o bien en el bit 24 de un float, o en el bit 80 de un long double.
Pero la diferencia entre ambos números no es de ninguna de esas dos formas.
(Sigo sumando razones para decir que el redondeo no es el problema).

Además, fijate que cuando le hacés un sizeof a la constante 1.1F, te da que es de 4 bytes, o sea, de tamaño float.

Pero eso no puede ser, porque la precisión de la información que conserva la constante 1.1F es la de un long double.

De hecho, la diferencia 1.1L - 1.1F da exactamente igual a 0.

_________________


Es muy posible que tengas razón en que el compilador ignora los sufijos de las constantes.
Pero la pregunta que me hago es si eso es realmente un "error" del diseño.

Porque no sé cuáles son las exigencias del estándar C99 en este caso.

Si este "aparente" error está dentro de lo aceptado por la normativa C99,
entonces no sería un realmente un BUG del compilador, sino una cuestión que habría que tener en cuenta al hacer programas bajo C99,
tratando de entender lo que significa el valor FLT_EVAL_METHOD == 2.

O sea, el compilador quizá tenga derecho a hacer lo que quiera a ahí.

_____________

Para que se entienda de lo que estoy hablando,
te digo que este asunto ya ha sido discutido por el comité de estandarización que hizo el C99.

Te repito acá un párrafo de la sección 5.2.4.2.2, que trata esta cuestión:

Cita
Except for assignment and cast (which remove all extra range and precision), the values of operations
with floating operands and values subject to the usual arithmetic conversions and of floating constants are
evaluated to a format whose range and precision may be greater than required by the type.


Para asegurar que un valor que necesitamos tratar como constante,
sea debidamente tratado en C,
hay que declararlo previamente como:

static const float valor_correcto1_1F = 1.1F;
En línea

Páginas: 1 [2]   Ir Arriba
  Imprimir  
 
Ir a:  

Impulsado por MySQL Impulsado por PHP Powered by SMF 1.1.4 | SMF © 2006, Simple Machines LLC XHTML 1.0 válido! CSS válido!