Search code examples
delphifloating-pointcross-platformfloating-accuracyconditional-compilation

How can I do conditional compilation branching based on the real type used?


I was doing a solution for this question with respect to underlying hardware and platform.

Due Delphi's code generator specifics, it produces 80x87 instructions for IA32 platform and SSE instructions for AMD64 platform, thus having generic Real type defined as Extended or Double depending on the target.

The algorithm specifics requires some attention to be paid to the base type, because machine epsilon differs for Double and Extended types. The only thing I figured out is branching on the size of function argument:

type
  Real = Extended{|Double|Single};

function SqrtHeron(a: Real): Real;
var
  x0: Real;
  x1: Real;
const
  FuzzFactor = 1000;
  {$IF SizeOf(a) = SizeOf(Extended)}
  Epsilon = 1E-19 * FuzzFactor;
  {$ELSE}{$IF SizeOf(a) = SizeOf(Double)}
  Epsilon = 1E-15 * FuzzFactor;
  {$ELSE}{$IF SizeOf(a) = SizeOf(Single)}
  Epsilon = 1E-7 * FuzzFactor;
  {$IFEND}{$IFEND}{$IFEND}
var
  n: Integer;
begin
  { ... }

How can I implement this branching in the better way? And I definitely want to avoid deceptively pretending Double machine epsilon for Extended machine epsilon (citing a clumsy patch to Math.pas):

const
  FuzzFactor = 1000;
  SingleResolution   = 1E-7 * FuzzFactor;
  DoubleResolution   = 1E-15 * FuzzFactor;
{$IFDEF CPUX64}
  ExtendedResolution = DoubleResolution;
{$ELSE !CPUX64}
  ExtendedResolution = 1E-19 * FuzzFactor;
{$ENDIF}

Solution

  • First, your {$IF} statements can be cleaned up using {$ELSEIF} instead of {$ELSE}{$IF}.

    Second, Extended and Double are the same thing on Win64, unless you enable {$EXTENDEDCOMPATIBILITY ON}, so your first {$IF} would end up using the wrong value. Try swapping them to account for that. I would even go as far as checking for SizeOf(Single) first, moving up in increasing byte size rather than down in decreasing size:

    const
      FuzzFactor = 1000;
      {$IF SizeOf(Real) = SizeOf(Single)}
      Epsilon = 1E-7 * FuzzFactor;
      {$ELSEIF SizeOf(Real) = SizeOf(Double)}
      Epsilon = 1E-15 * FuzzFactor;
      {$ELSEIF (SizeOf(Extended) > SizeOf(Double)) and (SizeOf(Real) = SizeOf(Extended))}
      Epsilon = 1E-19 * FuzzFactor;
      {$IFEND}
    

    If you need 80-bit precision on Win64, use TExtended80Rec or TExtendedHelper instead.