16/09/2019, 11:22:16 pm *
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 4928 veces)
0 Usuarios y 1 Visitante están viendo este tema.
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
« : 29/01/2013, 06:54:35 am »

Les voy a plantear aquí un enigma de la programación,
que más bien es como un desafío.
Se trata de una cuestión del lenguaje C.

Se trata de comparar un número consigo mismo, y que el resultado de la comparación sea ¡que son distintos!

Los números serán de tipo float (precisión simple).

Definimos una variable x de tipo float, inicializada a un valor sencillo: 1.1F.
El sufijo "F" asegura que la constante es de tipo float.
Por lo tanto en memoria ocupará sólo un tamaño de precisión simple (digamos 4 bytes).

Por otro lado, la variable x también es de tipo float, y ocupa en memoria el tamaño de precisión simple (para mí, 4 bytes).

Como se ve, no hay más espacio para "esconder" bits adicionales con dígitos extraños.
Es decir, hasta la precisión de float (que para 4 bytes nos dan 24 bits de precisión en la mantisa),
tanto x como la constante 1.1F coinciden en todos sus dígitos.

Y como no hay más dígitos, al hacer una comparación entre ellos, tendría que darnos que son iguales.

_______________

Sin embargo, al comparar (x == 1.1F) nos da FALSO.

¿Qué puede haber pasado?

Lo primero que se nos ocurre es que puede haber algún problema con la conversión de base decimal al binario de la representación interna.

En principio, el valor 1.1F al pasar a binario, trunca o redondea sus dígitos, por lo tanto ya deja de ser exactamente el número 1.1 en decimal, sino un valor ligeramente diferente.

Pero esto no puede ser el problema,
porque en ambos casos, en x y en la constante 1.1F,
tenemos el mismo valor como resultado de ese redondeo.
Ambos valores de entrada coinciden y se almacenan en memoria
como (la versión redondeada de) 1.1F,
que se convierte a binario de la misma manera para los dos, porque no puede ser de otra manera,
y con la misma precisión (o cantidad de bits), en lo que al tipo float se refiere.

Los valores que existen en memoria tras la conversión de decimal a binario, para x y para 1.1F, son los mismos, coinciden hasta el último bit.

__________

Otra idea que se nos ocurre es que a lo mejor los cálculos intermedios van convirtiendo los operandos a una precisión mayor.
En ese caso, especulamos, al convertir los valores de x y de la constante 1.1F a una precisión mayor,
por ejemplo long double, pueden haberse producido errores de aproximación en el cálculo de la resta x - 1.1F, digamos.

Pero esto no puede ser el problema, porque tanto x como 1.1F valen ambos lo mismo: 1.1F, y
se almancenan en memoria con los mismos bits de un tipo float.

Luego, al convertirlos a una precisión mayor, digamos long double,
esa conversión no cambia los dígitos ni de x ni de la constante 1.1F,
sino que el estándar establece que permanecen tal cual, aunque quizá con varios 0's agregados por detrás.
Después se hace la resta en la alta precisión de long double, y se obtiene el resultado.
Este resultado tendría que ser 0, porque no hay más remedio.

______________


Así que esas explicaciones están descartadas.
Está pasando algo más sutil.
¿Qué es?

El programita que hice yo tienen un printf() exagerado,
sólo para que la información se vea bien clara al correr el programa.
Pero no es necesario.
Se puede hacer todo más breve (mostrar el valor booleano de: ( x == 1.1F)).

Además puse la constante 1.1F en una macro, para poder repetirla varias veces,
y jugar con otros posibles valores.

Versión breve:


#include <stdio.h>

#define NUM 1.1F

int main (void) {
  float x = NUM;
  _Bool comparacion = (x == NUM);

  printf("\n Es x == 1.1F?    %s \n\n", (comparacion)? "VERDADERO": "FALSO"); 
  getchar();
}





El resultado que obtengo al correr el programa es FALSO.

¿Por qué? 
:lengua_afuera:




Versión extendida:



#include <stdio.h>

#define NUM  1.1F
#define SNUM "1.1F"

int main (void) {
     
  float x = NUM;

  _Bool comparacion = (x == NUM);

  printf(
     "\n"
     "Definimos: float x = " SNUM "\n\n"
     "Bytes ocupados en memoria: \n\n"
     "sizeof(x) == %i        sizeof(" SNUM ") == %i \n\n"
     "sizeof(x) == sizeof( " SNUM ")? ---> %s \n\n"
     "Por lo tanto: x y " SNUM " tienen %s precision.\n\n"
     "Es x == " SNUM "? %s \n\n",
     sizeof(x), sizeof(NUM),
     (sizeof(x) == sizeof(NUM))? "SI": "NO",
     (sizeof(x) == sizeof(NUM))? "la misma": "distinta",
     (comparacion)? "VERDADERO": "FALSO"
    ); 
 
  getchar();

}



En línea

feriva
Pleno*
*****

Karma: +1/-0
Desconectado Desconectado

Sexo: Masculino
España España

Mensajes: 8.360



Ver Perfil
« Respuesta #1 : 29/01/2013, 07:44:00 am »

 Pues no sé, pero por qué usas "%s" y "%i" en vez de "%g" para el float, qué diferencia hay entre %g y %s.

 En la versión extendida defines

#define NUM  1.1F
#define SNUM "1.1F"

 SNUM es de tipo char al estar entre comillas, ¿no?

Saludos.
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 #2 : 29/01/2013, 01:46:05 pm »

Uhh, te fijaste en los detalles que menos importaban, jeje.

%s es para string (o sea, char*),
%i para enteros,
%g es una forma alternativa de mostrar punto flotante.

SNUM es de tipo char*, no char.

En línea

Abdulai
Moderador Global
Pleno*
*

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 2.235


Ver Perfil
« Respuesta #3 : 29/01/2013, 02:05:42 pm »

Es problema del compilador o en la definición de _Bool.

Yo compilo la versión breve con el viejo compilador de Borland freeware (BCC32) cambiando solamente _Bool por bool y me devuelve VERDADERO.

EDIT

Hago lo mismo con la versión extendida y tengo:

Definimos: float x = 1.1F

Bytes ocupados en memoria:

sizeof(x) == 4        sizeof(1.1F) == 4

sizeof(x) == sizeof( 1.1F)? ---> SI

Por lo tanto: x y 1.1F tienen la misma precision.

Es x == 1.1F? VERDADERO
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 #4 : 29/01/2013, 02:11:36 pm »

El _Bool no es el problema.

Y el compilador tiene algo que ver, pero no es incorrecto.

Hay compiladores o computadoras donde todo funcionará bien, pero en otros no.

El problema proviene, más o menos, de unos detalles en las especificaciones técnicas del estándar C99.

Cuando lo compilo con GCC, el resultado me da: VERDADERO.
O sea que el problema no es el compilador.

Si ahora, al mismo compilador, le pongo el parámetro -std=c99 en línea de comandos,
entonces el resultado me da: FALSO.

En línea

Capitan Trueno
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
España España

Mensajes: 750


Ver Perfil
« Respuesta #5 : 29/01/2013, 03:49:49 pm »

No conozco el lenguaje C con precisión, pero se me ocurre una posibilidad. La comparación entre valores de variables no solo depende de su contenido sino también de su definición. En el programa se ve que ambas variables se definen de forma distinta (usando comandos distintos) y aunque el contenido numérico sea el mismo en ambas variables podría ocurrir que no lo sea su definición, y eso puede ser la causa de la diferencia que ves:

#define NUM  1.1F

float x = NUM;

¿Estás seguro de que ambas definiciones son equivalentes? En otros lenguajes de programación (que si conozco) ocurre que dos variables con idéntico contenido numérico pero con distinta definición nunca son iguales.

Salu2
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 #6 : 29/01/2013, 04:01:02 pm »

No tienen distinta definición, porque el #define NUM 1.1F
es solamente una abreviatura.
Uso la abreviatura solamente para poder cambiar la constante a gusto.

El resultado es igual si se programa directamente así:

float x = 1.1F;

Y luego más abajo se pone:

  _Bool comparacion = (x == 1.1F);

  printf("\n Es x == 1.1F?    %s \n\n", (comparacion)? "VERDADERO": "FALSO"); 


Incluso se puede hacer más breve, para ver que no hay errores del programador:



#include <stdio.h>
int main (void) {
  float x = 1.1F;

  printf("\n Es x == 1.1F?    %s \n", (x == 1.1F)? "VERDADERO": "FALSO"); 
  getchar();
}


La constante 1.1F es float,
tiene 24 bits de precisión,
y la variable x también es float,
y tiene por lo tanto 24 bits de precisión.

En la memoria, la variable x y la constante 1.1F tienen el mismo contenido numérico, la misma precisión, y los mismos bits, y son del mismo tipo.

Aún cuando C no se hace mucho problema al mezclar tipos de datos distintos,
acá están tomadas todas las precauciones igual, por las dudas.
Todo es lo mismo.

Además, si Abdulai corre esta última versión del programa con su compilador,
obtendrá VERDADERO.

Si yo lo corro con mi compilador GCC 4.7.1, obtengo VERDADERO.

Pero si le pongo la opción de compilación -std=c99 (que solamente hace que el compilador se ajuste lo más posible al estándar del año 1999 del lenguaje C), me da FALSO.

¿Algún problema con el estándar C99? No, porque el estándar es muy claro con lo que pasa a nivel de bits en los datos numéricos, tanto enteros como de punto flotante.
Es más sólido (o debiera serlo) un programa compilado con la normativa C99.

Entonces, ¿qué anda mal?

Aclaración: yo conozco la respuesta, por eso dije al principio que es un enigma o desafío.

Pista: La respuesta no es trivial. Y la compilación encima es correcta. Lo único que está mal es que no nos da lo que esperamos que da. ¿Por qué?
En línea

feriva
Pleno*
*****

Karma: +1/-0
Desconectado Desconectado

Sexo: Masculino
España España

Mensajes: 8.360



Ver Perfil
« Respuesta #7 : 29/01/2013, 07:06:57 pm »




Otra idea que se nos ocurre es que a lo mejor los cálculos intermedios van convirtiendo los operandos a una precisión mayor.
En ese caso, especulamos, al convertir los valores de x y de la constante 1.1F a una precisión mayor,
por ejemplo long double, pueden haberse producido errores de aproximación en el cálculo de la resta x - 1.1F, digamos.

Pero esto no puede ser el problema, porque tanto x como 1.1F valen ambos lo mismo: 1.1F, y
se almancenan en memoria con los mismos bits de un tipo float.

Luego, al convertirlos a una precisión mayor, digamos long double,
esa conversión no cambia los dígitos ni de x ni de la constante 1.1F,
sino que el estándar establece que permanecen tal cual, aunque quizá con varios 0's agregados por detrás.
Después se hace la resta en la alta precisión de long double, y se obtiene el resultado.
Este resultado tendría que ser 0, porque no hay más remedio.


Sí, pero al hacer eso es probable que algún flag o algo se active y ocupe un bit por ahí, hay bits que la máquina usa para sus cosas, que no son de valor, pero... no sé, porque no sé si se da esta suposición.

 El caso es que todo esto del C++ y sus conversiones, bases, redondeos, versiones, compiladores y demás, es muy denso, hay mucha cosa, no se aprende así como así  :lengua_afuera:

 Saludos.
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 #8 : 29/01/2013, 07:42:34 pm »

No hay ningún flag.

Lo único que hay son dos valores, el C los compara sin tocar sus bits, y da el resultado (verdadero o falso).
En línea

Abdulai
Moderador Global
Pleno*
*

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 2.235


Ver Perfil
« Respuesta #9 : 29/01/2013, 07:46:39 pm »

....
Además, si Abdulai corre esta última versión del programa con su compilador,
obtendrá VERDADERO.

Si yo lo corro con mi compilador GCC 4.7.1, obtengo VERDADERO.

Pero si le pongo la opción de compilación -std=c99 (que solamente hace que el compilador se ajuste lo más posible al estándar del año 1999 del lenguaje C), me da FALSO.

¿Algún problema con el estándar C99? No, porque el estándar es muy claro con lo que pasa a nivel de bits en los datos numéricos, tanto enteros como de punto flotante.
Es más sólido (o debiera serlo) un programa compilado con la normativa C99.

Entonces, ¿qué anda mal?

Lo compilé con gcc 4.4.1 con la opción -std=c99 y me sigue dando VERDADERO  :BangHead: :BangHead:

¿Cual es tu línea de comando completa?

¿Influye la versión de gcc?  (para no tener que bajar una mas nueva)


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 #10 : 29/01/2013, 07:53:47 pm »

Mi línea de comando es sólo eso: -std=c99.

El número de versión influye, porque sólo en las versiones más nuevas se ha agregado un mejor soporte para el estándar C99.

Pero sospecho que posiblemente en este caso no sea el problema la versión del compilador.

No hace falta bajar nada.
Si me creés que a mí me da "FALSO" es suficiente.

¿De dónde bajaste esa versión vieja?
Yo no tengo versiones viejas de GCC y me gustaría comparar los resultados.

Saludos
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 #11 : 29/01/2013, 08:00:54 pm »

Abdulai:

Ya que te descargaste esa versión, ¿podrías hacerme un pequeño favor?

Me gustaría que me digas qué números te da este programa (con la opción -std=c99):

#include <stdio.h>
#include <float.h>

int main(void) {
   printf("\n\t%i\t%i\n", FLT_EVAL_METHOD, FLT_ROUNDS);
   getchar();
}
En línea

feriva
Pleno*
*****

Karma: +1/-0
Desconectado Desconectado

Sexo: Masculino
España España

Mensajes: 8.360



Ver Perfil
« Respuesta #12 : 29/01/2013, 08:02:46 pm »

No hay ningún flag.

Lo único que hay son dos valores, el C los compara sin tocar sus bits, y da el resultado (verdadero o falso).


 Pues si no es un flag será otra cosa, pero es un problema de precisión de bits; por qué: porque si defino "logn double x" y dejo todo lo demás igual, da Verdadero, porque si cambio el valor 1.1 por 0.1 da falso; luego no tiene que ver con el valor del número en sí, sino con la precisión del float. Ahora, más de ahí... no sé qué puede pasar. Si fuera en ensamblador se vería bien, porque no se compila, lo que escribes es lo que pasa, pero aquí... vete a saber qué ocurre con la versión ésta o la otra. Yo me rindo (que no quiere decir que des la respuesta, porque alguien más puede que quiera intentarlo).

Otra cosa observada es que si uso en el comparado x>1.1F en vez de == me da verdadero, luego la variable ha cambiado el tipo de float, supongo, porque en precisión simple no podría indicar un valor mayor ¿no?

 Saludos.

 
En línea

Abdulai
Moderador Global
Pleno*
*

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 2.235


Ver Perfil
« Respuesta #13 : 29/01/2013, 08:05:11 pm »

La versión 4.4.1 es la que forma parte del CodeBlocks (V10.05)

Los números que me salen son:  2  1
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 #14 : 29/01/2013, 08:32:33 pm »

La versión 4.4.1 es la que forma parte del CodeBlocks (V10.05)

Los números que me salen son:  2  1

A mí me dan los mismos números.

Voy a probar con CodeBlocks a ver qué me da.

Pero la situación que planteo tiene que ver con esos dos números,
y ahora me parece extraño que te siga dando VERDADERO.

Ya te tendría que dar FALSO.

Sin embargo: Es todavía posible que aún usando la misma versión del compilador, tengamos resultados diferentes.
En línea

pierrot
pabloN
Moderador Global
Pleno*
*

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Uruguay Uruguay

Mensajes: 3.348


Ver Perfil
« Respuesta #15 : 29/01/2013, 08:37:39 pm »

Sin embargo: Es todavía posible que aún usando la misma versión del compilador, tengamos resultados diferentes.

 :sorprendido:
En línea

$_="loe  hnachaPkr erttes,urJ";$j=0;for($i=0;s/(.)(.{$j})$//;$i++){$_=$2.$_,$j+=1-$i%2,print$1}print
Abdulai
Moderador Global
Pleno*
*

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 2.235


Ver Perfil
« Respuesta #16 : 29/01/2013, 09:23:51 pm »

Lo compilé por línea de comando, sin Codeblocks.

Incluso ahora uní los dos bloques:

#include <stdio.h>
#include <float.h>

int main(void) {

  float x = 1.1F;

  printf("\n\t%i\t%i\n", FLT_EVAL_METHOD, FLT_ROUNDS);

  printf("\n Es x == 1.1F?    %s \n", (x == 1.1F)? "VERDADERO": "FALSO");
  getchar();
}


y sigue dando verdadero


* Clip1.jpg (26.47 KB - descargado 223 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 #17 : 29/01/2013, 09:24:22 pm »

Me descargué el CodeBlocks, y al compilar con GCC, con la opción -std=c99, me vuelve a dar FALSO.

 :malvado: :malvado:

(Pero la versión de compilador que tengo es 4.7.1 para el GCC que bajé).
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 #18 : 29/01/2013, 09:31:19 pm »

A mí da esto:


* 20130129cbminitest.png (9.3 KB - descargado 210 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 #19 : 29/01/2013, 09:34:46 pm »

Parece claro que la versión del compilador tiene algo que ver con esto.

Y aunque el resultado que obtengo es desagradable,
me parece que la versión 4.7.1 de GCC funciona mejor que la 4.4.1.

En realidad no sé exactamente qué es lo que hace tu compilador 4.4.1.

Sí entiendo lo que hace la versión 4.7.1.

Y a lo mejor el problema no sea la versión del compilador.
Pero no quiero ya preguntarte detalles íntimos de tu sistema...
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!