Quantcast
Channel: Delphi – The Wiert Corner – irregular stream of stuff
Viewing all articles
Browse latest Browse all 1440

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

$
0
0

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


Viewing all articles
Browse latest Browse all 1440

Trending Articles