Hex Conversion via a Batch File

Occasionally, when something bad happens, an application will return an exception code (which is occasionally an NTSTATUS value). Unfortunately dumping out %ERRORLEVEL% can give you a negative value which makes looking up the error a little harder. Since I was in batch-land, I had the desire to take the value and print it out in a hex format, which drastically simplified my diagnosis efforts.

Below is some batch code which will take a two's-compliment value, and print it out as an unsigned hexadecimal value. The basic algorithm is not very difficult (see 'hexloop' below): take the "ones place" digit off (by mod 16) and stick it in the result, then divide the input down by 16 and repeat. The tricky part comes when we have a negative value and thus need to deal with unsigned values that go beyond the range of a positively signed 32-bit value.

To deal with this we first figure out how far past the MAX_INT value we are (stored in _offset). We then break the number into two parts so that we can operate on them in the positive side of a signed variable. Into each part, we then need to add in the equivalent (also broken apart) MAX_INT pieces. The first part (the first digit) is handled immediately; for the remaining digits, we just fall down into the normal processing loop. Note that we are using the ! syntax for variables inside the 'if' statement as that helps keep the processor from getting confused.

@echo off
setlocal
setlocal ENABLEDELAYEDEXPANSION

call :tohex %1
echo %_DECVAL% == 0x%_HEXVAL%

goto :eof

:tohex
    set _DECVAL=%1
    set _HEXVAL=
    set _VAL=%1
    if %1 LSS 0 (
        REM break the number into two parts so that we can output the
        REM full value within the bounds of a 32 bit signed value
        set /A _offset="-(-2147483647 - !_VAL!) + 2"
        set /A _VAL="!_offset! / 16 + 0x7FFFFFF"
        set /A _P="!_offset! %% 16 + 0xF"

        if !_P! GEQ 16 (
        set /A _VAL="!_VAL! + 1"
        set /A _P="!_P! %% 16"
        )
        if !_P! LEQ 9 set _HEXVAL=!_P!!_HEXVAL!
        if "!_P!" == "10" set _HEXVAL=A!_HEXVAL!
        if "!_P!" == "11" set _HEXVAL=B!_HEXVAL!
        if "!_P!" == "12" set _HEXVAL=C!_HEXVAL!
        if "!_P!" == "13" set _HEXVAL=D!_HEXVAL!
        if "!_P!" == "14" set _HEXVAL=E!_HEXVAL!
        if "!_P!" == "15" set _HEXVAL=F!_HEXVAL!
    )

     :hexloop
    set /A _P="%_VAL% %% 16"
    if %_P% LEQ 9 set _HEXVAL=%_P%%_HEXVAL%
    if "%_P%" == "10" set _HEXVAL=A%_HEXVAL%
    if "%_P%" == "11" set _HEXVAL=B%_HEXVAL%
    if "%_P%" == "12" set _HEXVAL=C%_HEXVAL%
    if "%_P%" == "13" set _HEXVAL=D%_HEXVAL%
    if "%_P%" == "14" set _HEXVAL=E%_HEXVAL%
    if "%_P%" == "15" set _HEXVAL=F%_HEXVAL%
    set /A _VAL="%_VAL% / 16"
    if "%_VAL%" == "0" goto :endloop
    goto :hexloop
     :endloop

    set _offset=
    set _P=
    set _VAL=
    goto :eof