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:
- Implement or use a function in Delphi 7.
- 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:
- Converting the hexadecimal number digit by digit. This leads into solving how to
- multiply decimal numbers, which in turn needs us being able to
- 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 of0
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 functionsChr()
andOrd()
should support everything again.