Delphi :将长十六进制字符串转换为十进制字符串

qojgxg4l  于 2022-11-23  发布在  其他
关注(0)|答案(1)|浏览(196)

我正在用 Delphi 7维护一个旧项目。我需要将一个长的十六进制字符串转换为十进制字符串。我在C#中搜索并找到了示例代码,但在Delphi中没有。我只有两个选择:
1.在 Delphi 7中实现或使用函数。
1.在 Delphi 2010中实现或使用一个函数,然后将其导出为DLL。
我正在处理的十六进制字符串的最大长度是40个字符,下面是一个示例:

'6F000080B4D4B3426C66A655010001000080B4'

我使用rapidtables进行转换,这里是输出

'2475382888117010136950089026926167642744062132'

我希望有人以前解决过这个问题,可以帮助我。也许有人给予我一个算法,我可以用来写一个函数在 Delphi 中。
注意事项:
在 Delphi 中,Int64的最大正值是$7FFFFFFFFFFFFFFF = 9223372036854775807,这个值远远不是我所需要的。

798qvoo8

798qvoo81#

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.

相关问题