The Wiert Corner – irregular stream of stuff

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

  • 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 2,572 other followers

Archive for the ‘Delphi’ Category

Delphi: workaround doing math with generic types preventing “E2015 Operator not applicable to this operand type” with TValue (as there is no way to constraint the generic type to be floating point or ordinal)

Posted by jpluimers on 2021/12/14

A while ago on Facebook (it’s a private group, so you cannot see the posts unless you both have a Facebook account and are member of the group), [Archive.is] Niels Tjørnhøj-Thomsen (coming from a C++ templates background) asked why the below method would throw a E2015 Operator not applicable to this operand type in the complex expression:

function TAxis<t>.Calc(const AScalar: T): single;
begin
  Result := fStart + ( ( ( AScalar - fMin ) / fRange ) * fExtent );
end;

The type itself was very simple:

TAxis<T> = record
  fMin, fMax, fRange: T;
  fStart, fEnd, fExtent: single;
  function Calc( const AScalar: T ): single;
end;

He used these small example specialisations that put me on the wrong foot, as the order was TDateTime followed by single:

var
  rXAxis: TAxis<TDateTime>;
  rYAxis: TAxis<single>;

So at first I thought this might be caused by TDateTime to be defined in the System unit as a typed type:

type
  TDateTime = type Double;

It wasn’t.

Splitting the code in 4 lines with assignments of single expression operations would make the error appear in all expressions.

Casting parts of the expression to simple would not help either.

A small test program [Archive.is] might put you, like me, on the wrong foot because the specialisation is in the same source file as the generic type:

program DelphiMathAndGenerics;

type
  TAxis<T> = record
    fMin, fMax, fRange: T;
    fStart, fEnd, fExtent: single;
    function CalcCasted( const AScalar: T ): single;
    function CalcPlain( const AScalar: T ): single;
  end;

function TAxis<T>.CalcCasted(const AScalar: T): single;
var
  Offset: single;
  NormalisedOffset: single;
  ScaledOffset: single;
begin
  // First 2 lines give the same error: E2089 Invalid typecast
  Offset := single(AScalar) - fMin;
  NormalisedOffset := Offset / single(fRange);
  ScaledOffset := NormalisedOffset * fExtent;
  Result := fStart + ScaledOffset;
end;

function TAxis<T>.CalcPlain(const AScalar: T): single;
var
  Offset: T;
  NormalisedOffset: T;
  ScaledOffset: T;
begin
  // All 4 lines give the same error: E2015 Operator not applicable to this operand type
  Offset := AScalar - fMin;
  NormalisedOffset := Offset / fRange;
  ScaledOffset := NormalisedOffset * fExtent;
  Result := fStart + ScaledOffset;
end;

var
  rXAxis: TAxis<TDateTime>;
  rYAxis: TAxis<single>;

begin
end.

Splitting this in two files [Archive.is], a AxisUnit unit having only the TAxis<T> type, and a main program (even without having the specialisations) shows that even the unit itself would not compile.

This shows a major difference between Delphi (and similar C#) generics and C++ templates:

  • generics are compiled and fully verified at the generic stage
  • templates are pre-processed, then finally verified at specialisation stage

A solution would be that Delphi could constraint the generic type T into something like float or ordinal so the compiler would know that more operators are allowed in the code. But alas, Delphi – like C# – has a very limited number of constraints (C# only would allow a constraint for enumerations in version 7.3): Delphi Constraints in Generics – RAD Studio XE documentation wiki.

This StackOverflow question is very similar, and has the same answer (generics in Delphi work differently than templates in C++): [Source] templates – Arithmetic operations with generic types in Delphi – Stack Overflow

I’m new in Delphi. For a project required by my company, I need to translate some code from our existing C++ classes to Delphi. Some of these classes are templates, such …

Workaround: use the TValue.From<T>() function

There is a workaround though, but it is slow, as you need to convert from the generic T type to the actual (in this case floating point) type you can apply the operators on.

This is possible with the (Delphi 2010 introduced) TValue.From<T>() method which returns a TValue record. That TValue record has instance methods like AsExtended to extract or convert the embedded value as a specific type.

Initially, [Wayback] Delphi 2010 Rtti.TValue documentation had the From method signature wrong, maybe because of many wiki and blog HTML editors kill angle bracket pairs < and > in code blocks:

function From(const Value: T): TValue; static;

Since the [Wayback] Delphi XE System.Rtti.TValue documentation, the From method signature is fixed (see the bold parts):

class function From<T>(const Value: T): TValue; static;

With the [Wayback] Delphi XE2 Rtti.TValue documentation, the unit got renamed from Rtti into System.Rtti and has not changed further.

When using TValue.From<T>(), the AxisUnit becomes this:

unit AxisUnit;

interface

type
  TAxis<T> = record
    fMin, fMax, fRange: T;
    fStart, fEnd, fExtent: single;
    function Calc( const AScalar: T ): single;
  strict private
    function AsSingle(const Value: T): single;
  end;

implementation

uses
  System.Rtti;

function TAxis<T>.AsSingle(const Value: T): single;
begin
  Result := TValue.From<T>(Value).AsExtended
end;

function TAxis<T>.Calc(const AScalar: T): single;
var
  Offset: single;
  NormalisedOffset: single;
  ScaledOffset: single;
begin
  Offset := AsSingle(AScalar) - AsSingle(fMin);
  NormalisedOffset := Offset / AsSingle(fRange);
  ScaledOffset := NormalisedOffset * fExtent;
  Result := fStart + ScaledOffset;
end;

end.

–jeroen

Read the rest of this entry »

Posted in .NET, C#, Conference Topics, Conferences, Delphi, Development, Event, Software Development | Leave a Comment »

On TStrings (and TStringList) sorting: what the default Sort behaviour is and how to change sorting order

Posted by jpluimers on 2021/12/07

Because I need this eventually, here the full quote of my answer in [Wayback] sorting – How can I get TStringList to sort differently in Delphi – Stack Overflow (The default Sort behaviour is to accommodate i18n sorting in natural order):

i18n sorting totally depends on your locale.
So I totally agree with PA that this is not a bug: the default Sort behaviour works as designed to allow i18n to work properly.

Like Gerry mentions, TStringList.Sort uses AnsiCompareStr and AnsiCompareText (I’ll explain in a few lines how it does that).

But: TStringList is flexible, it contains SortCustomSort and CompareStrings, which all are virtual (so you can override them in a descendant class)
Furthermore, when you call CustomSort, you can plug in your own Compare function.

At the of this answer is a Compare function that does what you want:

  • Case Sensitive
  • Not using any locale
  • Just compare the ordinal value of the characters of the strings

CustomSort is defined as this:

procedure TStringList.CustomSort(Compare: TStringListSortCompare);
begin
  if not Sorted and (FCount > 1) then
  begin
    Changing;
    QuickSort(0, FCount - 1, Compare);
    Changed;
  end;
end;

By default, the Sort method has a very simple implementation, passing a default Compare function called StringListCompareStrings:

procedure TStringList.Sort;
begin
  CustomSort(StringListCompareStrings);
end;

So, if you define your own TStringListSortCompare compatible Compare method, then you can define your own sorting.
TStringListSortCompare is defined as a global function taking the TStringList and two indexes referring the items you want to compare:

type
  TStringListSortCompare = function(List: TStringList; Index1, Index2: Integer): Integer;

You can use the StringListCompareStrings as a guideline for implementing your own:

function StringListCompareStrings(List: TStringList; Index1, Index2: Integer): Integer;
begin
  Result := List.CompareStrings(List.FList^[Index1].FString,
                                List.FList^[Index2].FString);
end;

So, by default TStringList.Sort defers to TList.CompareStrings:

function TStringList.CompareStrings(const S1, S2: string): Integer;
begin
  if CaseSensitive then
    Result := AnsiCompareStr(S1, S2)
  else
    Result := AnsiCompareText(S1, S2);
end;

Which then use the under lying Windows API function CompareString with the default user locale LOCALE_USER_DEFAULT:

function AnsiCompareStr(const S1, S2: string): Integer;
begin
  Result := CompareString(LOCALE_USER_DEFAULT, 0, PChar(S1), Length(S1),
    PChar(S2), Length(S2)) - 2;
end;

function AnsiCompareText(const S1, S2: string): Integer;
begin
  Result := CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, PChar(S1),
    Length(S1), PChar(S2), Length(S2)) - 2;
end;

Finally the Compare function you need. Again the limitations:

  • Case Sensitive
  • Not using any locale
  • Just compare the ordinal value of the characters of the strings

This is the code:

function StringListCompareStringsByOrdinalCharacterValue(List: TStringList; Index1, Index2: Integer): Integer;
var
  First: string;
  Second: string;
begin
  First := List[Index1];
  Second := List[Index2];
  if List.CaseSensitive then
    Result := CompareStr(First, Second)
  else
    Result := CompareText(First, Second);
end;

Delphi ain’t closed, quite the opposite: often it is a really flexible architecture.
It is often just a bit of digging to see where you can hook into the that flexibility.

–jeroen

Posted in Delphi, Development, Software Development | Leave a Comment »

parsing – delphi – strip out all non standard text characers from string – Stack Overflow

Posted by jpluimers on 2021/12/02

From a while back a totally non-optimised code example by me (intentionally limiting to AnsiStr as it was about filtering ASCII, and UniCode has way many code points for the Latin script).

// For those who need a disclaimer: 
// This code is meant as a sample to show you how the basic check for non-ASCII characters goes
// It will give low performance with long strings that are called often.
// Use a TStringBuilder, or SetLength & Integer loop index to optimize.
// If you need really optimized code, pass this on to the FastCode people.
function StripNonAsciiExceptCRLF(const Value: AnsiString): AnsiString;
var
  AnsiCh: AnsiChar;
begin
  for AnsiCh in Value do
    if (AnsiCh >= #32) and (AnsiCh <= #127) and (AnsiCh <> #13) and (AnsiCh <> #10) then
      Result := Result + AnsiCh;
end;

and an optimised one by [WayBack] David Heffernan

function StrippedOfNonAscii(const s: string): string;
var
  i, Count: Integer;
begin
  SetLength(Result, Length(s));
  Count := 0;
  for i := 1 to Length(s) do begin
    if ((s[i] >= #32) and (s[i] <= #127)) or (s[i] in [#10, #13]) then begin
      inc(Count);
      Result[Count] := s[i];
    end;
  end;
  SetLength(Result, Count);
end;

Even when “trivial”, I usually do not prematurely optimise as optimised code is almost always less readable than non-optimised code.

Source: [Wayback] parsing – delphi – strip out all non standard text characers from string – Stack Overflow

–jeroen

Posted in Delphi, Development, Software Development | Leave a Comment »

Interesting situation now UWP isn’t panacea any more: for Windows development, native is back to center stage

Posted by jpluimers on 2021/11/17

Summary: UWP (Universal Windows Platform) is dying, long live native Windows development.

Source: [Wayback/Archive] Native Windows is Back to Center Stage

With the downplay of UWP, native development is again the primary Windows development model, after 20 years (given we had .NET in between). Native is what Delphi does at its best, so this is great news

Via:

–jeroen

Posted in Delphi, Development, Software Development, Windows Development | Leave a Comment »

Infusion pump and PCA (patient-controlled analgesia) calculation

Posted by jpluimers on 2021/11/09

This is a great PCA calculator: [Archive.is] CADD calculator 0.5BETA END USER Google Docs – Google Sheets

Via:

Related:

–jeroen

Read the rest of this entry »

Posted in Development, Excel, Google, GoogleDocs, GoogleSheets, Office, Office Automation, Office VBA, Power User, Scripting, Software Development | Leave a Comment »

 
%d bloggers like this: