Home > database >  Delphi : Converting long hexadecimal string to decimal string
Delphi : Converting long hexadecimal string to decimal string

Time:11-14

I'm maintenancing an old project with Delphi 7. I need to convert a long hexadecimal string to a decimal string. I searched and found sample code in C# but not in Delphi. I only have two options:

  1. Implement or use a function in Delphi 7.
  2. Implement or use a function in Delphi 2010 then export it as DLL.

Maximum length of hex string I'm working on is 40 character, here is a sample :


'6F000080B4D4B3426C66A655010001000080B4'

I used rapidtables to do conversion and here it's output

'2475382888117010136950089026926167642744062132'

I hope that some one has solved this problem before and can help. Maybe someone give me an algorithm I can use to write a function in Delphi.

Note :
In Delphi 7 for Int64 the maximum positive value is $7FFFFFFFFFFFFFFF = 9223372036854775807 and this value is far from what I need.

CodePudding user response:

For this to solve we need to cut it down into three steps:

  1. Converting the hexadecimal number digit by digit. This leads into solving how to
  2. multiply decimal numbers, which in turn needs us being able to
  3. add decimal numbers digit by digit.

As tiny helpers which are needed in the other functions let me have these:

type
  TArrayString= Array of String;  // In case it doesn't exist already

// Fill a text with leading zeroes up to the desired length
procedure MinLen( var s: String; iLen: Integer );
begin
  while Length( s )< iLen do s:= '0'  s;
end;

Addition

We all learnt written addition in school: write all numbers in one row, then add each row's digits and carry that sum's additional digits over to the next row of digits of the summands. This can also be done easily:

// Addition of multiple long numbers
function Summe( aSummand: TArrayString ): String;
var
  iLenMax, iA, iPos, iSum, iAdvance: Integer;
  c: Char;
begin
  result:= '0';
  case Length( aSummand ) of
    0: exit;  // Nothing to add at all
    1: begin
      result:= aSummand[Low( aSummand )];  // Sum equals the only summand
      exit;
    end;
  end;

  // Find the longest text, then make all texts as long as the longest,
  // so we can simply access an existing character at that position.
  iLenMax:= 0;
  for iA:= Low( aSummand ) to High( aSummand ) do begin
    if Length( aSummand[iA] )> iLenMax then iLenMax:= Length( aSummand[iA] );
  end;
  for iA:= Low( aSummand ) to High( aSummand ) do MinLen( aSummand[iA], iLenMax );
  MinLen( result, iLenMax );

  // All have the same length: process from the least significant digit
  // (right) to the most significant digit (left).
  for iPos:= iLenMax downto 1 do begin
    // Manual addition: write all numbers in one row, then add single
    // digits per row. Nobody will ever give this function so many
    // summands that the sum of single digits will come near the Integer
    // capacity.
    iSum:= 0;
    for iA:= Low( aSummand ) to High( aSummand ) do begin
      Inc( iSum, Ord( aSummand[iA][iPos] )- $30 );  // Add digit from each number
    end;
    Inc( iSum, Ord( result[iPos] )- $30 );  // Also add result's digit from potential previous carry

    // Turn that Integer sum into text again, digit by digit
    iAdvance:= 0;  // Exceeding the current position if we need to carry
    while iSum> 0 do begin
      c:= Chr( (iSum mod 10)  $30 );  // Only the rightmost digit
      if iPos- iAdvance< 1 then begin  // Outside the String?
        result:= c  result;  // Prepend
      end else begin
        result[iPos- iAdvance]:= c;  // Set new digit in overall sum
      end;

      iSum:= iSum div 10;  // This digit has been process, go to next one
      Inc( iAdvance );  // Not in the current position anymore, but processing potential carries
    end;
  end;
end;

I did not restrict it to always 2 summands for the following reasons:

  • Dealing with an unknown amount of summands needs virtually no extra work.
  • Adding multiple summands (instead of always 2) in one go is more efficient than calling this function again and again.
  • Later with multiplication we can lazily call this function once with f.e. 6 times the same summand to simulate multiplication by 6.

$30 is the ASCII code for the character '0' - subtracting the potential character '0' to '9' by that of '0' gives us the value 0 to 9.

Multiplication

We all learnt written multiplication in school, too: for each digit of one factor calculate that product (which can only be an "easy" multiplication by 0 to 9), write down all those products in a row as per digit position (optionally with trailing zeroes), then add all those products into a sum (referencing written addition). This can also be done easily, since we now have solved addition already:

// Multiplication of two long numbers
function Produkt( s1, s2: String ): String;
var
  iPos, iStep, iA, iNine: Integer;
  aSummand, aStep: TArrayString;
begin
  // For each digit of one factor we will make a separate multiplication
  SetLength( aSummand, Length( s1 ) );

  // This time it doesn't matter how long both numbers are: just again go
  // from least significant digit (right) to most significant digit (left).
  for iPos:= Length( s1 ) downto 1 do begin
    iA:= Length( s1 )- iPos;  // Array index per digit
    // As per our position the sum must be shifted by 10: first (rightmost)
    // digit needs no shift (0), second needs one (10), third needs two (100)...
    MinLen( aSummand[iA], iA );

    // Current digit
    iStep:= Ord( s1[iPos] )- $30;
    case iStep of
      0: ;  // Multiplication by 0 always results in 0, an empty summand equals one of "0"
      1: aSummand[iA]:= s2  aSummand[iA];  // No multiplication needed, just prepend with factor
    else
      // Cheap multiplication: use addition with 2 to 9 times the same summand
      SetLength( aStep, iStep );
      for iNine:= 0 to iStep- 1 do aStep[iNine]:= s2;
      aSummand[iA]:= Summe( aStep )  aSummand[iA];  // Prepend sum, zeroes must be trailing
    end;
  end;

  // Now just add all sums that we got per digit
  result:= Summe( aSummand );
end;

It could have been even shorter, since Summe() can already deal with 0 and 1 summands - I don't really need to treat that differently. As previously told: the easy multiplication is done by simple addition - not very performance efficient, but easy to comprehend.

Hexadecimal to decimal conversion

Since we can now add and multiply, we're also able to convert each digit of a non-decimal-base number and increase the outcome to the overall result:

// Convert base 2..36 long number into base 10 long number
function ConvertToDecimal( sFrom: String; sBase: String= '16' ): String;
var
  sStep, sPos: String;
  cFrom: Char;
  a: TArrayString;
begin
  SetLength( a, 2 );
  a[0]:= '0';  // Overall sum = result
  sPos:= '1';  // Current digit's power
  while Length( sFrom )> 0 do begin  // Process least significant digit (right)
    cFrom:= sFrom[Length( sFrom )];
    case cFrom of  // For now at max base 36 is supported, which includes hexadecimal
      '0'.. '9': sStep:= cFrom;  // Same as text
      'A'.. 'Z': sStep:= IntToStr( Ord( cFrom )- $41  10 );  // Turn "B" into "11"
    end;
    a[1]:= Produkt( sPos, sStep );  // Multiply current digit's value by current position's power
    a[0]:= Summe( a );  // Add both product and current overall result

    sPos:= Produkt( sPos, sBase );  // Increase power to next digit position
    Delete( sFrom, Length( sFrom ), 1 );  // Remove rightmost digit
  end;
  result:= a[0];
end;

It even works not only for hexadecimal (base 16) input, but also for others. $41 is the ASCII value for 'A' - subtracting the potential characters 'A' to 'Z' by that of 'A' gives us the value 0 to 25, to which we then just add 10.

Tests

Strings are implied. Spaces are just for optical reasons.

function parameters result proof
Summe() 123 456 579 brain
Summe() 123 456 9999 10578 MS calc, brain
Produkt() 36 * 12 504 MS calc, brain
Produkt() 8 6426578999 * 9731421999 8 4105351216 9179999001 rapidtables.com
ConvertToDecimal() 6F0000 80B4D4B3 426C66A6 55010001 000080B4 247538 2888117010 1369500890 2692616764 2744062132 rapidtables.com

Summary

  • This is a good exercise on turning basic/existing skills (arithmetics) into program code in the sense of needing more understanding in logics than in programming/the language, making it appealing to both beginners (still struggling with programming) and experts (mostly focusing on task automation).
  • Performance wise this is surely far from optimal, but should be rather easy to understand/comprehend/follow instead of being difficult to read. Likewise it should be easy to translate into other languages.
  • A bonus exercise would be to also add subtraction and division.
  • A few tiny details are omitted, f.e. numbers are always expected to not have leading zeroes - the results may then also have unneeded leading zeroes. Empty Strings are interpreted as 0, but a result of 0 is not reduced to an empty String either.
  • It works with any String type, because we only operate on ASCII anyway - there's no need to distinguish between AnsiString, UnicodeString, WideString and so on. However, Char is used as type, which is bound to that choice. But the functions Chr() and Ord() should support everything again.
  • Related