Current situation: Almost all (*) untyped hex-literal values found in source code
are non-negative, because negativity in FPC requires 8 bytes with highest bit (bit63) = 1.
(Further extension would be less consistent: 128-bit literals and 64-bit literals can be negative, others not)
(*) For statistics, if array is treated as one entity:)
I found some time to write a summary about how untyped hex literals should be:
First of all: Use untyped literals only if you assume auto-size (sizeof(constName)).
Untyped hexadecimal literals should represent only non-negative numbers (like in Delphi, C++ and other languages). "Non-negative" refers to the literal ($...) only. Literals with sign prefix (-$...) can be stored as result value or as expression.
The literal $FFFFFFFFFFFFFFFF should be equal to high(uint64) (unlike in FPC3.2),
and the minimal storage type for this large value is uint64, followed by int128 and uint128.
(Large negative number (-$FFFFFFFFFFFFFFFF) would need to be stored in int128.)
Hexadecimal numbers (eg. "$100") and decimal (eg. "256") are at same abstraction level
which is higher (input number) than bitpattern (at bit-operations level).
Fortunately the underlying uint64 bitpattern is also $FFFFFFFFFFFFFFFF,
but theoretically the untyped hex number $FFFFFFFFFFFFFFFF can be represented by multiple bitpatterns (eg. BCD or sufficient floating point types) and number can be read as stored byte-array (lowest abstraction) as well.
Due to the format-equality, the hex literal (value 0..high(uint64)) looks equal to
underlying bitpattern, which has unsigned type or belongs to non-negative part of signed type.
Typecast int64($FFFFFFFFFFFFFFFF) can be used to get reinterpretation (-1).
About typecast (explicit or implicit) to typed target:
Pascal has the convention to use explicit typecast,
other assignments are subject to range checking (error on value outside of target range).
The new concept needs decision about implicit cast from uint64 to int64:
i64 := $FFFFFFFFFFFFFFFF; // Delphi shows out-of-range warning, but error for all smaller types.
The more consistent (stricter) solution would be:
i64 := int64($FFFFFFFFFFFFFFFF); // explicit typecast always required
i64 := -$1; // is in target range [-$8000000000000000..$7FFFFFFFFFFFFFFF]
And possibly to port older sourcecode: FPC compile demands explicit typecast
on every u64 hex literal (not only in assignment) which fulfills this condition:
(u64 > high(int64)) and ((targetType <> uint64) or (targetType indefinite)).
(u64 > high(int64)) means byte7 (of 7..0) is in range ($80..$FF).
Example of noticeable difference:
i64 := -1;
// in Delphi is:
assert( i64 <> $FFFFFFFFFFFFFFFF);
assert( i64 = int64($FFFFFFFFFFFFFFFF));
// in FPC3.2 is:
assert( i64 = $FFFFFFFFFFFFFFFF);
// Delphi calls overloaded functions for uint64 (instead of int64):
writeSomeInt( $FFFFFFFFFFFFFFFF);
********************************************************
Advice for programmers:
If you have untyped hex literals with (bit63 = 1) in your source code:
[$8000000000000000..$FFFFFFFFFFFFFFFF], then declare them with typecasting
to make your intended interpretation portable between compilers:
int64($Fxxxxxxxxxxxxxxx) // this is interpretation of untyped literal in FPC3.2
uint64($Fxxxxxxxxxxxxxxx) // this is interpretation in Delphi and possibly future FPC.
The untyped literal was auto-size, thus you may use (if value allows) a smaller type:
Example: longint($8xxxxxxx) instead of int64($FFFFFFFF8xxxxxxx)
If you are looking for even more fun:
Negative values could also be represented as expressions:
(-$mag) with (mag := (not negInt64Bitpattern) + 1):
-$1 { = int64($FFFFFFFFFFFFFFFF) = -1}
-$2 { = int64($FFFFFFFFFFFFFFFE) = -2}
-$7FFFFFFFFFFFFFFF { = int64($8000000000000001) = -2**63 + 1}
-$8000000000000000 { = int64($8000000000000000) = -2**63 = low(int64)}
Warning: The last expression "-$8000000000000000" might not be supported yet everywhere:
Reason: Negate(u64) may technically fail for (u64 > high(int64)).
And as long as untyped $8000000000000000 has negative interpretation,
we would actually need "-uint64($8000000000000000)".
********************************************************
http://docwiki.embarcadero.com/RADStudio/Rio/en/Declared_ConstantsDelphi has implied max-type uint64 for untyped hex-literals ($FFFFFFFFFFFFFFFF = high(uint64)),
but certain typed locations also accept untyped negative int64Bitpatterns (warning might occur, but no error):
const i: int64 = $FFFFFFFFFFFFFFFF; { targettype int64, becomes (-1)}
const a: array[0..3] of int64 = ($FFFFFFFFFFFFFFFF, ...);
int64Var := $FFFFFFFFFFFFFFFF; { targettype int64}
case int64Var of $FFFFFFFFFFFFFFFF:...; else .. end;
for int64Var := 0 downto $FFFFFFFFFFFFFFFF do ...;
writeSomeInt64( $FFFFFFFFFFFFFFFF); // calls the
int64-function only if all preferred (uint64, float) overloading functions are absent
Reason is the implicit typecast from uint64 to int64 in Delphi:
The input literal is accepted correctly as (-1), but better (stricter) would be:
explicitly typecast them as well or even better, provide range-compliant values:
const i: int64 = -$1; // in target range [-$8000000000000000..$7FFFFFFFFFFFFFFF]
********************************************************
A method to find hex-literals of range [$8000000000000000..$FFFFFFFFFFFFFFFF]:
Lazarus -> Search -> Find in Files:
Enable "Regular expressions" for "Text to find":
\$[8-9a-fA-F][0-9a-fA-F]{15}