News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Large number conversion to unsigned hexadecimal value

Started by colinr, August 17, 2023, 11:30:09 PM

Previous topic - Next topic

NoCforMe

Sheesh; it ain't that complicated.

Here's a li'l testbed that does exactly what the OP wants: takes an ASCII decimal string and converts it to a quadword value, using 32-bit code. Tested, works.

Here's the heart of it, the function that does the conversion:

;====================================
; A2Q()
;
; Converts ASCII string to quadword binary value.
;
; On entry,
; EAX--> ASCII string
; Returns:
; EDX:EAX = binary value
;
; Tested: 8/17/23 --works--
;====================================

A2Q PROC

PUSH EBX
PUSH ESI
PUSH EDI
XOR EBX, EBX ;EDI:EBX = accumulator.
XOR EDI, EDI
MOV ESI, EAX

nxtdgt: XOR EAX, EAX
LODSB
TEST AL, AL
JZ done
SUB AL, '0' ;ASCII--> binary.

; Multiply accum. X 10 (64x32-bit multiply):
PUSH ESI
PUSH EAX ;Save digit just seen.
MOV EAX, EBX
MUL Ten
MOV ESI, EDX
MOV ECX, EAX ;ESI:ECX = partial product.
MOV EAX, EDI
MUL Ten
ADD ESI, EAX
POP EAX ;Get back current digit.
ADD ECX, EAX ;Add it in to accum.
ADC ESI, 0 ;Handle any carry.
MOV EDI, ESI ;Store new accum.
MOV EBX, ECX
POP ESI
JMP nxtdgt

done: MOV EAX, EBX
MOV EDX, EDI
POP EDI
POP ESI
POP EBX
RET

A2Q ENDP

Testbed program attached.

I'm sure the performance vultures are going to swoop down and point out the eleven ways from Sunday how this is inefficient, not fast enough, etc. So let me be the first to point out that this is a fairly dumbass way to do the conversion. I'm sure someone could figure out a clever way, using a bunch of XCHGs, to simplify this. But hey, it's pretty simple and IT WORKS. (Oh, yeah, you need to add a "Ten  DD 10" in your .data section.)

Any questions?
Assembly language programming should be fun. That's why I do it.

NoCforMe

A little more on a crucial part of my conversion routine, which is the 64x32-bit multiply. Looking online, I found a world of confusion over this topic, which again just ain't that complicated. Found loooooong, long discussions on places like Stack Overflow and the like which only devolved into total confusion. Then there was a comment in one of these discussions by none other than the estimable Raymond Chen, who said, basically, "work it out on paper: it's just like old-fashioned multiplication". Which it is.

The diagram below should explain this. Rather than try to copy any of the tortured, convoluted code examples that abounded on these sites, I just sat down with paper and pencil (!) and came up with a simple working routine. It ain't rocket surgery.

OK, just to cover my ass here: yes, technically this is an incomplete solution, because properly speaking a 64x32 bit multiply can yield up to a 96-bit result; it could result in overflow.

However, i think this satisfies the OP's question pretty well. Since the maximum value of a 64-bit (unsigned) number is 18,446,744,073,709,551,615 (what is that, 18-bazillion-something?), don't think there's much danger of running out of room unless you're dealing with astronomical quantities.
Assembly language programming should be fun. That's why I do it.

colinr

Quote from: NoCforMe on August 18, 2023, 06:46:50 AMAny questions?

Wow, I like it.

Will test as soon as I get the chance, which may not be until early next week now.

I don't think i'll get anywhere near exhausting the maximum number as the converted number is actually an offset into \\.\Physicaldrive

I never expected the code to be so short, a lot of thought has clearly gone into this as it's all done in registers and they need preserving between calculations.

I was looking to do this by iterating through the ASCII numbers backwards (obviously!) subtracting 30h from each one and then multiplying it by its ^10 and then subsequently adding that value to a QWORD, I knew it would be inefficient hence my reason to post on here as there had to be a simpler, more efficient way of achieving this.

Kudos to all the posters that have come up with various solutions, I'm sure there will be many others that will encounter this problem and discover this thread.

NoCforMe

Assembly language programming should be fun. That's why I do it.

colinr

This has got me thinking now about going the other way, 64 bit unsigned to an arbitrary length ASCII decimal string without any leading zeros.

Is there an efficient way to do this? I'm currently using sprintf for these conversions, but it is not efficient and certainly not secure.

I think this is one for you Mr NoCforMe!

jj2007

Quote from: colinr on September 07, 2023, 08:50:50 AMIs there an efficient way to do this?

There are several ways to do this. Latest version:

Intel(R) Core(TM) i5-2450M CPU @ 2.50GHz (SSE4)

30187   cycles for 100 * MasmBasic Str$()
102430  cycles for 100 * CRT sprintf
5150    cycles for 100 * uqword (Paul Dixon)

30660   cycles for 100 * MasmBasic Str$()
101666  cycles for 100 * CRT sprintf
5114    cycles for 100 * uqword (Paul Dixon)

29921   cycles for 100 * MasmBasic Str$()
101694  cycles for 100 * CRT sprintf
5108    cycles for 100 * uqword (Paul Dixon)

29902   cycles for 100 * MasmBasic Str$()
102012  cycles for 100 * CRT sprintf
5145    cycles for 100 * uqword (Paul Dixon)

29846   cycles for 100 * MasmBasic Str$()
101773  cycles for 100 * CRT sprintf
5158    cycles for 100 * uqword (Paul Dixon)

47      bytes for MasmBasic Str$()
36      bytes for CRT sprintf
711     bytes for uqword (Paul Dixon)

1234567890123456789 for MasmBasic Str$()
1234567890123456789 for CRT sprintf
1234567890123456789 for uqword (Paul Dixon)

colinr

Quote from: NoCforMe on August 18, 2023, 06:46:50 AMTestbed program attached.

I'm sure the performance vultures are going to swoop down and point out the eleven ways from Sunday how this is inefficient, not fast enough, etc. So let me be the first to point out that this is a fairly dumbass way to do the conversion. I'm sure someone could figure out a clever way, using a bunch of XCHGs, to simplify this. But hey, it's pretty simple and IT WORKS. (Oh, yeah, you need to add a "Ten  DD 10" in your .data section.)

Any questions?

Just a quick one, It may be an issue my side, but I'll thow it here just in case.

I'm using this code in conjuction with the API call SetFilePointer.

Basically, when I'm seeking into a file precisely 2GB, it fails, I don't have the error code, but I think it might be something to do with it being converted to a signed integer? I'm still investigating this, but is this plausable?

Kind Regards

fearless

You probably need to specify the high order dword to move as well > 2Gb or as you suspected it will use that value as a negative and move back if possible (signed dword value).

https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfilepointer

[in] lDistanceToMoveThe low order 32-bits of a signed value that specifies the number of bytes to move the file pointer.
If lpDistanceToMoveHigh is not NULL, lpDistanceToMoveHigh and lDistanceToMove form a single 64-bit signed value that specifies the distance to move.
If lpDistanceToMoveHigh is NULL, lDistanceToMove is a 32-bit signed value. A positive value for lDistanceToMove moves the file pointer forward in the file, and a negative value moves the file pointer back.
[in, out, optional] lpDistanceToMoveHighA pointer to the high order 32-bits of the signed 64-bit distance to move.
If you do not need the high order 32-bits, this pointer must be set to NULL.
When not NULL, this parameter also receives the high order DWORD of the new value of the file pointer. For more information, see the Remarks section in this topic.

Or you can use the SetFilePointerEx and specify a qword size to move: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfilepointerex

colinr

Quote from: fearless on September 08, 2023, 12:07:33 AMYou probably need to specify the high order dword to move as well > 2Gb or as you suspected it will use that value as a negative and move back if possible (signed dword value).

I'm specifying both, besides the high order would not be required for 2GB.

jj2007

It would be much easier to help you if you posted code. Now it's just guessing, and I assure you our crystal balls suck :cool:

lpDistanceToMoveHigh

Points to the high-order word of the 64-bit distance to move. If the value of this parameter is NULL, SetFilePointer can operate only on files whose maximum size is 2^32 - 2. If this parameter is specified, the maximum file size is 2^64 - 2. This parameter also receives the high-order word of the new value of the file pointer.

zedd151

To reiterate what fearless and jj2007 are telling you, read this https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfilepointer

It explains what you need to know.
It would be very helpful if you posted the code that is giving you these problems, full source preferred, but the code using the call might help as well, if you don't want to post the full source code for some reason.

colinr

Here is the code extract:

;EDX:EAX = binary value
    lea        eax, Parameter2 ;contains ASCII decimal string
    call    A2Q
    mov        OffsetHigh, edx
    mov        OffsetLow, eax

Followed by:

invoke    SetFilePointer, DHandle, OffsetLow, OffsetHigh, FILE_BEGIN
Sorry for the brevity, just extremely busy.

I'll try and do some debugging with Olly when I get the chance, a million things going on at the moment!

jj2007

Quote from: colinr on September 08, 2023, 03:10:12 AMinvoke    SetFilePointer, DHandle, OffsetLow, OffsetHigh, FILE_BEGIN

SetFilePointer, hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod

Quote from: jj2007 on September 08, 2023, 02:27:04 AMIt would be much easier to help you if you posted code. Now it's just guessing, and I assure you our crystal balls suck :cool:

lpDistanceToMoveHigh

Points to the high-order word of the 64-bit distance to move.

colinr

#28
Quote from: jj2007 on September 08, 2023, 03:55:01 AMPoints to the high-order word of the 64-bit distance to move.


So it should look like this:

invoke    SetFilePointer, DHandle, OffsetLow, addr OffsetHigh, FILE_BEGIN