The following code which attempts to convert a value well beyond the double precision range
StrToFloat('1e99999999')
correctly reports an incorrect floating point value in Delphi 10.2r3 with the Windows 32 bits compiler, but when compiled with the Window 64 bits compiler, it silently returns a 0 (zero).
Is there a way to have StrToFloat report an error when the floating point value is incorrect?
I have tried TArithmeticException.exOverflow, but this has no effect in that case.
I also tried TArithmeticException.exPrecision but it triggers in many usual approximation cases (f.i. it triggers when converting '1e9').
Issue was noticed with Delphi 10.2 update 3
addendum: to workaround the issue, I have started a clean-room alternative implementation of string to double conversion, initial version with tests can be found in dwscript commit 2ba1d4a
This is a defect that is present in all versions of Delphi that use the PUREPASCAL version of StrToFloat. That maps through to InternalTextToExtended which reads the exponent like this:
function ReadExponent: SmallInt;
var
LSign: SmallInt;
begin
LSign := ReadSign();
Result := 0;
while LCurrChar.IsDigit do
begin
Result := Result * 10;
Result := Result + Ord(LCurrChar) - Ord('0');
NextChar();
end;
if Result > CMaxExponent then
Result := CMaxExponent;
Result := Result * LSign;
end;
The problem is the location of
if Result > CMaxExponent then
This test is meant to be inside the loop, and in the asm x86 version of this code it is. As coded above, with the max exponent test outside the loop, the 16 bit signed integer result value is too small for your exponent of 99999999. As the exponent is read, the value in Result overflows, and becomes negative. So for your example it turns out that an exponent of -7937 is used rather than 99999999. Naturally this leads to a value of zero.
This is a clear bug and I have submitted a bug report: RSP-20333.
As for how to get around the problem, I'm not aware of another function in the Delphi RTL that performs this task. So I think you will need to do one of the following:
Roll your own StrToFloat.
Pre-process the string, and handle out of range exponents before they read StrToFloat.
Use one of the functions from the C runtime library that performs the same task.
Finally, I am grateful for you asking this question because I can see that my own program is affected by this defect and so I can now fix it!
Update:
You may also be interested to look at a related bug that I found when investigating: RSP-20334. It might surprise you to realise that, StrToFloat('߀'), when using the PUREPASCAL version of StrToFloat, returns 1936.0. The trick is that the character that is being passed to StrToFloat is a non-Latin digit, in this case U+07C0.
Related
I have the following code:
function Extenso(nb: real); string;
var
total: real;
begin
total := Trunc(nb);
...
Result := ...;
end;
This function returns a string describing the number in full, for a country that has no cents, therefore the Trunc.
When input param nb comes in as 790209.00 (my error case, with decimals 0), strangely enough the result of Trunc is 790208 on debugger. Can someone please try to explain that ?!? I'm on Delphi XE3 update 2, with IDE fix pack 5.93, Windows 10 Pro 64.
Edit: as a clarification to the question, there's a business rule involved here, customer will accept rounds up to the result, but not down. Thanks #Dsm and #CraigYoung to your contributions.
You need to use round rather than trunc here. With type real, the value is not quite exact, so 790209.00 might really be 790208.999999999 for example, and truncing this will give the value shown. Of course, if you might have a value ending in .5, rounding might not give you what you want every time, and you might need to consider some function like
Trunc( nb + 0.000001);
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;
I had problems with the following code:
var
FileSize : Int64;
...
FileSize := Info.nFileSizeLow or (Info.nFileSizeHigh shl 32);
I expected it to work because of the Int64 type of the left side of the assignment. But it does not. The partial calculation containing the shl seems to produce an overflow.
So I changed it to:
FileSize := Info.nFileSizeLow or (Int64 (Info.nFileSizeHigh) shl 32);
which works on my 32 bit operating system, but does not work on Vista 64 bit!
Finally,
FileSize := Info.nFileSizeHigh;
FileSize := FileSize shl 32;
FileSize := Info.nFileSizeLow or FileSize;
works on both systems.
Can someone explain the differences in these three versions?
Generally speaking, the type of the expression a * b, where a and b are of type Integer and * is an operator that applies to Integer, is an integer type with the same range as Integer. (I say generally, as an exception is /.) In order for an operator to use 64-bit operations, one or more of the operands must have a range that is only expressible with a 64-bit type. That should cause all the operands to be promoted to 64-bit, and a 64-bit operation performed.
The fact that the left hand side of an assignment is a 64-bit location generally has no effect on the interpretation and typing of the expression on the right hand side of the assignment operator. This is the way it is in almost all languages that I'm aware of that have statically dispatched 32-bit and 64-bit operator overloads (as opposed to polymorphically dispatched operators on arbitrary precision integers or numeric towers etc.); making things behave otherwise would be very surprising behaviour.
For example, arguments to procedure calls are effectively implicit assignments to the parameters. If the left hand side of an assignment could change the interpretation of the expression on the right, we would not know how to interpret the argument to a procedure call without already knowing the definition:
var a, b: Integer;
// ...
P((a shl 16) or b); // 32-bit operation or 64-bit operation?
I do not know why you are seeing different behaviour with your second and third versions of the code. As far as I can see, they should be interpreted the same, and in my tests, they are interpreted the same. If you could provide sample code that works on 32-bit Windows but fails on 64-bit Windows, I could investigate further.
Actually, this is pretty well documented in Delphi 7's help file, under "Integer types":
In general, arithmetic operations on integers return a value of type Integer--which, in its current implementation, is equivalent to the 32-bit Longint. Operations return a value of type Int64 only when performed on one or more Int64 operand. Hence the following code produces incorrect results.
The code example provided:
var
I: Integer;
J: Int64;
...
I := High(Integer);
J := I + 1;
To get an Int64 return value in this situation, cast I as Int64:
...
J := Int64(I) + 1;
First of all FileSize must be defined as UInt64 and not Int64...
UInt64 (not available in early Delphi versions) is an unsigned 64 bit integer, aka a QWORD. This is the expected type for the FileSize (you won't expect a negative file size, won't you?).
IMHO you could have coded - using UInt64 because we don't want to have some values reported as negative:
FileSize := UInt64(Info.nFileSizeLow) or (UInt64(Info.nFileSizeHigh) shl 32));
But under Delphi 7 it produces the same exact code as yours.
FileSize := Info.nFileSizeLow or (Int64(Info.nFileSizeHigh) shl 32));
So there is perhaps some compiler regression. Could you take a look at the asm generated code (step debugger then Alt+F2), and see if there is a difference. But it's unlikely...
In all cases, here is a better (and faster) code:
with Int64Rec(FileSize) do
begin
Lo := Info.nFileSizeLow;
Hi := Info.nFileSizeHigh;
end;
The official MSDN documentation states about the WIN32_FIND_DATA Structure:
nFileSizeHigh: The high-order DWORD value of the file size, in bytes.
This value is zero unless the file size is greater than MAXDWORD.
The size of the file is equal to
(nFileSizeHigh * (MAXDWORD+1)) +
nFileSizeLow.
nFileSizeLow: The low-order DWORD value of the file size, in bytes.
Here is the resulting code:
FileSize := UInt64(Info.nFileSizeLow)+(UInt64(Info.nFileSizeHigh)*UInt64(1 shl 32));
Quite a funny definition, indeed...
This is not really an answer, but it's too long for a comment.
I noticed Delphi gets confused when the result of an expression is to be written into a 64 bit variable, but the operands are 32 bit. I ran into this bug when I was implementing a hash function returning an 64 bit number. Your third variant works because you're first assigning the 64 bit variable, helping Delphi figure out it really needs to do 64 bit arithmetic.
I'm tempted to say both variants (1) and (2) are actually failing because Delphi generates 32 bit arithmetic and then assignes the result to the 64 bit variable. I'm tempted to say the variant that works well on your 32 bit machine benefits from some sort of "unlucky non-failure" (ie: the code is bad, but none the less it produces good results for the given test). The trouble is, COMPILED code doesn't change when moved from a 32bit machine to a 64 bit machine. If the code is the same, the input is the same, you'd have to pin the error on the CPU, but you know you didn't find an bug in your CPU, so you have to fall back and re-think your tests, or pin it on the "unluck non-failure".
test on Delphi 7 and version 2 is OK. Must be bug of later version
I'm trying to figure out a way to check a string's first element if it's either a number or not.
if not(myString[0] in [0..9]) then //Do something
The problem is that I get an error "Element 0 inaccessible - use 'Length' or 'SetLength"
Another way came to my head from my C-like exprieince - convert the first element of the string to char and check the char,but there is no difference in the compile errors.
if not(char(myString[0]) in [0..9]) then //Do something
How do I accomplish it?
Strings are 1-based:
if not (myString[1] in ['0'..'9']) then // Do something
Pascal and Delphi indexes string from 1. This is a legacy from time where zero byte contained length, while next 255 (index 1 to 255) contained actual characters.
Joel Spolsky wrote quite good article on string issues:
http://www.joelonsoftware.com/articles/fog0000000319.html
Delphi strings use a 1-based index, so just rewrite to
if not(myString[1] in ['0'..'9']) then //Do something
Also take note of the quotes around the 0..9, otherwise you would be comparing characters to integers.
We should keep in mind some things:
String in Delphi is 0-based for mobile platforms and 1-based for Windows.
String in old versions of Delphi is AnsiString (1-byte per char) and WideString in new versions (2-bytes per char).
Delphi supports set of AnsiChar, but doesn't support set of WideChar.
So if we want to write a code compatible with all versions of Delphi, then it should be something like this:
if (myString[Low(myString)]>='0') and (myString[Low(myString)]<='9') then
// Do something
if not(myString[0] in [0..9]) then //Do something
If you're using Delphi 2009, the TCharacter class in Character.pas has functions like IsDigit to help simplify these kinds of operations.
Once you fix the indexing, of course. :)
With later updates to Delphi mobile code, the bottom string index changed from 0 to 1. When you compile older programmes, they compile and run correctly using 0 starting index. Programmes created with the later IDE produce an error. When you have mixtures, life gets complex!
It would be good to be able to take an older programme and tell the IDE that you want it brought up to date (maybe this would fix other things, like fonts getting scrambled when you answer a phone call!) but it would be good to get things consistent!
The simplest way to check to see if the first character of string is an integer, and then dispatch:
var
iResult : integer;
begin
if TryStrToInt( mySTring[1], iResult) then
begin
// handle number logic here iResult = number
end
else
begin
// handle non number logic here
end;
end;
I use a utility function to test the entire string:
function IsNumeric(const Value: string): Boolean;
var
i: Integer;
begin
Result := True;
for i := 1 to Length(Value) do
if not (Value[i] in ['0'..'9','.','+','-']) then
begin
Result := False;
Break;
end;
end;
The above code is for Delphi versions prior to 2007. In 2007 and 2009, you could change the integer variable i to a character c, and use for c in Value instead.
To test for integers only, remove the '.' from the set of characters to test against.
This is incorrect. ISO strings and older Pascal's also started at one. It is just a general convention, and afaik the s[0] thing is a result of that being vacant, and cheap to code in the UCSD bytecode interpreter. But that last bit is before my time, so only my guessing.
It results from the Pascal ability to have arbitrary upper and lower bounds, which provides for more typesafety accessing arrays.
Really old Pascal strings (till early eighties) strings were even worse than C ones btw. Multiple conventions were in used, but all were based on static arrays (like early C), but they were typically space padded, so you had scan back from the end till the spaces ended.
(removed the legacy tag, since being 1 based is not legacy. Accessing s[0] as length IS legacy, but that is not what the question is about)
Foreach element in strName
if not element in [0-9] then
do something
else
element is a digit
end if
Don't forget the quote between digits number.
I was curious how long a dynamic array could be so I tried
SetLength(dynArray, High(Int64));
That has a value of 9,223,372,036,854,775,807 and I figure that would be the largest number of indexes I could reference anyway. It gave me a:
ERangeError with message 'Range check error'.
So I tried:
SetLength(dynArray, MaxInt);
and got the same error!
Interestingly I could call it with
SetLength(dynArray, Trunc(Power(2, 32));
Which is actually twice the size of MaxInt!
I tried
SetLength(dynArray, Trunc(Power(2, 63) - 1));
Which is the same as High(Int64), and that failed too.
Short of continued trial and error, does someone know the maximum size? Does it depend on the size of the elements in the array?
I am using Delphi 2009. Will it be different for different versions (obviously when Commadore comes out it should be greater!)
The answer is clear from System.DynArraySetLength procedure, from line 20628:
Inc(neededSize, Sizeof(Longint)*2);
if neededSize < 0 then
Error(reRangeError);
The maximum value you can allocate without raising a range check error is therefore theoretically Maxint - SizeOf(Longint) * 2. Practically, you will get an out-of-memory error depending on how much memory is available.
There's no point in speculating about the maximum theoretical length of a dynamic array, as the maximum practical length is much smaller.
The size of the data structure and the data contained in it has to be smaller than the maximum memory an application can allocate, minus the memory the application code itself, the stack and the other data will need. On Windows (32 bit, the only version we mere mortals can target with Delphi right now) this is a virtual address range of 2 GByte, or 3 GByte with a special switch for the OS loader, for each application. I'm not sure though that a Delphi application can even handle the 3 GByte memory space, as the last third would have negative values for offsets in all places where integers are used instead of LongWords.
So you could try to allocate a dynamic array of say 80% or 90% of MaxInt div SizeOf(array element) - with the most probable result that the allocation of that memory block fails at runtime.
Also: giving an int64 length and getting no exception would not mean that the array has the intended length. Consider this code:
procedure TForm1.Button1Click(Sender: TObject);
var
a: array of byte;
l: int64;
begin
l := $4000000000;
SetLength(a, l);
Caption := IntToStr(Length(a));
end;
If range checking is off this will compile without hints and warnings, and run without exceptions. It has only the slight problem that the array has length 0 after the call to SetLength(). So for the checks in your question you should definitely read back the length of the dynamic array after a successful SetLength(), it is the only way to be sure that the compiler and runtime did what you intended.
Note that afaik elementcount is also limited, more than 2^31-1 is unlikely. It could be that size has the same limit (to avoid signed<>unsigned issues in the RTL) I doubt that more of than 2GB is possible even in /3GB mode.
MMaths:
max_array_bytesize = 2^31 - 9
max_array_elements_number = [(2^31 - 9) / array_element_bytesize]
Code:
max_array_elements_number := (MaxInt-Sizeof(Longint)*2) div SizeOf(array_element);
Example:
type
TFoo = <type_description>;
TFooDynArray = array of TFoo
const
cMaxMemBuffSize = MaxInt-Sizeof(Longint)*2;
var
A : TFooDynArray;
B : array of int64;
MaxElems_A : integer;
MaxElems_B : integer;
begin
MaxElems_A := cMaxMemBuffSize div SizeOf(TFoo);
MaxElems_B := cMaxMemBuffSize div SizeOf(int64);
ShowMessage('Max elements number for array:'#13#10+
'1) A is '+IntToStr(MaxElems_A)+#13#10+
'2) B is '+IntToStr(MaxElems_B)
);
end;