logo To Foot
© J R Stockton, ≥ 2008-03-11

Delphi Bit/Byte-Moving.

No-Frame * Framed Index * Frame This
Links within this site :-

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.

General

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.

Gen... Routines Below

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

Other routines will generally be Pascal-compatible; and slight optimisation with Result may be possible in Delphi.

Type int64 requires Delphi 4 or higher.

Viewing Bit/Byte Patterns

I have a test program using these routines.

Bytes

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} ;

Bits

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} ;

Addressing Parts of Variables

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 ;

Split

As above.

Join

After Peter Below (TeamB) :-

function MakeInt64(LoPart, HiPart : dword) : int64 ; assembler ;
  asm  end ; // sic.

Or as above.

Byte Operations

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.

Reversing Byte Order

To reverse the order of the bytes in a larger type, without reversing the contents of the bytes themselves.

2 bytes

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 ;

4 bytes

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 ;

8 bytes

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 ;

General

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.

Rotating Byte Order

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.

General Re-Arrangement

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.

Bit Operations

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.

Reversing Bit Order

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.

Shift And Rotate Bits

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.

Shift

Rotate

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} ;
MUCH OF THE ABOVE WAS NOT TESTABLE BY ME WHEN WRITTEN
Home Page
Mail: no HTML
© Dr J R Stockton, near London, UK.
All Rights Reserved.
These pages are tested mainly with Firefox 3.0 and W3's Tidy.
This site, http://www.merlyn.demon.co.uk/, is maintained by me.
Head.