Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   OOP (https://www.clubdelphi.com/foros/forumdisplay.php?f=5)
-   -   Problema con redondeos en doubles (https://www.clubdelphi.com/foros/showthread.php?t=78277)

briast 04-04-2012 11:14:40

Problema con redondeos en doubles
 
Hola. Tengo un problema con los redondeos cuando la variable es de tipo double. Cuando es extended va bien. El problema se reproduce tanto en Delphi 5 como en Delphi 2010, haciendo la misma prueba en ambos.
No sé de donde viene el problema, pero me gustaría determinar si lo mejor es usar siempre extended. Este problema se agudiza cuando tienes dos doubles y comparas, diciéndote que son diferentes cuando en realidad deberían ser iguales.
Os pongo el código fuente para que lo veáis:

a) Ejemplo de rendondeo por pantalla en un bucle. Tengo en pantalla un botón y dos campos memo.
Código:

procedure TForm1.Button1Click(Sender: TObject);
var pp,pp2:Double;
    kk,kk2:Extended;
    j:smallint;
begin
    { case RadioGroup1.ItemIndex of
    0:Math.SetRoundMode(rmNearest);
    1:Math.SetRoundMode(rmUp);
    2:Math.SetRoundMode(rmDown);
    3:Math.SetRoundMode(rmTruncate);
    end;  }

    memo1.Lines.Clear;
    memo2.lines.clear;
    pp:=0.005;
    kk:=0.005;

    pp2:=-0.005;
    kk2:=-0.005;

    for j := 1 to 100 do
      begin
          pp:=pp+0.01;
          kk:=kk+0.01;
          pp2:=pp2-0.01;
          kk2:=kk2-0.01;

          if formatfloat('0.00',pp)=formatfloat('0.00',kk) then
            memo1.Lines.Add(floattostr(pp)+' | '+formatfloat('0.00',pp)+' | '+formatfloat('0.00',kk)+' | '+floattostr(kk))
          else memo1.Lines.Add(floattostr(pp)+' | '+formatfloat('0.00',pp)+' | '+formatfloat('0.00',kk)+' | '+floattostr(kk)+' <--- error');

          if formatfloat('0.00',pp2)=formatfloat('0.00',kk2) then
            memo2.Lines.Add(floattostr(pp2)+' | '+formatfloat('0.00',pp2)+' | '+formatfloat('0.00',kk2)+' | '+floattostr(kk2))
          else memo2.Lines.Add(floattostr(pp2)+' | '+formatfloat('0.00',pp2)+' | '+formatfloat('0.00',kk2)+' | '+floattostr(kk2)+' <--- error');

      end;
end;

El texto entre comentarios para el radiogroup es para cambiar el método de redondeo en delphi 2010 (unit math), pero el resultado es el mismo (excepto con rmDown). La cuestión es que si se ejecuta sale algo como lo siguiente:
Código:

MEMO1:
0,015 | 0,01 | 0,02 | 0,015 <--- error
0,025 | 0,02 | 0,03 | 0,025 <--- error
0,035 | 0,03 | 0,04 | 0,035 <--- error
0,045 | 0,04 | 0,05 | 0,045 <--- error
0,055 | 0,06 | 0,06 | 0,055
0,065 | 0,07 | 0,07 | 0,065
0,075 | 0,07 | 0,08 | 0,075 <--- error
0,085 | 0,08 | 0,09 | 0,085 <--- error
0,095 | 0,09 | 0,10 | 0,095 <--- error
0,105 | 0,10 | 0,11 | 0,105 <--- error
0,115 | 0,11 | 0,12 | 0,115 <--- error
0,125 | 0,12 | 0,13 | 0,125 <--- error
0,135 | 0,13 | 0,14 | 0,135 <--- error
0,145 | 0,14 | 0,15 | 0,145 <--- error
0,155 | 0,15 | 0,16 | 0,155 <--- error
0,165 | 0,17 | 0,17 | 0,165
0,175 | 0,18 | 0,18 | 0,175
0,185 | 0,19 | 0,19 | 0,185
0,195 | 0,20 | 0,20 | 0,195
0,205 | 0,21 | 0,21 | 0,205
0,215 | 0,22 | 0,22 | 0,215
0,225 | 0,23 | 0,23 | 0,225
0,235 | 0,24 | 0,24 | 0,235
0,245 | 0,25 | 0,25 | 0,245
0,255 | 0,26 | 0,26 | 0,255
0,265 | 0,27 | 0,27 | 0,265
0,275 | 0,28 | 0,28 | 0,275
0,285 | 0,29 | 0,29 | 0,285
0,295 | 0,30 | 0,30 | 0,295
0,305 | 0,31 | 0,31 | 0,305
0,315 | 0,32 | 0,32 | 0,315
0,325 | 0,33 | 0,33 | 0,325
0,335 | 0,34 | 0,34 | 0,335
0,345 | 0,35 | 0,35 | 0,345
0,355 | 0,36 | 0,36 | 0,355
0,365 | 0,37 | 0,37 | 0,365
0,375 | 0,38 | 0,38 | 0,375
0,385 | 0,39 | 0,39 | 0,385
0,395 | 0,40 | 0,40 | 0,395
0,405 | 0,41 | 0,41 | 0,405
0,415 | 0,42 | 0,42 | 0,415
0,425 | 0,43 | 0,43 | 0,425
0,435 | 0,44 | 0,44 | 0,435
0,445 | 0,45 | 0,45 | 0,445
0,455 | 0,46 | 0,46 | 0,455
0,465 | 0,47 | 0,47 | 0,465
0,475 | 0,48 | 0,48 | 0,475
0,485 | 0,49 | 0,49 | 0,485
0,495 | 0,50 | 0,50 | 0,495
0,505 | 0,51 | 0,51 | 0,505
0,515 | 0,52 | 0,52 | 0,515
0,525 | 0,53 | 0,53 | 0,525
0,535 | 0,54 | 0,54 | 0,535
0,545 | 0,55 | 0,55 | 0,545
0,555 | 0,56 | 0,56 | 0,555
0,565 | 0,57 | 0,57 | 0,565
0,575 | 0,58 | 0,58 | 0,575
0,585 | 0,59 | 0,59 | 0,585
0,595 | 0,60 | 0,60 | 0,595
0,605 | 0,61 | 0,61 | 0,605
0,615 | 0,62 | 0,62 | 0,615
0,625 | 0,63 | 0,63 | 0,625
0,635 | 0,64 | 0,64 | 0,635
0,645 | 0,65 | 0,65 | 0,645
0,655 | 0,66 | 0,66 | 0,655
0,665 | 0,67 | 0,67 | 0,665
0,675 | 0,68 | 0,68 | 0,675
0,685 | 0,69 | 0,69 | 0,685
0,695 | 0,70 | 0,70 | 0,695
0,705 | 0,71 | 0,71 | 0,705
0,715 | 0,72 | 0,72 | 0,715
0,725 | 0,73 | 0,73 | 0,725
0,735 | 0,74 | 0,74 | 0,735
0,745 | 0,75 | 0,75 | 0,745
0,755 | 0,76 | 0,76 | 0,755
0,765 | 0,77 | 0,77 | 0,765
0,775 | 0,78 | 0,78 | 0,775
0,785 | 0,79 | 0,79 | 0,785
0,795 | 0,80 | 0,80 | 0,795
0,805 | 0,81 | 0,81 | 0,805
0,815000000000001 | 0,82 | 0,82 | 0,815
0,825000000000001 | 0,83 | 0,83 | 0,825
0,835000000000001 | 0,84 | 0,84 | 0,835
0,845000000000001 | 0,85 | 0,85 | 0,845
0,855000000000001 | 0,86 | 0,86 | 0,855
0,865000000000001 | 0,87 | 0,87 | 0,865
0,875000000000001 | 0,88 | 0,88 | 0,875
0,885000000000001 | 0,89 | 0,89 | 0,885
0,895000000000001 | 0,90 | 0,90 | 0,895
0,905000000000001 | 0,91 | 0,91 | 0,905
0,915000000000001 | 0,92 | 0,92 | 0,915
0,925000000000001 | 0,93 | 0,92 | 0,925 <--- error
0,935000000000001 | 0,94 | 0,93 | 0,935 <--- error
0,945000000000001 | 0,95 | 0,94 | 0,945 <--- error
0,955000000000001 | 0,96 | 0,95 | 0,955 <--- error
0,965000000000001 | 0,97 | 0,96 | 0,965 <--- error
0,975000000000001 | 0,98 | 0,97 | 0,975 <--- error
0,985000000000001 | 0,99 | 0,98 | 0,985 <--- error
0,995000000000001 | 1,00 | 0,99 | 0,995 <--- error
1,005 | 1,01 | 1,01 | 1,005

Como véis hay diferencias entre redondear a dos decimales un double y un extended. Pero lo más extraño de todo es que 0,805 + 0,01 da 0,8150000000001. Aquí empieza otro problema. En el memo2 tenemos:
Código:

MEMO2:
-0,015 | -0,01 | -0,02 | -0,015 <--- error
-0,025 | -0,02 | -0,03 | -0,025 <--- error
-0,035 | -0,03 | -0,04 | -0,035 <--- error
-0,045 | -0,04 | -0,05 | -0,045 <--- error
-0,055 | -0,06 | -0,06 | -0,055
-0,065 | -0,07 | -0,07 | -0,065
-0,075 | -0,07 | -0,08 | -0,075 <--- error
-0,085 | -0,08 | -0,09 | -0,085 <--- error
-0,095 | -0,09 | -0,10 | -0,095 <--- error
-0,105 | -0,10 | -0,11 | -0,105 <--- error
-0,115 | -0,11 | -0,12 | -0,115 <--- error
-0,125 | -0,12 | -0,13 | -0,125 <--- error
-0,135 | -0,13 | -0,14 | -0,135 <--- error
-0,145 | -0,14 | -0,15 | -0,145 <--- error
-0,155 | -0,15 | -0,16 | -0,155 <--- error
-0,165 | -0,17 | -0,17 | -0,165
-0,175 | -0,18 | -0,18 | -0,175
-0,185 | -0,19 | -0,19 | -0,185
-0,195 | -0,20 | -0,20 | -0,195
-0,205 | -0,21 | -0,21 | -0,205
-0,215 | -0,22 | -0,22 | -0,215
-0,225 | -0,23 | -0,23 | -0,225
-0,235 | -0,24 | -0,24 | -0,235
-0,245 | -0,25 | -0,25 | -0,245
-0,255 | -0,26 | -0,26 | -0,255
-0,265 | -0,27 | -0,27 | -0,265
-0,275 | -0,28 | -0,28 | -0,275
-0,285 | -0,29 | -0,29 | -0,285
-0,295 | -0,30 | -0,30 | -0,295
-0,305 | -0,31 | -0,31 | -0,305
-0,315 | -0,32 | -0,32 | -0,315
-0,325 | -0,33 | -0,33 | -0,325
-0,335 | -0,34 | -0,34 | -0,335
-0,345 | -0,35 | -0,35 | -0,345
-0,355 | -0,36 | -0,36 | -0,355
-0,365 | -0,37 | -0,37 | -0,365
-0,375 | -0,38 | -0,38 | -0,375
-0,385 | -0,39 | -0,39 | -0,385
-0,395 | -0,40 | -0,40 | -0,395
-0,405 | -0,41 | -0,41 | -0,405
-0,415 | -0,42 | -0,42 | -0,415
-0,425 | -0,43 | -0,43 | -0,425
-0,435 | -0,44 | -0,44 | -0,435
-0,445 | -0,45 | -0,45 | -0,445
-0,455 | -0,46 | -0,46 | -0,455
-0,465 | -0,47 | -0,47 | -0,465
-0,475 | -0,48 | -0,48 | -0,475
-0,485 | -0,49 | -0,49 | -0,485
-0,495 | -0,50 | -0,50 | -0,495
-0,505 | -0,51 | -0,51 | -0,505
-0,515 | -0,52 | -0,52 | -0,515
-0,525 | -0,53 | -0,53 | -0,525
-0,535 | -0,54 | -0,54 | -0,535
-0,545 | -0,55 | -0,55 | -0,545
-0,555 | -0,56 | -0,56 | -0,555
-0,565 | -0,57 | -0,57 | -0,565
-0,575 | -0,58 | -0,58 | -0,575
-0,585 | -0,59 | -0,59 | -0,585
-0,595 | -0,60 | -0,60 | -0,595
-0,605 | -0,61 | -0,61 | -0,605
-0,615 | -0,62 | -0,62 | -0,615
-0,625 | -0,63 | -0,63 | -0,625
-0,635 | -0,64 | -0,64 | -0,635
-0,645 | -0,65 | -0,65 | -0,645
-0,655 | -0,66 | -0,66 | -0,655
-0,665 | -0,67 | -0,67 | -0,665
-0,675 | -0,68 | -0,68 | -0,675
-0,685 | -0,69 | -0,69 | -0,685
-0,695 | -0,70 | -0,70 | -0,695
-0,705 | -0,71 | -0,71 | -0,705
-0,715 | -0,72 | -0,72 | -0,715
-0,725 | -0,73 | -0,73 | -0,725
-0,735 | -0,74 | -0,74 | -0,735
-0,745 | -0,75 | -0,75 | -0,745
-0,755 | -0,76 | -0,76 | -0,755
-0,765 | -0,77 | -0,77 | -0,765
-0,775 | -0,78 | -0,78 | -0,775
-0,785 | -0,79 | -0,79 | -0,785
-0,795 | -0,80 | -0,80 | -0,795
-0,805 | -0,81 | -0,81 | -0,805
-0,815000000000001 | -0,82 | -0,82 | -0,815
-0,825000000000001 | -0,83 | -0,83 | -0,825
-0,835000000000001 | -0,84 | -0,84 | -0,835
-0,845000000000001 | -0,85 | -0,85 | -0,845
-0,855000000000001 | -0,86 | -0,86 | -0,855
-0,865000000000001 | -0,87 | -0,87 | -0,865
-0,875000000000001 | -0,88 | -0,88 | -0,875
-0,885000000000001 | -0,89 | -0,89 | -0,885
-0,895000000000001 | -0,90 | -0,90 | -0,895
-0,905000000000001 | -0,91 | -0,91 | -0,905
-0,915000000000001 | -0,92 | -0,92 | -0,915
-0,925000000000001 | -0,93 | -0,92 | -0,925 <--- error
-0,935000000000001 | -0,94 | -0,93 | -0,935 <--- error
-0,945000000000001 | -0,95 | -0,94 | -0,945 <--- error
-0,955000000000001 | -0,96 | -0,95 | -0,955 <--- error
-0,965000000000001 | -0,97 | -0,96 | -0,965 <--- error
-0,975000000000001 | -0,98 | -0,97 | -0,975 <--- error
-0,985000000000001 | -0,99 | -0,98 | -0,985 <--- error
-0,995000000000001 | -1,00 | -0,99 | -0,995 <--- error
-1,005 | -1,01 | -1,01 | -1,005

Con lo cual pasa lo mismo con los negativos.

Pero ahora, visto lo visto, pongo otro botón en la pantalla y un label y asocio al botón el código siguiente:
Código:

procedure TForm1.Button2Click(Sender: TObject);
var pp,kk:double;
begin
    pp:=0.805;
    kk:=0.815;
    pp:=pp+0.01;
    if pp=kk then label1.caption:='iguales'
    else label1.caption:='distintos: ' + FloatToStr(pp)+ ' <> ' + floattostr(kk);
end;

Si pulsas el botón, en lugar de salir en el label el texto "iguales", aparece el texto: distintos 0,815<>0,815
:eek: :confused:

Algo estoy haciendo mal ... esto no puede ser así ...

ecfisa 04-04-2012 14:25:33

Hola briast.

Revisá este artículo

Saludos.

Delphius 04-04-2012 15:05:54

Yo recomendaría la lectura del artículo What Every Computer Scientist Should Know About Floating-Point Arithmetic.
Podrá ser bastante denso y aburrido, sobre todo sabiendo que está en inglés y a algunos no nos fascina ese idioma, pero señala y aborda muchas cuestiones técnicas sobre la aritemética de punto flotante.
Más en puntual sobre la comparación de números de punto flotante el artículo Comparing Floating Point Number aclara muchas cosas. He colocado el enlace a la versión actualizada (a este año).

Ya con eso te puedes hacer una idea de porque es una muy mala idea hacer comparaciones tan directas como lo hacemos de toda la vida con los números enteros ;)

Saludos,

briast 05-04-2012 11:10:16

Ok. Gracias por la información.
Utilizaré la función para comparar valores disponible en la unit math y ya está.


La franja horaria es GMT +2. Ahora son las 23:51:56.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi