3
votes

I am having some serious issues with delphi rounding (when used from a c# project) and consistency of it's results when the value seems to fall within the .5 bracket i.e. is it rounding up or down???

I have created a delphi DLL that performs a number of operations. The crux of one of the operations is (as an example)

double := Round(double1   * double2);

where double1 = 1.9 and double2 = 225 (values obtained from file these are read from)

The result on my calculator says it should be 427.5. The result in the delphi DLL says 427. Rounding down, all good as per delphi documentation for Round()

The values of these calculations are stored against Delphi (classes I believe) which represent forms. In the case above like so

property SRDairy           : Double   read FSRDairy             write FSRDairy;

This property belongs to a bunch of classes and is serialized to the buffer in the Delphi DLL like so (AOvrFarm is the parent that contains all the items to be serialized included my SRDairy one)

procedure GetResultString(AOvrFarm: TdataV6; outputStringBuffer: PChar; var
    bufLen: Integer);
var
  MyStrings : TStringList;
  resultString   : string;
begin
  MyStrings := TStringList.Create;
  try
    AOvrFarm.SavetoStrings(MyStrings);
    resultString := MyStrings.Text;

    if outputStringBuffer = nil then
    begin
      bufLen := Length(resultString) + 1;
    end
    else
    begin
      StrLCopy(outputStringBuffer, PChar(resultString), bufLen - 1);
    end;
  finally
    MyStrings.Free;
  end;
end;

There are a-lot of other properties, forms (classes) etc that are done in this DLL. Upon the end, this file is serialised to a string and that string is returned to the caller (c#) app and the refoutputStringBuffer is filled using something like (in delphi):

  StrLCopy(outputStringBuffer, PChar(resultString), bufLen - 1);

My issue is when I'm calling this DLL from a c# application. The result I'm getting is not 427, it's 428. Rounded up!!!!

The Delphi signature:

function ConvertString(fileContents: PChar; fileExt: PChar; var outputStringBuffer: PChar; var outputStringBufferSize: Integer; var errorMsgBuffer: PChar; var errorMsgBufferSize: Integer): WordBool; stdcall; export;

I use the DLL from my c# like so:

[DllImport("OvrFileImport.dll",
               CallingConvention = CallingConvention.StdCall,
               CharSet = CharSet.Ansi)]

public static extern bool
        ConvertString(
        string fileContents, 
        string fileExt, 
        ref string refputStringBuffer,
        ref int outputStringBufferSize, 
        ref string errorMsgBuffer, 
        ref int errorMsgBufferSize);

The convert string is called once to get the buffer size and then again to fill the buffer. It's on the second call that I notice the difference. When using the same DLL from a Test Delphi project I get 427 (rather than the c# 428).

I've tried building my c# against Any CPU and just x86 incase it was some sort of cpu 64bit issue as my PC is a 64bit machine.

Has anyone ever come across this kind of thing, and if so did they have any way around it?

EDIT - ANSWER

As mentioned by David Heffernan the control word used when calling the DLL from c# was different to that used when calling from another Delphi application. The control word used when calling from c# was in fact $639.

Unfortunately, setting the control word to $1372 caused issues in my c# app while debugging namely it caused the local's watch window to show "unable to evaluate due to stack overflow state" exceptions for all variables. I found an article - strange floating point results which talks about this and hence changed it to be $133F instead (which resolved both the floating point differences and the c# debugging issues).

Code being:

begin
     cw := Get8087CW;
     Set8087CW($133F);
     ....
     finally
        Set8087CW(cw);    
1
Where is the rounded value observed at? (Is it one of the out parameters above?) C# will "truncate" as well when casting to an integer type...user166390
@pst It is in the resultant refputStringBuffer. I'll clarify a bit more in questiondreza
Also, have the original Delphi method signature handy? And, in the Delphi code, is it truncating or rounding? (If the Delphi code could also be provided...)user166390
@pst The Delphi code is using the Math Round() method which I understand implements Bankers rounding?dreza

1 Answers

2
votes

1.9 is not exactly representable in floating point, whereas 225 is exactly representable.

It's not clear where the input numbers are coming from, but it is simply impossible with IEEE754 arithmetic to perform the product 1.9*225 exactly. The behaviour you see has nothing to do with rounding and is in fact all about representability. If you need to perform this calculation exactly then you need to use decimal arithmetic rather than floating point. That would mean using the currency type in Delphi or decimal in C#.

The source of your differing behaviour is presumably down to the 8087 control word that controls the floating point register. When your DLL executes, called from C#, the control word will differ from the default Delphi setting. Call Set8087CW($1372) at the entry point to your DLL to use the default Delphi control word. Remember to restore it before you return from the DLL.