Los floats son mala gente

Started by osk, September 01, 2009, 10:14:39 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

osk

Hola.
Estoy intentando hacer una función que redondee un número decimal dado por parámetro, hasta el nivel deseado (1=décimas, 2=centésimas,etc) pasado como segundo parámetro. Hago el truco de multiplicar el número decimal por la potencia de 10 adecuada, sumarle 0,5 y truncarlo a int para luego volverlo a dividir por la potencia de diez para que se me vuelva a poner el número en su orden de magnitud. No sé si es el mejor método, sinceramente...Pero lo que os quería comentar es otra cosa: si os fijáis en el código que os pongo, puedo ir modificando el número a redondear para arriba o para abajo con los cursores, de centésima en centésima. Pero al hacerlo, en pantalla se me ven hasta seis decimales (¡los cuales son totalmente espúreos!) que también varían cuando se tocan los cursores, cosa que no debería pasar porque sólo estoy modificando hasta el nivel de centésimas, en teoria.¿Qué pasa con estos decimales que salen de la nada?... Otro ejemplo de lo mismo: al mostrar el resultado redondeado, vuelven a salir seis decimales siempre, aunque sólo se tengan definidos realmente dos o tres o los decimales que se quieran: siempre salen seis que han aparecido de la nada, con el agravante que, por ejemplo, si el número redondeado es 45.45, a veces aparece 45.449999 con lo que si sólo se viera hasta las centésimas (que es lo que yo quiero), entonces se vería un resultado incorrecto.
Bueno, no tengo una pregunta fija: solamente quería mostrar el funcionamiento de los floats, que no sé si es que es mejorable o soy yo directamente que hago las cosas mal.
Gracias.
[code language="bennu"]
import "mod_text";
import "mod_key";
import "mod_math"; //Necesario para nuestra función round()

Declare float round(float num_a_redondear, int decimales)
End

process main()
private
  float num_redondeado;
  float num_a_redondear=87.967;
end
begin
  write(0,5,5,0, "Número a redondear:");
  write(0,5,15,0, "Número redondeado:"); 
  write_var(0,150,5,0, num_a_redondear);
  write_var(0,150,15,0, num_redondeado);     
  loop   
    if(key(_up))
      num_a_redondear=num_a_redondear+0.01;
     end   
    if(key(_down))   
      num_a_redondear=num_a_redondear-0.01;
     end
    if(key(_enter)) num_redondeado=round(num_a_redondear,3); end
    if(key(_esc)) break; end
    frame;
  end
end

function float round (float num_a_redondear, int decimales)
private
    int num_truncado;
    float num_redondeado;
end
begin
    num_a_redondear=num_a_redondear*pow(10,decimales);
    if(num_a_redondear>=0)
      num_truncado=num_a_redondear+0.5;               
     end
    if(num_a_redondear<0)
      num_truncado=num_a_redondear-0.5;
    end     
    num_redondeado=num_truncado/pow(10,decimales);
    return num_redondeado;
end
[/code]


SplinterGU

es problemas de la IEEE, no lo vas a poder solucionar de forma facil, la unica opcion es pasarlo a string y cortarlo, o hacer algo para detectar digito a digito e imprimirlo.
Download Lastest BennuGD Release: http://www.bennugd.org/node/2

Drumpi

(Danger, Drumpi divagando)
Recuerdo que en clase nos comentaban que los numeros float no son tan precisos como nos gustaría: todos los datos se guardan como unos y ceros en grupos, y hay que interpretar dicha información. El caso de los decimales es algo bastante complejo (tanto que no los recuerdo bien ^^U) pues hay que guardar la mantisa (los dígitos significativos) y la exponencial (10 elevado a menos...), y la precisión dependía de cuantos bits se guardaban para uno u otro.
El caso es que, tratando con valores binarios y un espacio muy limitado, los FLOAT no tienen una gran precisión cuando hablamos de acertar con el número que queremos representar, pero ofrecen una gran magnitud de números representados (tanto 0.0000004534 como 4357346538.23, por ejemplo).

Supongo que Bennu tiene ese problema igual que lo tienen las SDL y C. El último tutorial que estaba leyendo comentaba ese fallo en los FLOAT, y que se suele huir de ellos, aunque no pasa lo mismo con otro tipo, creo que era DOUBLE FLOAT, que ocupan el doble y no tienen ese problema, pero se utilizan en casos muy específicos.
Hala, como con 1001 procesos sólo va a 9 FPS, vamos a meterle 32 veces más, a ver si revienta.
(Drumpi epic moment)

SplinterGU

de esto ya hable bastante... yo creo que mas que en clases lo leiste aca...
Download Lastest BennuGD Release: http://www.bennugd.org/node/2

osk

Ook.
Gracias por la explicación y la sugerencia.

Windgate

Coño, redondeo...

Ahora en mi visor 3D estoy asignando floats a int directamente para redondear/truncar a unidades.

La desgracia es que en ocasiones unos calculos requieren redondeo y otros calculos truncado, ¿Hay en Bennu funciones especificas para ello o tendria que usar el codigo que comenta osk y/o modificaciones?
Iván García Subero. Programador, profesor de informática, monitor de actividades culturales y presidente de TRINIT Asociación de Informáticos de Zaragoza. http://trinit.es

osk

Bien, siguiendo los consejos de Splinter, aquí tenéis un ejemplo que utiliza una función de redondeo a la que se le puede decir el nivel decimal al que se quiere llegar...Creo que funciona bien, pero me gustaría que la viérais por si he hecho algo mal, soy bastante chapuzas en general. Lo que os puede sorprender es que devuelva string en vez de float, pero en los comentarios del código está explicado por qué.

Venga, hasta luego

[code language="bennu"]
import "mod_text";
import "mod_key";
import "mod_math";   //Necesario para nuestra función round()
import "mod_string"; //Necesario para nuestra función round()
import "mod_regex";  //Necesario para nuestra función round()

Declare string round(float num_a_redondear, int decimales)
End

process main()
private
//  float num_redondeado;
  string num_redondeado;
  float num_a_redondear=87.967;
end
begin
  write(0,5,5,0, "Número a redondear:");
  write(0,5,15,0, "Número redondeado:"); 
  write_var(0,150,5,0, num_a_redondear);
  write_var(0,150,15,0, num_redondeado);     
  loop   
    if(key(_up))
      num_a_redondear=num_a_redondear+0.01;
     end   
    if(key(_down))   
      num_a_redondear=num_a_redondear-0.01;
     end
    if(key(_enter))
   while(key(_enter)) frame; end
   num_redondeado=round(num_a_redondear,3);
    end
    if(key(_esc)) break; end
    frame;
  end
end

function string round (float num_a_redondear, int decimales)
private
    int num_truncado;
    float num_redondeado;
    string cad_redondeada;
    string cadent;
    string caddec;
end
begin
/*Me aprovecho de que en Bennu, si se asigna un valor float a un entero, éste
adquiere el valor truncado de aquél. Por lo que multiplico el número a redondear
por la potencia de 10 necesaria para seguidamente asignarlo a una variable entera,
la cual se quedará sólo con la parte de la izquierda del punto decimal*/
    num_a_redondear=num_a_redondear*pow(10,decimales);
    if(num_a_redondear>=0)
      num_truncado=num_a_redondear+0.5;               
     end
    if(num_a_redondear<0)
      num_truncado=num_a_redondear-0.5;
    end
/*Deshago la multiplicación anterior para volver al orden de magnitud original.
La división de int entre float (pow devuelve float) da float siempre, por lo que
ahora no perdemos ningún decimal en ningún truncamiento*/
    num_redondeado=num_truncado/pow(10,decimales);
/*Esta línea es una chapuza para evitar que en la división anterior num_redondeado
se quede por debajo del número que debería de ser (debido a la poca precisión de los
float) y entonces el redondeo se produzca a la baja. Para evitar esto, se le suma una
pequeña cantidad de un orden de magnitud menor que el límite de redondeo, para que
éste siempre quede mínimamente por encima del valor real de redondeo para cortarlo adecuadamente.
Es más fácil entenderlo si se comenta la línea y se observa qué pasa con says.*/
    num_redondeado=num_redondeado + 5*pow(10,-(decimales+1));

/*Las líneas siguientes son para mostrar solamente el número de decimales deseado. El resto
de decimales que se obvian son totalmente aleatorios y espúreos y no sirven para nada.*/
    //Convierto el float en cadena para poderla cortar 
    cad_redondeada=ftoa(num_redondeado);
    //Primero obtengo la parte entera del número
    cadent=substr(cad_redondeada,0,regex("\.",cad_redondeada));
    //Ahora la decimal, pero sólo hasta donde se ha redondeado
    caddec=substr(cad_redondeada,regex("\.",cad_redondeada),decimales+1);
    //Y lo junto
    cad_redondeada=cadent+caddec;
    return cad_redondeada;
/*En vez de devolver una cadena, lo lógico sería que la función devolviera el
número float ya redondeado, pero si volvemos a hacer la conversión de cadena en
float, resulta que vuelven a aparecer los decimales espúreos más allá de donde
hemos redondeado, hasta completar un máximo de 6 decimales, los últimos sin
sentido. Para observar este efecto, se puede comentar la línea return anterior
y descomentar las dos siguientes.*/
//    num_redondeado=atof(cad_redondeada);
//    return num_redondeado;
end
[/code]

P.D: Trabajar con floats es un tremendo rollo. A evitar en lo posible!

Windgate

Mmmmh, por mi parte había hecho una FUNCTION para redondeo de float a int, es decir, sin decimales ni nada:

FUNCTION int round ( float f )
//Redondea a las unidades cualquier valor real, probado y funciona con positivos y negativos
PRIVATE
int n;
float g;
BEGIN
g = abs ( f );
n = g;
g = g - n;
IF ( g >= 0.5 )
IF ( f >= 0 )
RETURN f + 1;
ELSE
RETURN f - 1;
END
ELSE
RETURN f;
END
END


¿Este tipo de funciones no deberían meterse en el mod_math.dll?

No me refiero a los códigos expuestos aquí porque seguramente los habrá más eficientes, pero me parecen funciones útiles para venir de serie con el modulito matemático.

Y ahora que lo recuerdo la entrada de string, int y float desde teclado también las veo candidatas a meterlas en una de las DLL...

Mucho pedir, ¿Qué opinas al respecto Splinter?
Iván García Subero. Programador, profesor de informática, monitor de actividades culturales y presidente de TRINIT Asociación de Informáticos de Zaragoza. http://trinit.es

SplinterGU

el problema es la representacion IEEE, no siempre se podra evitar el .*9999999
Download Lastest BennuGD Release: http://www.bennugd.org/node/2

panreyes

Este es un hilo de hace 9 años y pensaréis que vengo a espamear sobre viagra, pero no! xD
Esta es mi versión, que simplemente devuelve una string con un número concreto de decimales.

function string my_round(float num_a_redondear, int decimales)
private
string temp_string;
int dot_pos;
begin
temp_string=""+num_a_redondear;
dot_pos=find(temp_string,".");
temp_string=substr(temp_string,0,dot_pos+decimales+1);
return temp_string;
end

Futu-block

Muy bien, Karma++, los floats es algo que no he tocado y me tocará tocar, vaya tela ;)