This page relies largely on contributions; all should be tested before use, and all not attributed to others must be so tested.
I got Delphi 3 standard edition in Autumn 1998. Between 2006-10-14 and 2008-01-12, I could not run Delphi 3. On 2008-02-10, I added Delphi 2006.
This is a Delphi page; parts will be applicable to Pascal.
Questions about modifications should be sent to one of the applicable newsgroups; for example, comp.lang.pascal.delphi.misc or borland.public.delphi.language.objectpascal.
Operations can be invoked in four fashions - as in-line code, as a function, as a procedure operating on a variable parameter, and as a procedure operating on in and out parameters. Any subroutine form can be implemented from any of the other forms; but optimal use of available operations may differ for each.
Code can be pure Delphi, or can include or largely be assembler.
Code can require the latest Delphi, or can be compatible with earlier versions of Delphi or Pascal.
The int64 type can be emulated to some extent by comp, but few of the comp operations will be useful here.
Note that the sizes of some types are version-dependent; see Pascal / Delphi / + Types.
The test routines below which have names beginning with Gen are intended to be general rather than to be safe or efficient; and to be compatible with both Borland Pascal and Delphi.
The following will be assumed in those routines only :-
{$IFDEF Delphi} {$H-} {$ENDIF} // shortstrings type ByteArr = array [byte] of byte ; PByteArr = ^ByteArr ; type ByteSet = set of byte ; PBS = ^ByteSet ;
Other routines will generally be Pascal-compatible; and slight optimisation with Result may be possible in Delphi.
Type int64 requires Delphi 4 or higher.
I have a test program using these routines.
function GenHexStr(const Pin : pointer ; const Bytes : byte) : string ; const hexa : array [$0..$F] of char = '0123456789ABCDEF' ; var S : string ; B, C : byte ; begin S[0] := char(Bytes*2) ; for B := 1 to Bytes do begin C := PByteArr(Pin)^[Bytes-B] ; S[2*B-1] := hexa[C shr 4] ; S[2*B ] := hexa[C and $F] ; end ; GenHexStr := S end {GenHexStr} ;
function GenBitStr(const Pin : pointer ; const Last : byte) : string ; const OI : array [boolean] of char = '01' ; var S : string ; B : byte ; begin S[0] := char(Last+1) ; for B := 0 to Last do S[Last-B+1] := OI[B in PBS(Pin)^] ; GenBitStr := S end {GenBitStr} ;
Types such as the following can often be useful; but note that casting any variable to a type of a different size is at best dangerous. It is sometimes not realised that any variable reference can be cast, even when the variable is being assigned to. The list is not all inclusive, and generally it may be better to use longer type-names.
type B02 = array [01..02] of byte ; B04 = array [01..04] of byte ; B08 = array [01..08] of byte ; B16 = array [01..16] of byte ; W02 = array [01..02] of word ; W04 = array [01..04] of word ; W08 = array [01..08] of word ; D02 = array [01..02] of dword ; D04 = array [01..04] of dword ; Q02 = array [01..02] of int64 ;
All that one really needs, where the ?? are sufficiently large, is :-
BB = array [01..??] of word ; WW = array [01..??] of dword ; DD = array [01..??] of dword ; QD = array [01..??] of int64 ;
Using these, a larger type can be cast to an array of smaller types, on either the left or the right side of an assignment or in equivalent circumstances.
D02(Answer)[1] := LoPart ; HiPart := D02(Answer)[2] ;
I believe that those compile to simple 32-bit Delphi assignments.
Some may prefer to use a record type consisting of a variant part in which variants have field lists using types such as the above :-
type R = packed record case byte of 0 : ( B : B08 ) ; // or ( B0,B1,B2,B3,B4,B5,B6,B7 : byte ) ; 1 : ( W : W04 ) ; // or ( W0,W1,W2,W3 : word ) ; 2 : ( D : D02 ) ; // or ( D0,D1 : dword ) ; 3 : ( I : int64 ) end ;
As above.
After Peter Below (TeamB) :-
function MakeInt64(LoPart, HiPart : dword) : int64 ; assembler ; asm end ; // sic.
Or as above.
One can write a General Byte Rearranger, type-unsafe and omnipotent; it could be used to test other methods. It should not be unreasonably slow; but for specific reasonable cases there will generally be something better.
procedure GenByteShuf(const Pin, Pout : pointer ; const Order : string) ; type TArI = array ['a'..'p'] of byte ; TPtI = ^TArI ; type TArO = array [ 1 .. 16] of byte ; TPtO = ^TArO ; var B : byte ; begin if Pin=Pout then RunError(222) ; for B := 1 to Length(Order) do TPtO(Pout)^[B] := TPtI(Pin)^[Order[B]] ; end {GenByteShuf} ;
The parameter Order gives by its length the size of Pout^ and by its contents (drawn from 'a'..'p') the actual rearrangement.
const A : array [1..8] of char = '12345678' ; const B : string [8] = 'ABCDEFGH' ; ... GenByteShuf(@A, @B[1], 'ghefcdab') ;
gives B = '78563412'.
The following dedicated methods are more efficient.
To reverse the order of the bytes in a larger type, without reversing the contents of the bytes themselves.
After Stefan Hoffmeister (TeamB); as Swap (deprecated?) is in the System unit (01→10) :-
function Swap16(ASmallInt : SmallInt) : SmallInt ; register ; asm xchg al,ah end ;
After Peter Below (TeamB) - Swaps the 4 bytes in a longint, dword, etc. (0123→3210) :-
function Swap32(value : dword) : dword ; assembler ; asm bswap eax end ;
After Bob Lee, using untyped var parameters :-
procedure Swap32(var src, dest) ; asm mov ecx, dword ptr[eax] ; bswap ecx ; mov dword ptr[edx], ecx end ;
From Rudy Velthuis, code by Bob Lee, to reverse byte order (01234567→76543210) :-
function Int64Swap(A : int64) : int64 ; asm mov edx,dword ptr [A] ; mov eax,dword ptr [A+4] bswap edx ; bswap eax end ;
and
function Swap64(const A : int64) : int64 ; register ; asm mov edx,dword ptr [A] ; mov eax,dword ptr [A+4] bswap edx ; bswap eax end ;
Tested in BP7 and D3 :-
procedure Reverse(var Bytes ; const N : integer) ; type T = array [0..65519] of char ; var P : ^T ; k : word ; begin GetMem(P, N) ; for k := 0 to N-1 do P^[N-k-1] := T(Bytes)[k] ; Move(P^, Bytes, N); FreeMem(P, N) ; end {Reverse} ;
However, the task can be done in situ. After a Duncan Murdoch idea, but still to be tested (must need value of Data to be set!) :-
procedure SwapBytes(var Bytes ; Len : Integer) ; var Data : PChar ; T : char ; k : Integer ; begin for k:=0 to (Len div 2) - 1 do begin T := Data[k] ; Data[k] := Data[Len-k-1] ; Data[Len-k-1] := T end ; end {SwapBytes} ;
I have a recollection that, under at least one circumstance (not necessarily including Delphi) it is better to use within the loop a procedure something like
procedure ByteSwap(var A, B : byte) ; var T : byte ; begin T := A ; A := B ; B := T end ;
since the addresses of A, B are then only determined once and the compiler should generate excellent code. If speed matters, that might be worth trying. Alternatively, set pointers to the two bytes.
To rotate the bytes within a variable X of a larger type.
The effect can sometimes be achieved by moving a byte from within one end to outside the other, and adjusting a pointer.
If one has to do a lot of instances of the same set of swaps within a type of structure, then it might be better to declare and load an array A such that the main code can be along the lines of :-
for B := 1 to Length(Src) do Dst[A[B]] := Src[B] ; or for B := 1 to Length(Dst) do Dst[B] := Src[A[B]] ;
It should be possible to avoid range-checking here.
Testing and setting of individual bits by using the efficient set operations is covered, for Pascal but equally applicable to Delphi, in Borland Pascal Optimisation 1.
One can write a Very General Bit Shuffler, type-unsafe and omnipotent; it could be used to test other methods. It will be unreasonably slow for reasonable cases; for those, there will be something much better.
procedure VeryGenBitShuf(const Pin, Pout : pointer ; const Last : byte ; const Order : pointer) ; var B : byte ; begin if Pin=Pout then RunError(222) ; for B := 0 to Last do begin if PByteArr(Order)^[B] in PBS(Pin)^ then Include(PBS(Pout)^, B) else Exclude(PBS(Pout)^, B) ; end ; end {VeryGenBitShuf} ;
The parameter Order gives the actual rearrangement.
const Order : array [0..7] of byte = (4,5,6,7,0,1,2,3) ; const A : byte = $71 ; ... VeryGenBitShuf(@A, @B, High(Order), Order) ;
gives B = 23 = $17.
The following dedicated methods are more efficient.
To reverse the bits within a variable X; written for bytes, adaptable for longer ordinal types.
function RevByte(const X : byte) : byte ; type BS = set of 0..7 ; var K : byte ; Q : BS ; begin Q := [] ; for K := 0 to 7 do if 7-K in BS(X) then Include(Q, K) ; RevByte := byte(Q) end ; function ByteRev(X : byte) : byte ; var K : byte ; Q : byte ; begin Q := 0 ; for K := 0 to 7 do begin Q := 2*Q + Ord(Odd(X)) ; X := X div 2 end ; ByteRev := Q end ;
For a byte-size variable, it will often be worth constructing a look-up table array [byte] of byte, and initialising it by slower code.
One may also reverse a nibble at a time :-
function ReverseByte(const X : byte) : byte ; const A : array [$0..$F] of byte = ($0,$8,$4,$C, $2,$A,$6,$E, $1,$9,$5,$D, $3,$B,$7,$F) ; begin ReverseByte := (A[X and $0F] shl 4) or (A[X shr 4]) end ;
Larger variables can be fully reversed by a combination of bit and byte reversal.
There are many possible forms of shift and rotate; some are better supported by delphi and the x86 character set than others.
For a simple or logical shift, right or left, bits shifted in are zeroes and bits shifted out are lost from the variable, but may affect flags.
For an arithmetic right shift, the most significant, sign bit is reproduced.
The basic Delphi operations are shl and shr, apparently on the unsigned bit patterns. One can also use *2 and div 2 which should treat the sign of the value arithmetically.
Be careful about possible unexpected (or needed) behaviour of the leftmost bit on a shift right, or vice versa; it appears ill-defined, and may (or perhaps should) depend on whether the type is signed.
Consider that a variable of a small type, such as a shortint, may be extended before the operation is carried out. Check, in particular, what happens with shr and div on a negative value.
Rotate is not directly provided, and needs Shift.
After John Herbster : For N-bit-rotates :-
function JHROR64(Value : int64 ; N : integer) : int64 ; begin Result := (Value shr N) + (Value shl (64-N)) end ; function JHROL64(Value : int64 ; N : integer) : int64 ; begin Result := (Value shl N) + (Value shr (64-N)) end ; function JHROR32(Value : dword ; N : integer) : dword ; begin Result := (Value shr N) + (Value shl (32-N)) end ; function JHROL32(Value : dword ; N : integer) : dword ; begin Result := (Value shl N) + (Value shr (32-N)) end ; // etc.
From Henrick Hellström :-
function HHROR64(const Value : int64 ; N : integer) : int64 ; asm push EBX lea EBX,dword ptr [Value] mov ECX,N mov EAX,[EBX] mov EDX,[EBX + 4] and ECX,$3F jz @@Exit cmp ECX,$20 ja @@Left je @@Swap shrd EAX,EDX,CL mov EBX,[EBX] shrd EDX,EBX,CL jmp @@Exit @@Left: neg ECX and ECX,$1F shld EAX,EDX,CL mov EBX,[EBX] shld EDX,EBX,CL jmp @@Exit @@Swap: xchg EAX,EDX @@Exit: pop EBX end {HHROR} ;