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: [WayBack] Structured 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)
equals0
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 forFValues
. So, I think a class is best here. It also behaves rather weirdly when you modifyLow
. No doubt you’d want to extend this. You’d add aSetBounds
, 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 othervarXXX
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 returnsDouble
. Bounds are from1
toaSV.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: [WayBack] delphi – Do dynamic arrays support a non-zero lower bound (for VarArrayCreate compatibility)? – Stack Overflow