Anonymous methods in unit testing

0

Anonymous methods (aka Closures) are rare visitors in Delphi code. I have upgraded to Delphi 2009 a year before and never used the anonimous methods. But last week it finally happened.
I was writing the unit tests for fast fourier transform code. The idea of some tests was to compare the results of fast fourier trasform and discrete fourier transform of arbitrary function, and it appeared that the anonymous method is the best way to pass the function to the test.

type
  TRealFunc = reference to function(N: Integer): Extended;

type
  TTestFFT = class(TTestCase)
  private
    procedure TestRealFunc(Func: TRealFunc; Count: Integer);
  published
    procedure TestRealFFT00;
    ...
  end;

All checks are done within TestRealFunc procedure. It receives a function to transform as an argument, makes fourier transform of the function using two different methods and compares the results:

procedure TTestFFT.TestRealFunc(Func: TRealFunc; Count: Integer);
var
  FFTData: array of Extended;
  DFTData: array of TksComplex;
  M, N: Integer;
  Arg: TksComplex;

begin
  SetLength(FFTData, Count);
  SetLength(DFTData, Count);
  for M:= 0 to Count - 1 do begin
    FFTData[M]:= Func(M);

// discrete Fourier transform;
    DFTData[M]:= 0;
    for N:= 0 to Count - 1 do begin
      Arg.Re:= 0;
      Arg.Im:= (2 * Pi * N * M) / Count;
      DFTData[M]:= DFTData[M] + Func(N) * TksComplex.Exp(Arg);
    end;
  end;

// fast Fourier transform
  RealFFT(@FFTData[0], Count, False);

  CheckEquals(FFTData[0], DFTData[0].Re, 1E-15);
  CheckEquals(FFTData[1], DFTData[Count shr 1 - 1].Re, 1E-15);
  CheckEquals(0, DFTData[0].Im, 1E-15);
  CheckEquals(0, DFTData[Count shr 1 - 1].Im, 1E-15);
  for N:= 1 to Count shr 1 - 1 do begin
    CheckEquals(FFTData[2 * N], DFTData[N].Re, 1E-15);
    CheckEquals(FFTData[2 * N + 1], DFTData[N].Im, 1E-15);
    CheckEquals(FFTData[2 * N], DFTData[Count - N].Re, 1E-15);
    CheckEquals(FFTData[2 * N + 1], - DFTData[Count - N].Im, 1E-15);
  end;
end;

The test procedures themselves just calls the TestRealFunc with different arguments. For example,

procedure TTestFFT.TestRealFFT00;
begin
  TestRealFunc(
    function(N: Integer): Extended
    begin
      Result:= Sin(pi / 4 * N) + 0.5 * Sin(pi / 2 * N + 3 * pi / 4);
    end,
    8
  );
end;

Though the syntax looks cumbersome, the solution based on anonimous methods is very logical.

Advertisements

How to uppecase [lowercase] strings in Delphi 2009+ ?

0

The answer is not so obvious as it was before.

In the time immemorial strings were ASCII strings, and since then we have UpperCase function. The function is  just replaces the lowercase ‘a’..’z’ characters by their uppercase ‘A’..’Z’ versions. Though nowadays the UpperCase is a Unicode function it still ‘uppercases’ only ASCII characters. That is good but that is not all uppercase functionality we need.

The early Delphi versions introduced ANSI strings. The uppercase conversion on ANSI strings includes local characters besides ASCII ‘a’..’z’ and consequntly is locale-dependent. The only locale supported by pre-unicode Delphi versions is system locale. The ANSIUpperCase function was introduced to uppercase ANSI strings using system locale. That is not so good as it may be, since sometimes we need a locale different from system locale, but at least is clear and unambiguous.

Now in Delphi2009 we can explicitly define the ANSI locale in Ansistring type, and that is great. The ANSI uppercase conversion is locale-sensitive, and now we can perform ANSI string uppercase conversions on any locale by just calling AnsiUpperCase function, right? The answer is – NO.

First, in Delphi 2009 the AnsiUpperCase is unicode function. I understand the [compatibility] reasons why the unicode function has Ansi prefix, but I prefer it would never happen.

Second, the real ANSI version of AnsiUpperCase function defined in Ansistrings unit is a wrapper for WinAPI CharUpperBuffA function, and still uses the current system locale and ignores the locale associated with the string type.

As a result it is better to avoid using AnsiUpperCase at all. The AnsiUpperCase function is just a backward compatibility issue.

Nowadays the defaul string type is Unicode string. The unicode string can include characters of all different locales at once, so the unicode strings are again locale-indendent as ASCII strings were, right? Well, it is almost right. But to be absolutely correct, the answer is still NO.

The only problem I know that makes Unicode uppercase conversion locale-dependent is the case of dotless i (ı, $0131) and dotted I (İ, $0130). In most languages the upper of i ($69) is I ($49), but in turkish locale i ($69) maps to İ ($0130). Similarly in turkish the lower of I ($49) is ı ($0131).

Now let us go back to Delphi. The new string format containes no locale information for unicode strings. We have 2 functions to make the unicode uppercase conversion – the already mentioned SysUtils.AnsiUpperCase introduced for backward compatibility and new ToUpper function defined in Character unit. The SysUtils.AnsiUpperCase is a wrapper for WinAPI CharUpperBuffW function and ignores locale-specific issues. The CharUpperBuffW works much better than its ANSI analog CharUpperBuffA, but still can’t help with “turkish case”. The Character.ToUpper function is a wrapper for LCMapString function and is locale-dependent, but the locale parameter is set to system locale. So if you need “turkish uppercase” on the system with different locale (or want to make the result of uppercase independent from system locale) you must write your own LCMapString wrapper.

BTW: the early Delphi 2009 releases containes side-effect bug in ToUpper and ToLower implementations. The bug was fixed in Update 3 (build 12.0.3420.21218).

ksTools 0.20

0

The second pre-release (ver 0.20) of ksTools library containes documentation in .chm format and many TksComPort enhancements. The most useful enhancement is the second TksComPort.Read method overload with new (Timeout) parameter, which simplifies the serial communications with external device using “Request – Answer” scheme. The end of the device answer now can be defined in OnReadStop event handler.

For example, some devices answer by ASCII string of arbitrary length ending with LF character. With the correspondent OnReadStop event handler Read returnes immediately after finding LF in the input:

procedure TForm1.ksComPort1ReadStop(Sender: TObject; Value: Byte;
var Stop: Boolean);
begin
if Value = $0A { LF } then Stop:= True;
end;

ksTools Project

0

I created my first project on Google Code Project. The ksTools is a general purpose Delphi library. The initial checkin containes the TksComPort component for serial communication and the Terminal app to test the component. Both are for Delphi 2009 only. I am planning to add Delphi 2007 support and write some documentation later.

More details about the ksTools library are on the project page https://sergworks.wordpress.com/kstools