Friday, September 21, 2007

DN4DP#16: .NET only: Floating-point semantics

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

The previous post covered support for .NET Attributes. This time we will look at platform differences when it comes to floating-point operations.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Floating-point semantics

Native Delphi signals invalid floating-point operations by raising exceptions such as EZeroDivide and EOverflow. These semantics are preserved in Delphi for .NET by default. While the Win32 floating-point exception support is efficiently implemented directly in the processor hardware, the corresponding .NET support must be implemented explicitly in software by emitting the ckfinite IL instruction.

The .NET solution is a little slower, so if you have time-critical code that do not depend on exceptions being raised, you can speed it up a little by using the {$FINITEFLOAT OFF} compiler directive. In this mode, invalid operations will return special floating-point values like NaN (Not a Number), +Inf and -Inf (Infinity) instead of raising exceptions. To get the same semantics in Win32 code, you use the SetExceptionMask function from the Math unit.

{$IFDEF CLR}
{$FINITEFLOAT OFF}
{$ELSE}
Math.SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision]);
{$ENDIF}
One := 0;
Two := 42;
Three := Two / One; // Returns +Inf, no exception raised

In .NET the generated IL code for the division statement is:

FloatingPointSemanticsU.pas.51: Three := Two / One; 
IL_0063: ldloc.1
IL_0064: ldloc.0
IL_0065: div
IL_0066: ckfinite
IL_0067: stloc.2

At run time the ckfinite IL instruction actually expands into seven x86 instructions, including CALLing a routine – doing this for every floating-point operation slows things down noticeably.


Tip: Unless you absolutely need floating-point exceptions, turn them off with {$FINITEFLOAT OFF}

unit FloatingPointSemanticsU;

interface

procedure Test;

implementation

uses
SysUtils, Math;

procedure TestNaNs;
var
One, Two, Three: Double;
begin
Writeln('Floating point exceptions off:');
{$IFDEF CLR}
{$FINITEFLOAT OFF}
{$ELSE}
Math.SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide,
exOverflow, exUnderflow, exPrecision]);
{$ENDIF}
One := 0;
Two := 42;
Three := Two / One; // Returns +Inf, no exception raised
Writeln(Two:1:1 , ' / ', One:1:1, ' = ', Three:1:1);
One := 0;
Two := -42;
Three := Two / One; // Returns -Inf, no exception raised
Writeln(Two:1:1 , ' / ', One:1:1, ' = ', Three:1:1);
One := 0;
Two := 0;
Three := Two / One; // Returns NaN, no exception raised
Writeln(Two:1:1 , ' / ', One:1:1, ' = ', Three:1:1);
end;

procedure TestExceptions;
var
One, Two, Three: Double;
begin
Writeln('Floating point exceptions on :');
{$IFDEF CLR}
{$FINITEFLOAT ON}
{$ELSE}
Math.SetExceptionMask([exDenormalized, exOverflow, exUnderflow, exPrecision]);
{$ENDIF}
One := 0;
Two := 42;
try
Write(Two:1:1 , ' / ', One:1:1, ' = ');
Three := Two / One;
Writeln(Three:1:1);
except
on E:EMathError do
writeln(E.ClassName, ': ', E.Message);
end;
One := 0;
Two := -42;
try
Write(Two:1:1 , ' / ', One:1:1, ' = ');
Three := Two / One;
Writeln(Three:1:1);
except
on E:EMathError do
writeln(E.ClassName, ': ', E.Message);
end;
One := 0;
Two := 0;
try
Write(Two:1:1 , ' / ', One:1:1, ' = ');
Three := Two / One;
Writeln(Three:1:1);
except
on E:EMathError do
writeln(E.ClassName, ': ', E.Message);
end;
end;

procedure Test;
begin
TestNaNs;
writeln;
TestExceptions;
end;

end.

"

No comments:



Copyright © 2004-2007 by Hallvard Vassbotn