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,161 other followers

Delphi history: No, dynamic arrays do not support a non-zero lower bound, but what if you want them? – via Stack Overflow

Posted by jpluimers on 2017/11/21

A post of some older Delphi stuff I did in the past just in case need it again.

David Heffernan found the documentation for this: [WayBackStructured Types (Delphi): Dynamic Arrays – RAD Studio

Since I needed a dynamic array structure supporting a non-zero lower bound, I was glad he also provided an answer with a data structure that does provide a non-zero lower bound.

For my own reference I’ve put his answers and questions below (as it’s way easier to search my blog than the complete internet) and my own implementation:

Answers:

Lower bound of dynamic arrays

Dynamic arrays always have a lower bound of 0. So, low(A) equals 0 for all dynamic arrays. This is even true for empty dynamic arrays, i.e. nil. From the documentation:

Dynamic arrays are always integer-indexed, always starting from 0.

TSpecifiedBoundsArray

Having answered your direct question already, I also offer you the beginnings of a generic class that you can use in your porting.

type
    TSpecifiedBoundsArray<T> = class
    private
      FValues: TArray<T>;
      FLow: Integer;
      function GetHigh: Integer;
      procedure SetHigh(Value: Integer);
      function GetLength: Integer;
      procedure SetLength(Value: Integer);
      function GetItem(Index: Integer): T;
      procedure SetItem(Index: Integer; const Value: T);
    public
      property Low: Integer read FLow write FLow;
      property High: Integer read GetHigh write SetHigh;
      property Length: Integer read GetLength write SetLength;
      property Items[Index: Integer]: T read GetItem write SetItem; default;
    end;

  { TSpecifiedBoundsArray<T> }

  function TSpecifiedBoundsArray<T>.GetHigh: Integer;
  begin
    Result := FLow+System.High(FValues);
  end;

  procedure TSpecifiedBoundsArray<T>.SetHigh(Value: Integer);
  begin
    SetLength(FValues, 1+Value-FLow);
  end;

  function TSpecifiedBoundsArray<T>.GetLength: Integer;
  begin
    Result := System.Length(FValues);
  end;

  procedure TSpecifiedBoundsArray<T>.SetLength(Value: Integer);
  begin
    System.SetLength(FValues, Value);
  end;

  function TSpecifiedBoundsArray<T>.GetItem(Index: Integer): T;
  begin
    Result := FValues[Index-FLow];
  end;

  function TSpecifiedBoundsArray<T>.SetItem(Index: Integer; const Value: T);
  begin
    FValues[Index-FLow] := Value;
  end;

I think it’s pretty obvious how this works. I contemplated using a record but I consider that to be unworkable. That’s down to the mix between value type semantics for
FLow and reference type semantics for FValues. So, I think a class is best here. It also behaves rather weirdly when you modify Low. No doubt you’d want to extend this. You’d add a SetBounds, a “copy to”, a “copy from” and so on. But I think you may find it useful. It certainly shows how you can make an object that looks very much like an array with non-zero lower bound.

Question:

Do dynamic arrays support a non-zero lower bound (for VarArrayCreate compatibility)?

I’m going maintain and port to Delphi XE2 a bunch of very old Delphi code that is full of VarArrayCreate constructs to fake dynamic arrays having a lower bound that is not zero. Drawbacks of using Variant types are:

  • quite a bit slower than native arrays (the code does a lot of complex financial calculations, so speed is important)
  • not type safe (especially when by accident a wrong var... constant is used, and the Variant system starts to do unwanted conversions or rounding)

Both could become moot if I could use dynamic arrays. Good thing about variant arrays
is that they can have non-zero lower bounds. What I recollect is that dynamic arrays used to always start at a lower bound of zero. Is this still true? In other words: Is it possible to have dynamic arrays start at a different bound than zero?
As an illustration a before/after example for a specific case (single dimensional, but the code is full of multi-dimensional arrays, and besides varDouble, the code also uses various other varXXX data types that TVarData allows to use):

function CalculateVector(aSV: TStrings): Variant;
var
  I: Integer;
begin
  Result := VarArrayCreate([1,aSV.Count-1],varDouble);
  for I := 1 to aSV.Count-1 do
    Result[I] := CalculateItem(aSV, I);
end;

The CalculateItem function returns Double. Bounds are from 1 to aSV.Count-1. Current replacement is like this, trading
the space zeroth element of Result for improved compile time checking:

type
  TVector = array of Double;
function CalculateVector(aSV: TStrings): TVector;
var
  I: Integer;
begin
  SetLength(Result, aSV.Count); // lower bound is zero, we start at 1 so we ignore the zeroth element
  for I := 1 to aSV.Count-1 do
    Result[I] := CalculateItem(aSV, I);
end;

My own implementation:

unit DynamicArrays;

interface

type
  IDynamicDoubleArray = interface
    function GetCount: Integer; 
    function GetLowerBound: Integer; 
    function GetRelativeIndex(const Index: Integer): Integer;
    function GetUpperBound: Integer; 
    function GetValue(Index: Integer): Double;
    procedure ReDimension(const UpperBound: Integer); 
    procedure SetValue(Index: Integer; const Value: Double);
    property Count: Integer read GetCount;
    property LowerBound: Integer read GetLowerBound;
    property UpperBound: Integer read GetUpperBound;
    property Value[Index: Integer]: Double read GetValue write SetValue; default;
  end;

  TDynamicDoubleArray = class(TInterfacedObject, IDynamicDoubleArray)
  strict private
    FLowerBound: Integer;
    FStorage: array of Double;
    FUpperBound: Integer;
  protected
    function GetCount: Integer; virtual;
    function GetLowerBound: Integer; virtual;
    function GetRelativeIndex(const Index: Integer): Integer; virtual;
    function GetUpperBound: Integer; virtual;
    function GetValue(Index: Integer): Double; virtual;
    procedure ReDimension(const UpperBound: Integer); virtual;
    procedure SetUpperBoundAndReAllocateStorage(const UpperBound: Integer); virtual;
    procedure SetValue(Index: Integer; const Value: Double); virtual;
    property Count: Integer read GetCount; 
    property LowerBound: Integer read GetLowerBound;
    property UpperBound: Integer read GetUpperBound;
    property Value[Index: Integer]: Double read GetValue write SetValue; default;
  public
    constructor Create(const UpperBound: Integer); overload;
    constructor Create(const LowerBound, UpperBound: Integer); overload;
  end;

implementation

uses
  Variants, 
  VarUtils,
  SysUtils,
  SysConst;

constructor TDynamicDoubleArray.Create(const UpperBound: Integer);
begin
  Create(0, UpperBound);
end;

constructor TDynamicDoubleArray.Create(const LowerBound, UpperBound: Integer);
begin
  FLowerBound := LowerBound;
  SetUpperBoundAndReAllocateStorage(UpperBound);
end;

function TDynamicDoubleArray.GetCount: Integer;
begin
  Result := 1 + UpperBound - LowerBound; // 2..5 -> 1 + 5 - 2 == 4
end;

function TDynamicDoubleArray.GetLowerBound: Integer;
begin
  Result := FLowerBound;
end;

function TDynamicDoubleArray.GetUpperBound: Integer;
begin
  Result := FUpperBound;
end;

function TDynamicDoubleArray.GetValue(Index: Integer): Double;
var
  RelativeIndex: Integer;
begin
  RelativeIndex := GetRelativeIndex(Index);
  Result := FStorage[RelativeIndex];
end;

procedure TDynamicDoubleArray.SetValue(Index: Integer; const Value: Double);
var
  RelativeIndex: Integer;
begin
  RelativeIndex := GetRelativeIndex(Index);
  FStorage[RelativeIndex] := Value;
end;

{ IDynamicDoubleArray }

function TDynamicDoubleArray.GetRelativeIndex(const Index: Integer): Integer;
begin
  if Index > UpperBound then
    raise ERangeError.CreateRes(@SRangeError);
  if Index < LowerBound then
    raise ERangeError.CreateRes(@SRangeError);
  Result := Index - LowerBound;
end;

procedure TDynamicDoubleArray.ReDimension(const UpperBound: Integer);
begin
  SetUpperBoundAndReAllocateStorage(UpperBound);
end;

{ IDynamicDoubleArray }

procedure TDynamicDoubleArray.SetUpperBoundAndReAllocateStorage(const UpperBound: Integer);
begin
  if UpperBound < LowerBound then
    raise ERangeError.CreateRes(@SRangeError);
  FUpperBound := UpperBound;
  SetLength(FStorage, Count);
end;

end.

–jeroen

via: [WayBackdelphi – Do dynamic arrays support a non-zero lower bound (for VarArrayCreate compatibility)? – Stack Overflow

10 Responses to “Delphi history: No, dynamic arrays do not support a non-zero lower bound, but what if you want them? – via Stack Overflow”

  1. Bob said

    Could you post a example of how to use the unit?
    I can’t find hot to create/use the DynamicDoubleArray array.

    Var
    tt : TDynamicDoubleArray;

    Begin
    tt.Create(1,10);

    End;

    compiler do not complain, but crash when executing the procedure.

    How to create, dimension, put/get values from the array…?

    • jpluimers said

      Sorry Bob, this is not going to happen any time soon.

      I first need to recover from metastasized rectum cancer, which will take considerable time (think quarters, not weeks).

      Until then, the blog is on “auto-post” (with a queue of about 18 months).

      Health status updates will be at https://twitter.com/jpluimers

      Regards,

      –jeroen

  2. Bob said

    Could you post an example using the unit?
    I have been looking for this long time ago, but can’t find how to create the DynamicDoubleArray array and how to get / set values.

  3. rgreat said

    Why make it class and not a record?

    TArrayEx = record

    • jpluimers said

      Because I wanted a reference type, not a value type:

      I contemplated using a record but I consider that to be unworkable. That’s down to the mix between value type semantics for
      FLow and reference type semantics for FValues. So, I think a class is best here.

      • A record should work fine and would be usable like a value type. But why on earth would anyone need a non-zero lower bound? You may want one, but there is no need for them.

        • jpluimers said

          In for instance the actuary profession, they are very common. Code like this makes it a lot easier to translate business requests into code.

        • Bob said

          Lower zero bound is useful when you have to translate large code where the arrays are 1-based lower bounds, like in Fortran. Code is so large that is preferable to keep the structure as the code is.
          In my example, it includes complex math iterations that could lead to wrong results if modifying the bounds for zero based, unless you spend lots of extra time to fully understand the operation and re-do the J-1 things properly…

          IMHO

        • Bob said

          Lower non-zero bound is useful when you have to translate large code where the arrays are 1-based lower bounds, like in Fortran. Code is so large that is preferable to keep the structure as the code is.
          In my example, it includes complex math iterations that could lead to wrong results if modifying the bounds for zero based, unless you spend lots of extra time to fully understand the operation and re-do the J-1 things properly…

          IMHO

          • jpluimers said

            I totally agree with this. Please remind me at year end, maybe I’ve recovered enough by then to create some proper examples.

            Note that Rudy has passed away last year, so he won’t respond.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

 
%d bloggers like this: