The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My work

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 1,822 other followers

Delphi – Michael Justin had strange floating point results when his 8087 FPU Control Word got hosed

Posted by jpluimers on 2009/05/06

Two days ago, Michael Justin (who just released version 1.7 of the Habari Active MQ Client components) posted a blog entry about a strange circumstance when 1.99 would not compare equally to 1.99.
He tracked it down to the 8087 (more formally: Intel FPU) Control Word being hosed on his system.

I could not reproduce his particular case, but since I have seen similar issues in the past, I wrote the DUnit test case below which shows you what can happen by manually setting the 8087 Control Word.

The difference between the 8087 Control Word values $1372 (default) and $1272 (failure) is the internal mantissa precision (see the “Art of Assembly Language” and the Intel FPU Control Word documentation on this).
Edit: Found a much more complete description of the bits in the FPU Control word.

It changes from 64 bits to 53 bits, which is enough to make 1.99 not equal to 1.99.

I have seen behaviour like this in the past with some networking stacks in the Turbo Pascal 7 era, with some C++ DLL’s in the Delphi 1-3 era, and some printer drivers in the Delphi 5-7 era.
Let me know in the comments (or using the contact form) where you have bumped into this.

The code below makes use of the Jcl8087 unit which is part of the JCL (JEDI Code Library) at SourceForge.
Add the unit to any DUnit test project you created and observe the results.

unit TestControlWordUnit;

interface

uses
  TestFramework;

type
  TTestControlWord = class(TTestCase)
  strict protected
    procedure CurrencyCheckEquals;
    procedure ShowCW(const ControlWord: Word);
  published
    procedure TestCurrencyCheckEqualsWith1272CW;
    procedure TestCurrencyCheckEqualsWith1372CW;
    procedure Show1272CW;
    procedure Show1372CW;
  end;

implementation

uses
  Jcl8087, TypInfo;

const
  BadCW = $1272;
  GoodCW = $1372;

procedure TTestControlWord.Show1272CW;
begin
  ShowCW(BadCW);
end;

procedure TTestControlWord.Show1372CW;
begin
  ShowCW(GoodCW);
end;

procedure TTestControlWord.CurrencyCheckEquals;
var
  CurrencyValue: Currency;
begin
  CurrencyValue := 1.99;
  CheckEquals(1.99, CurrencyValue);
end;

procedure TTestControlWord.ShowCW(const ControlWord: Word);
var
  Precision: T8087Precision;
  Line: string;
begin
  Set8087CW(ControlWord);
  Precision := Get8087Precision;
  Line := GetEnumName(TypeInfo(T8087Precision), Ord(Precision));
  Assert(False, Line); // quick hack to show this in DUnit
end;

procedure TTestControlWord.TestCurrencyCheckEqualsWith1272CW;
begin
  // bad: result is not equal
  Set8087CW(BadCW);
  CurrencyCheckEquals;
end;

procedure TTestControlWord.TestCurrencyCheckEqualsWith1372CW;
begin
  // default CW value
  // good: result is not equal
  Set8087CW(GoodCW);
  CurrencyCheckEquals();
end;

initialization
  RegisterTest(TTestControlWord.Suite);
end.

Edit – Guus Creuwels added a comment and wondered what his control words meant, so there is the list:

  $027F - [cweAllowInvalidNumbers, cweAllowDenormals, cweAllowDivideByZero, cweAllowOverflow, cweAllowUnderflow, cweAllowInexactPrecision], icProjective, pcDouble,   rcNearestOrEven
  $1272 - [                        cweAllowDenormals,                                         cweAllowUnderflow, cweAllowInexactPrecision], icAffine,     pcDouble,   rcNearestOrEven
  $133F - [cweAllowInvalidNumbers, cweAllowDenormals, cweAllowDivideByZero, cweAllowOverflow, cweAllowUnderflow, cweAllowInexactPrecision], icAffine,     pcExtended, rcNearestOrEven
  $1372 - [                        cweAllowDenormals,                                         cweAllowUnderflow, cweAllowInexactPrecision], icAffine,     pcExtended, rcNearestOrEven
Default - [                        cweAllowDenormals,                                         cweAllowUnderflow, cweAllowInexactPrecision], icAffine,     pcExtended, rcNearestOrEven

Setting the 8087 FPU Control word to $133F effectively disables all floating point exceptions (the lower 6 bits with mask $3F determine the exception behaviour).

–jeroen

8 Responses to “Delphi – Michael Justin had strange floating point results when his 8087 FPU Control Word got hosed”

  1. […] The question datetime – Delphi Now() function returns a wrong value – Stack Overflow is similar to my article Delphi – Michael Justin had strange floating point results when his 8087 FPU Control Word got hose…. […]

  2. Just ran across this today. Unsure what is setting the value in the code, but a new unit test was breaking all others. I’ve just started retaining the state of the 8087 in the setup and setting back in the teardown. Thanks for the post, was good to hear I wasn’t alone in this.

  3. jpluimers said

    It looks like the .NET ComVisible handling does not like one ore more of these floating point exception bits is turned off:
    [cweAllowInvalidNumbers, cweAllowDivideByZero, cweAllowOverflow]

    When these bits are turned off, and the event that belongs to such a bit happens, the FPU will fire an exception.
    If that exception does not get handled correctly, you will observe strange behaviour.

    Since Delphi can handle these exceptions fine, you only need to change the FPU control word for external code not handling them fine.
    It is wise to restore the FPU control word afterwards.

    The difference between $133F and $027F is that $027F sets up the FPU for doing less precise calculations (limiting to Double in stead of Extended) and different infiniti handling (which was used for older FPU’s, but is not used any more).

    This page describes the bits in detail: http://www.website.masmforum.com/tutorials/fptute/fpuchap1.htm#cword
    I’ve just added a link to that page to the original post as well.

    • Guus Creuwels said

      Thanks again! Maybe this new CrossTalk thing you mentioned will help me in the future so that I do not need to use Com anymore…

  4. Guus Creuwels said

    Hi,

    I (and some other people as well) experienced some wierd issues when calling a .NET assembly with a ComVisible from a Win32 Delphi app.

    See the following links:
    http://www.nldelphi.com/forum/showthread.php?p=235043#post235043
    http://qc.embarcadero.com/wc/qcmain.aspx?d=8399

    The solution was to call “Set8087CW($133F);” or “Set8087CW($027f);”.

    I’m still confused about the values $133F and #027f… I have no clue what the difference is…

    Anyway… it works :-)

    Kind regards,
    Guus.

    • jpluimers said

      Guus,

      Thx. I made an edit to the article so you can see what your Control Word values mean.

      –jeroen

      • Guus Creuwels said

        Thanks. Is there any way you can explain why $133F or $027F would be a better option to use when calling a .NET ComVisible class from a Win32 application? They both work and in our software we currently use $027F…

        Regards,
        Guus

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: