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: [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)equals0for 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.
TSpecifiedBoundsArrayHaving 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
recordbut I consider that to be unworkable. That’s down to the mix between value type semantics for
FLowand 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 othervarXXXdata 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
CalculateItemfunction returnsDouble. Bounds are from1toaSV.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






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
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.
rgreat said
Why make it class and not a record?
TArrayEx = record
jpluimers said
Because I wanted a reference type, not a value type:
Rudy Velthuis said
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.