Question

In the following code, the last two calls to Ceil give unexpected result. Could you help to comment on the reason?

Furthermore, if the error (or deviation) is random, could I get the expected value?

Ceil(Calculated_Var_Value) = 7 when Calculated_Var_Value = 7.0000000000.

Many thanks!

    procedure TForm2.FormCreate(Sender: TObject);
    var
      A, B, C: Extended;
      Val: Extended;
    begin
      ShowMessage(FloatToStr((1.8 - 2.5) / -0.1));
      ShowMessage(FloatToStrF((1.8 - 2.5) / -0.1, ffFixed, 20, 20));
      ShowMessage(FloatToStr(Ceil((1.8 - 2.5) / -0.1)));


      Val := (1.8 - 2.5) / -0.1;
      ShowMessage(FloatToStr(Val));
      ShowMessage(FloatToStrF(Val, ffFixed, 20, 20));
      ShowMessage(FloatToStr(Ceil(Val)));


      Val := (1.8 - 2.5) / -0.1;
      ShowMessage(FloatToStr(Val * 100 / 100));
      ShowMessage(FloatToStrF(Val * 100 / 100, ffFixed, 20, 20));
      ShowMessage(FloatToStr(Ceil(Val * 100 / 100)));


      A := 1.8; B := 2.5; C := -0.1;
      Val := (A - B) / C;
      ShowMessage(FloatToStr(Val));
      ShowMessage(FloatToStrF(Val, ffFixed, 20, 20));
      ShowMessage(FloatToStr(Ceil(Val)));


      A := 1.8; B := 2.5; C := -0.1;
      Val := (A - B) / C;
      ShowMessage(FloatToStr(Val * 100 / 100));
      ShowMessage(FloatToStrF(Val * 100 / 100, ffFixed, 20, 20));
      ShowMessage(FloatToStr(Ceil(Val * 100 / 100)));
    end;
Était-ce utile?

La solution

This is just down to the inherent inaccuracy of floating point arithmetic. Two of your values are not exactly representable in binary floating point, 1.8 and -0.1. So, those numbers are approximated by the closest representable values. And that means that it's quite plausible that your equation won't evaluate to exactly 7.

Now consider your two expressions:

Val1 := (1.8 - 2.5) / -0.1;
Val2 := (A - B) / C;

The difference between these two is that Val1 is evaluated at compile time and Val2 is evaluated at runtime. Now, it's down to the compiler how the constant expression (1.8 - 2.5) / -0.1 is evaluated. To the best of my knowledge, it's not documented how that will be evaluated.

However, it is clear that the compiler uses a different evaluation method to evaluate the constant expression from that used at runtime. This program illustrates that:

{$APPTYPE CONSOLE}

uses
  SysUtils, Math;

var
  A, B, C: Extended;
  Val1, Val2: Extended;
begin
  Val1 := (1.8 - 2.5) / -0.1;
  Writeln(Ceil(Val1));

  A := 1.8; B := 2.5; C := -0.1;
  Val2 := (A - B) / C;
  Writeln(Ceil(Val2));

  Writeln(BoolToStr(Val1=7.0, True));
  Writeln(BoolToStr(Val2=7.0, True));
  Writeln(BoolToStr(Val1<Val2, True));

  Readln;
end.

Output:

7
8
True
False
True

So, this shows that Val1 and Val2 have different values, and that Val2 is strictly greater than 7.

The fundamental problem you have is that of representably of floating point values. Because you are using Extended, which uses a binary representation, your decimal input values are not exactly representable. If you want exact arithmetic here, you will need to use a decimal representation.

As always when answering variants of this question, I refer you to the essential reading on the subject: What Every Computer Scientist Should Know About Floating-Point Arithmetic.

Autres conseils

This is a typical rounding error. You can see it when you add this line to your output:

ShowMessage(FloatToStr(Val-7));
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top