First of all, I'm not a very experienced programmer. I'm using Delphi 2009 and have been working with sets, which seem to behave very strangely and even inconsistently to me. I guess it might be me, but the following looks like there's clearly something wrong:
unit test;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
private
test: set of 1..2;
end;
var Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
test := [3];
if 3 in test then
Edit1.Text := '3';
end;
end.
If you run the program and click the button, then, sure enough, it will display the string "3" in the text field. However, if you try the same thing with a number like 100, nothing will be displayed (as it should, in my opinion). Am I missing something or is this some kind of bug? Advice would be appreciated!
EDIT: So far, it seems that I'm not alone with my observation. If someone has some inside knowledge of this, I'd be very glad to hear about it. Also, if there are people with Delphi 2010 (or even Delphi XE), I would appreciate it if you could do some tests on this or even general set behavior (such as "test: set of 256..257") as it would be interesting to see if anything has changed in newer versions.
I was curious enough to take a look at the compiled code that gets produced, and I figured out the following about how sets work in Delphi 2010. It explains why you can do test := [8] when test: set of 1..2, and why Assert(8 in test) fails immediately after.
How much space is actually used?
An set of byte has one bit for every possible byte value, 256 bits in all, 32 bytes. An set of 1..2 requires 1 byte but surprisingly set of 100..101 also requires one byte, so Delphi's compiler is pretty smart about memory allocation. On the othter hand an set of 7..8 requires 2 bytes, and set based on a enumeration that only includes the values 0 and 101 requires (gasp) 13 bytes!
Test code:
TTestEnumeration = (te0=0, te101=101);
TTestEnumeration2 = (tex58=58, tex101=101);
procedure Test;
var A: set of 1..2;
B: set of 7..8;
C: set of 100..101;
D: set of TTestEnumeration;
E: set of TTestEnumeration2;
begin
ShowMessage(IntToStr(SizeOf(A))); // => 1
ShowMessage(IntToStr(SizeOf(B))); // => 2
ShowMessage(IntToStr(SizeOf(C))); // => 1
ShowMessage(IntToStr(SizeOf(D))); // => 13
ShowMessage(IntToStr(SizeOf(E))); // => 6
end;
Conclusions:
The basic model behind the set is the set of byte, with 256 possible bits, 32 bytes.
Delphi determines the required continuous sub-range of the total 32 bytes range and uses that. For the case set of 1..2 it probably only uses the first byte, so SizeOf() returns 1. For the set of 100.101 it probably only uses the 13th byte, so SizeOf() returns 1. For the set of 7..8 it's probably using the first two bytes, so we get SizeOf()=2. This is an especially interesting case, because it shows us that bits are not shifted left or right to optimize storage. The other interesting case is the set of TTestEnumeration2: it uses 6 bytes, even those there are lots of unusable bits around there.
What kind of code is generated by the compiler?
Test 1, two sets, both using the "first byte".
procedure Test;
var A: set of 1..2;
B: set of 2..3;
begin
A := [1];
B := [1];
end;
For those understand Assembler, have a look at the generated code yourself. For those that don't understand assembler, the generated code is equivalent to:
begin
A := CompilerGeneratedArray[1];
B := CompilerGeneratedArray[1];
end;
And that's not a typo, the compiler uses the same pre-compiled value for both assignments. CompiledGeneratedArray[1] = 2.
Here's an other test:
procedure Test2;
var A: set of 1..2;
B: set of 100..101;
begin
A := [1];
B := [1];
end;
Again, in pseudo-code, the compiled code looks like this:
begin
A := CompilerGeneratedArray1[1];
B := CompilerGeneratedArray2[1];
end;
Again, no typo: This time the compiler uses different pre-compiled values for the two assignments. CompilerGeneratedArray1[1]=2 while CompilerGeneratedArray2[1]=0; The compiler generated code is smart enough not to overwrite the bits in "B" with invalid values (because B holds information about bits 96..103), yet it uses very similar code for both assignments.
Conclusions
All set operations work perfectly well IF you test with values that are in the base-set. For the set of 1..2, test with 1 and 2. For the set of 7..8 only test with 7 and 8. I don't consider the set to be broken. It serves it's purpose very well all over the VCL (and it has a place in my own code as well).
In my opinion the compiler generates sub-optimal code for set assignments. I don't think the table-lookups are required, the compiler could generate the values inline and the code would have the same size but better locality.
My opinion is that the side-effect of having the set of 1..2 behave the same as set of 0..7 is the side-effect of the previous lack of optimization in the compiler.
In the OP's case (var test: set of 1..2; test := [7]) the compiler should generate an error. I would not classify this as a bug because I don't think the compiler's behavior is supposed to be defined in terms of "what to do on bad code by the programmer" but in terms of "what to do with good code by the programmer"; None the less the compiler should generate the Constant expression violates subrange bounds, as it does if you try this code:
(code sample)
procedure Test;
var t: 1..2;
begin
t := 3;
end;
At runtime, if the code is compiled with {$R+}, the bad assignment should raise an error, as it does if you try this code:
(code sample)
procedure Test;
var t: 1..2;
i: Integer;
begin
{$R+}
for i:=1 to 3 do
t := i;
{$R-}
end;
According to the official documentation on sets (my emphasis):
The syntax for a set constructor is: [
item1, ..., itemn ] where each item is
either an expression denoting an
ordinal of the set's base type
Now, according to Subrange types:
When you use numeric or character
constants to define a subrange, the
base type is the smallest integer or
character type that contains the
specified range.
Therefore, if you specify
type
TNum = 1..2;
then the base type will be byte (most likely), and so, if
type
TSet = set of TNum;
var
test: TSet;
then
test := [255];
will work, but not
test := [256];
all according to the official specification.
I have no "inside knowledge", but the compiler logic seems rather transparent.
First, the compiler thinks that any set like set of 1..2 is a subset of set of 0..255. That is why set of 256..257 is not allowed.
Second, the compiler optimizes memory allocation - so it allocates only 1 byte for set of 1..2. The same 1 byte is allocated for set of 0..7, and there seems to be no difference between the both sets on binary level. In short, the compiler allocates as little memory as possible with alignment taken into account (that means for example that compiler never allocates 3 bytes for set - it allocates 4 bytes, even if set fits into 3 bytes, like set of 1..20).
There is some inconsistency in a way the compiler treats sets, which can be demonstrated by the following code sample:
type
TTestSet = set of 1..2;
TTestRec = packed record
FSet: TTestSet;
FByte: Byte;
end;
var
Rec: TTestRec;
procedure TForm9.Button3Click(Sender: TObject);
begin
Rec.FSet:= [];
Rec.FByte:= 1; // as a side effect we set 8-th element of FSet
// (FSet actually has no 8-th element - only 0..7)
Assert(8 in Rec.FSet); // The assert should fail, but it does not!
if 8 in Rec.FSet then // another display of the bug
Edit1.Text := '8';
end;
A set is stored as a number and can actually hold values that are not in the enumeration on which the set is based. I would expect an error, at least when Range Checking is on in the compiler options, but this doesn't seem to be the case. I'm not sure if this is a bug or by design though.
[edit]
It is odd, though:
type
TNum = 1..2;
TSet = set of TNum;
var
test: TSet;
test2: TNum;
test2 := 4; // Not accepted
test := [4]; // Accepted
From the top of my head, this was a side effect of allowing non contiguous enumeration types.
The same holds for .NET bitflags: because in both cases the underlying types are compatible with integer, you can insert any integer in it (in Delphi limited to 0..255).
--jeroen
As far as I'm concerned, no bugs there.
For exemple, take the following code
var aByte: Byte;
begin
aByte := 255;
aByte := aByte + 1;
if aByte = 0 then
ShowMessage('Is this a bug?');
end;
Now, you can get 2 result from this code. If you compiled with Range Checking TRUE, an exception will be raise on the 2nd line. If you did NOT compile with Range Checking, the code will execute without any error and display the message dialogs.
The situation you encountered with the sets is similar, except that there is no compiler switch to force an exception to be raised in this situation (Well, as far as I know...).
Now, from your exemple:
private
test: set of 1..2;
That essentially declare a Byte sized set (If you call SizeOf(Test), it should return 1). A byte sized set can only contain 8 elements. In this case, it can contains [0] to [7].
Now, some exemple:
begin
test := [8]; //Here, we try to set the 9th bit of a Byte sized variable. It doesn't work
Test := [4]; //Here, we try to set the 5th bit of a Byte Sized variable. It works.
end;
Now, I need to admit I would kind of expect the "Constant expression violates subrange bounds" on the first line (but not on 2nd)
So yeah... there might be a small issue with the compiler.
As for your result being inconsistent... I'm pretty sure using set values out of the set's subrange values isn't guaranteed to give consistent result over different version of Delphi (Maybe not even over different compiles... So if your range is 1..2, stick with [1] and [2].
Related
The running example is really simple to understand:
program Project1;
uses
SysUtils, Math;
var
fValue: double;
fValueMax: double;
begin
fValue := 7.0207503445953527;
fValueMax := Max(0, fValue);
writeln(fValue);
writeln(fValueMax);
readln;
end.
However the result is completely unexpected. For some reason, the Max function does not only return the larger number from the two arguments but also changes it's value.
In the example code above, the expected value of fValueMax is exactly fValue, but instead fValueMax is bigger. The difference is approximately E-7, so small, but still unexpected and crashes my following code (which is not published here to keep the question clear and simple).
I should state upfront that the last time I used Pascal was close to 25 years ago. But I pulled down Free Pascal out of curiosity and tried this:
program Project1;
uses
SysUtils, Math;
var
fValue: double;
fValueMax: double;
fSingle: single;
fValue2: double;
fValue2b: double;
fValueMax2: double;
begin
fValue := 7.0207503445953527;
fSingle := 7.0207503445953527;
fValueMax := Max(0, fValue);
writeln(fValue); // prints 7.0207503445953527E+000
writeln(fValueMax); // prints 7.0207505226135254E+000
writeln(fSingle); // prints 7.020750523E+00
fValue2 := 7.0207503445953527;
fValue2b := 0.0;
fValueMax2 := Max(fValue2b, fValue2);
writeln(fValue2); // prints 7.0207503445953527E+000
writeln(fValueMax2); // prints 7.0207503445953527E+000
readln;
end.
My first two writeln commands show the same result that you reported seeing. I suspected that perhaps Max was returning a value with less precision that the double you expected to get back, so I created fSingle and assigned it the same literal as you assigned to fValue, and sure enough, its value looks very close to what you're getting back in fValueMax.
So finally, instead of invoking Max with fValue and the literal 0, I called it with two variables of type double, one of which I had set to 0.0. In this case you can see that the input (fValue2) and the output (fValueMax2) have exactly the same value. So while I don't know exactly what Pascal's rules are for determining which overload to call, I wonder if your original call to Max was somehow resolving to the version that takes two single values and returns the same.
While you may be aware of this, I feel compelled to throw in the usual caution about how floating-point types like single and double won't always be able to exactly represent the values you want them to. Here's a good overview.
Write a program to convert an integer number to its hexadecimal representation without using inbuilt functions.
Here is my code, but it is not working. Can anyone tell where is the mistake?
It is giving an error:
"Project raised exception class EAccessViolation with message 'Access violation at address 00453B7B in module 'Project.exe'.Write of address FFFFFFFF'.Process stopped.Use Step or Run to continue."
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,Forms,
Dialogs;
type
TForm1 = class(TForm)
end;
function hexvalue(num:Integer):Char;
var
Form1: TForm1;
implementation
{$R *.dfm}
function hexvalue(num:Integer):Char;
begin
case num of
10: Result:='A';
11: Result:='B';
12: Result:='C';
13: Result:='D';
14: Result:='E';
15: Result:='F';
else Result:=Chr(num);
end;
end;
var
intnumber,hexnumber,actualhex:String;
integernum:Integer;
i,j,k:Byte;
begin
InputQuery ('Integer Number','Enter the integer number', intnumber);
integernum:=StrToInt(intnumber);
i:=0;
while integernum >= 16 do
begin
hexnumber[i]:=hexvalue(integernum mod 16);
integernum:= integernum div 16;
Inc(i);
end;
hexnumber[i]:= hexvalue(integernum);
k:=i;
for j:=0 to k do
begin
actualhex[j]:= hexnumber[i];
Dec(i);
end;
ShowMessage(actualhex);
end.
Since this obviously is a homework assignment, I don't want to spoil it for you and write the solution, but rather attempt to guide you to the solution.
User input
In real code you would need to be prepared for any mistake from the user and check that the input really is integer numbers only and politely ask the user to correct the input if erroneous.
Conversion loop
You have got that OK, using mod 16 for each nibble of integernum and div 16 to move to the next nibble, going from units towards higher order values.
Conversion of nibble to hex character
Here you go wrong. If you would have written out also the cases for 0..9, you could have got the case statement right. As others have commented, Chr() takes an ASCII code. However, using a case statement for such a simple conversion is tedious to write and not very efficient.
What if you would have a lookup table (array) where the index (0..15) directly would give you the corresponding hex character. That would be much simpler. Something like
const
HexChars: array[_.._] of Char = ('0',_____'F')
I leave it to you to fill in the missing parts.
Forming the result (hex string)
Your second major mistake and the reason for the AV is that you did not set the length of the string hexnumber before attempting to acess the character positions. Another design flaw is that you fill in hexnumber backwards. As a result you then need an extra loop where you reverse the order to the correct one.
There are at least two solutions to solve both problems:
Since you take 32 bit integer type input, the hex representation is not more than 8 characters. Thus you can preset the length of the string to 8 and fill it in from the lower order position using 8 - i as index. As a final step you can trim the string if you like.
Don't preset the length and just concatenate as you go in the loop hexnumber := HexChars[integernum mod 16] + hexnumber;.
Negative values
You did not in any way consider the possibility of negative values in your code, so I assume it wasn't part of the task.
First mistake : String are 1 indexed. Meaning that the index of their first character is 1 and not 0. You initialize "i" to 0 and then try to set hexnumber[i].
Second mistake : Strings might be dynamic, but they don't grow automatically. If you try to access the first character of an empty string, it won't work. You need to call SetLength(HeXNumber, NumberOfDigits). You can calculate the number of digits this way :
NumberOfDigits := Trunc(Log16(integernum)) + 1;
Since Log16 isn't really something that exists, you can either use LogN(16,integernum) or (Log(IntegerNum) / Log(16)) depending on what is available in your version of Delphi.
Note that this might return an invalid value for very, very large value (high INT64 range) due to rounding errors.
If you don't want to go that road, you could replace the instruction by
hexnumber := hexvalue(integernum mod 16) + hexnumber;
which would also remove the need to invert the string at the end.
Third Mistake : Using unsigned integer for loop variable. While this is debatable, the instruction
for I := 0 to Count - 1 do
is common practice in Delphi without checking Count > 0. When count = 0 and using an unsigned loop counter, you'll either get an integer overflow (if you have them activated in your project options) or you'll loop High(I) times, which isn't what you want to be doing.
Fourth mistake : already mentionned : Result:=Chr(num) should be replaced by something like Result := InttoStr(Num)[1].
Personally, I'd implement the function using an array.
HexArr : Array[0..15] of char = ('0', '1',...,'D','E','F');
begin
if InRange(Num, 0, 15) then
Result := HexArr[Num]
else
//whatever you want
end;
Optimization on/off doesn't matter. This is simplified code to demonstrate the warning. In the original routine all assignments and compares are to function expressions that could return a variety of values.
procedure test;
var i, k: integer;
begin
k := 21;
repeat
if k = 20 then break;
i := 5
until i = 5;
end;
This does seem to be a weakness in the compiler.
repeat
if k = 20 then break;
i := 5
until i = 5;
A human static analysis can easily check that i is always assigned before it is read. The line before the until assigns it. If that assignment is skipped by the break, then the until test is also skipped.
So, this can only be described as a compiler bug because the compiler should be able to understand how break and until interact. Clearly the compiler's analysis depends on an understanding of these things, since removing the break will also remove the warning. So it can only be that the compiler doesn't understand well enough.
It turns out that the 32 bit Windows compiler still behaves the same way in the current Delphi release, XE7. But the 64 bit compiler correctly emits no warning for your code.
Note that you might expect the compiler to realise that the condition in the if test in your code always evaluates False. Well, the compiler won't. It does not perform static analysis of constant propagation through non constant variables. Its analysis takes no account of the values that you place in variables.
The reason why Delphi warns you about this is the fact that local variables don't get initialized automatically. What this means? This means that if you try reading any such variable it will return some more or less random result (contents of the memory it points to).
So as soon as Delphi recognizes a posibility that some variable might be read before anything is written to it (initialization of a variable does write a default value into it) the warning will be raised.
And this would happen in your code if the value of "k" would be 20 becouse the "i := 5" line would be skipped.
How do you solve this warning in your case? Simply set some value to "i" outside any loops or any conditional statments. For instance:
procedure test;
var i, k: integer;
begin
//Set initial value for i
i := 0;
k := 21;
repeat
if k = 20 then break;
i := 5
until i = 5;
end;
When I compile the following code in Delphi XE2 for the target platform 64-bit Windows ...
function HKeyToString(_HKey: HKey): string;
begin
case _HKey of
HKEY_CLASSES_ROOT: result := 'HKEY_CLASSES_ROOT'; // do not translate
HKEY_CURRENT_USER: result := 'HKEY_CURRENT_USER'; // do not translate
HKEY_LOCAL_MACHINE: result := 'HKEY_LOCAL_MACHINE'; // do not translate
HKEY_USERS: result := 'HKEY_USERS'; // do not translate
HKEY_PERFORMANCE_DATA: result := 'HKEY_PERFORMANCE_DATA'; // do not translate
HKEY_CURRENT_CONFIG: result := 'HKEY_CURRENT_CONFIG'; // do not translate
HKEY_DYN_DATA: result := 'HKEY_DYN_DATA'; // do not translate
else
Result := Format(_('unknown Registry Root Key %x'), [_HKey]);
end;
end;
... I get warnings for each of the HKEY_-Constants:
"W1012 Constant expression violates subrange bounds"
I checked the declarations in Winapi.Windows (with Ctrl+Leftclick on the identifiers):
type
HKEY = type UINT_PTR;
{...}
const
HKEY_CLASSES_ROOT = HKEY(Integer($80000000));
These look fine to me. Why does the compiler still think there is a problem?
On the 64 bit compiler the actual value of HKEY_CLASSES_ROOT is:
FFFFFFFF80000000
That's because the cast to Integer makes 80000000 into a negative number. And then the conversion to unsigned leads to FFFFFFFF80000000. Note that this value is correct. The declaration in the windows header file is:
#define HKEY_CLASSES_ROOT (( HKEY ) (ULONG_PTR)((LONG)0x80000000) )
and when you include the header file and inspect the value of HKEY_CLASSES_ROOT in a C++ program, it is the exact same value as for the Delphi declaration.
And then we can solve the puzzle from the Delphi documentation which states that the selectors in a case statement can only be:
any expression of an ordinal type smaller than 32 bits
You have no choice but to replace your case statement with an if statement.
HKEY=UINT_PTR is an unsigned 64 bit integer in your case, and case ... of statement seems not to handle it.
The XE2/XE3 compiler front-end still assumes it targets a 32bit platform, even if there is no technical reason for the compiler back-end not able to handle 64 bit case statements (with the classic sub register,constant; jz #... asm code generation pattern).
You can try to typecast everything to integer:
const
HKEY_CLASSES_ROOT32 = Integer($80000000);
...
function HKeyToString(_HKey: integer): string;
begin
case _HKey of
HKEY_CLASSES_ROOT32: result := 'HKEY_CLASSES_ROOT'; // do not translate
...
or just ignore the upmost 32 bits of the _HKey value (this is the same):
function HKeyToString(_HKey: HKey): string;
begin
case _HKey and $ffffffff of
HKEY_CLASSES_ROOT and $ffffffff: result := 'HKEY_CLASSES_ROOT'; // do not translate
...
It will work as expected under Windows: due to the limited number of HKEY_* constants, I think you can just ignore the upmost 32 bits of the _HKey value, and therefore use the buggy case .. of... statement. And it will work of course for both Win32 and Win64.
I suspect even ... and $f will be enough - see all HKEY_* constants.
Last (and certainly best solution) is to use good old nested if... else if... statements:
function HKeyToString(_HKey: HKey): string;
begin
if_HKey=HKEY_CLASSES_ROOT then
result := 'HKEY_CLASSES_ROOT' else // do not translate
if_HKey=HKEY_CURRENT_USER then
result := 'HKEY_CURRENT_USER' else // do not translate
....
I guess the last one is preferred, and not slower, with modern pipelines CPUs.
I'm porting a Delphi project to 64 bits and I have a problem with a line of code which has the IN operator.
The compiler raise this error
E2010 Incompatible types: 'Integer' and 'Int64'
I wrote this sample app to replicate the problem.
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
Var
I : Integer;
L : Array of string;
begin
try
if I in [0, High(L)] then
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
readln;
end.
This code works ok in 32 bits, but why doesn't it compile in Delphi XE2 64 bits? How I can fix this issue?
*UPDATE *
It seems which my post caused a lot of confusion (sorry for that) , just for explain the original code which i'm porting is more complex, and I just wrote this code as a sample to illustrate the issue. the original code uses an in operator to check if a value (minor than 255) belongs to a group of values (all minor or equal to 255) like so
i in [0,1,3,50,60,70,80,127,High(LArray)]
This code can't be compiled because the High function is returning a 8 byte value, which is not a ordinal value. and the In operator can be only used in sets with ordinal values.
FYI, the size of the results returned by the High function is different depending of the parameter passed as argument.
Check this sample
Writeln(SizeOf(High(Byte)));
Writeln(SizeOf(High(Char)));
Writeln(SizeOf(High(Word)));
Writeln(SizeOf(High(Integer)));
Writeln(SizeOf(High(NativeInt)));
Writeln(SizeOf(High(TBytes)));
Finally, you can fix your code casting the result of High function to integer.
if I in [0, Integer(High(L))] then
UPDATE
Check the additional info provided by David and remember to be very careful when you use the in operator to check the membership of a value in set with variable values. The in operator only checks the least significant byte of each element (in delphi 32 bits).
Check this sample
i:=257;
Writeln( 1 in [i]);
This return true because the low byte of 257 is 1.
And in Delphi 64 bits, the values greater than 255 are removed of the set. So this code
i:=257;
Writeln( 1 in [i]);
will return false because is equivalent to
Writeln( 1 in []);
What RRUZ says is quite correct.
To add a little bit more explanation, in 64 bit Delphi, dynamic array indices can be 64 bits wide. This is clearly needed, for example, when working with a large TBytes memory block. And so the high function must return a value of a type wide enough to hold all possible indices. So, high when applied to a dynamic array, returns a value of type Int64.
Once you start compiling 64 bit code the in operator is unsuited to the problem you are trying to solve. Whilst you could use the cast that RRUZ suggests, it may be clearer to write the code like this
if (I=low(L)) or (I=high(L)) then
Whilst the in operator makes for quite readable code, it is my opinion that a cast to Integer is not acceptable here. That will simply set a trap for you to fall into when you first have an array with more than high(Integer) elements. When that happens the code with the cast will stop working.
But in fact the problems run far deeper than this. The in version of the code fails long before you reach high(Integer) elements. It turns out that your code, whilst it compiles, does not really work. For example, consider this program:
program WeirdSets;
{$APPTYPE CONSOLE}
uses
SysUtils;
var
a: array of Integer;
begin
SetLength(a, 257);
Writeln(BoolToStr(Length(a) in [0, Length(a)], True));
end.
You would expect this program to output True but in fact it outputs False. If instead you were to write
Writeln(BoolToStr(Length(a) in [0, 257], True));
then the compiler reports:
[DCC Error] WeirdSets.dpr(9): E1012 Constant expression violates subrange bounds
The fundamental issue here is that sets are limited to 256 elements so as soon as you have an array with length greater than that, your code stops working.
Sadly, Delphi's support for sets is simply inadequate and is in urgent need of attention.
I also wonder whether you actually meant to write
if I in [0..High(L)] then
If so then I would recommend that you use the InRange function from Math.
if InRange(I, 0, High(L)) then
or even better
if InRange(I, low(L), High(L)) then
The most serious problem with the OP code is that in operator is limited to set size, i.e. [0..255]. Try this in any 32 bit version of Delphi to avoid the 64 bit issue:
var
I: Integer;
L: array of Integer;
begin
SetLength(L, 1000);
I:= 999;
Assert(I in [0, High(L)]); // fails !
end;
The OP is lucky if Length(L) <= 256 always, otherwise it is a bug you probably never thought of.
To find this bug switch range checking on:
{$R+}
procedure TForm1.Button2Click(Sender: TObject);
var
I: Integer;
A: array of Integer;
begin
SetLength(A, 1000);
I:= 999;
if I in [0, High(A)] then ShowMessage('OK!'); // Project .. raised exception
// class ERangeError with message 'Range check error'.
end;