Skip to main content

How to Convert Between Hexadecimal and Decimal in Batch Script (Hex to Dec and viceversa)

While batch scripting primarily uses decimal (base-10) numbers for its arithmetic operations via set /a, interactions with external systems, configuration files, registry entries, or low-level data often involve hexadecimal (base-16) values. Being able to seamlessly convert numbers between these two bases directly within your batch scripts is a powerful capability for automation and data manipulation.

This guide will provide two robust batch script subroutines:

  • :toDec for converting hexadecimal strings to their decimal equivalents,
  • and :toHex for converting decimal integers (positive or negative) into their 32-bit hexadecimal string representations.

We'll break down how each script works and show you how to integrate them into your own batch files.

Why Convert Between Hexadecimal and Decimal in Batch?

  • Hexadecimal (Hex): Base-16, uses digits 0-9 and letters A-F. Common for representing memory addresses, color codes, data flags, error codes, and in various low-level computing contexts.
  • Decimal (Dec): Base-10, the standard number system for everyday use and for most arithmetic operations within batch scripts (set /a).

The need for conversion arises when:

  • You receive hexadecimal input (e.g., from a command output, file, or registry) that needs to be used in decimal-based calculations or comparisons within your batch script.
  • You have a decimal value that needs to be formatted or sent as a hexadecimal string to an external program, API, or configuration.

Converting Hexadecimal to Decimal (:toDec)

This subroutine takes a hexadecimal string and converts it to its decimal integer equivalent.

The :toDec Subroutine Script

@echo off
REM --- Main script example area ---
CALL :toDec "FF" result_decimal
ECHO Hex FF is Decimal: %result_decimal%

CALL :toDec "1A3" another_decimal
ECHO Hex 1A3 is Decimal: %another_decimal%

ECHO Hex B is Decimal:
CALL :toDec "B"
ECHO ---
REM --- End of main script example, use GOTO :EOF or EXIT /B for real scripts ---
GOTO :EOF


:toDec hex dec -- convert a hexadecimal number to decimal
:: -- hex [in] - hexadecimal number to convert
:: -- dec [out,opt] - variable to store the converted decimal number in
SETLOCAL
set /a dec_value=0x%~1
( ENDLOCAL & REM RETURN VALUES
IF "%~2" NEQ "" (SET %~2=%dec_value%) ELSE ECHO.%dec_value%
)
EXIT /b

Output:

Hex FF is Decimal: 255
Hex 1A3 is Decimal: 419
Hex B is Decimal:
11

How :toDec Works: set /a with the 0x Prefix**

The conversion is elegantly handled by the set /a command, which is batch's arithmetic evaluator.

  • set /a dec_value=0x%~1:
    • %~1: Takes the first argument passed to the subroutine (the hex string, e.g., FF) and removes any surrounding quotes.
    • 0x%~1: Prefixes the hex string with 0x (e.g., 0xFF).
    • When set /a encounters a number prefixed with 0x, it automatically interprets it as a hexadecimal value and converts it to its decimal representation for the assignment. So, 0xFF is internally converted to decimal 255, which is then stored in dec_value.

Usage Examples for :toDec

  • Store result in a variable:

    CALL :toDec "C8" myDecVar
    ECHO Value of C8 is %myDecVar%

    Output:

    Value of C8 is 200
  • Direct output (echo):

    ECHO Decimal for 20 (hex):
    CALL :toDec "20"

    Output:

    Decimal for 20 (hex):
    32

Limitations of :toDec

  • Input Validation: The script doesn't rigorously check if the input is a valid hex string. Invalid hex characters might be treated as 0 by set /a, or cause an error if the string is completely unparseable by set /a's hex parser.
  • Number Size: set /a operates on 32-bit signed integers. Hexadecimal values representing numbers outside the approximate range of -2,147,483,648 to 2,147,483,647 might lead to incorrect results or wrap-around behavior. For typical 8-bit to 32-bit hex values (e.g., 00 to FFFFFFFF), this is generally suitable.

Converting Decimal to Hexadecimal (:toHex)

This subroutine converts a decimal integer (positive or negative) into its 32-bit, 8-character hexadecimal string representation.

The :toHex Subroutine Script

@echo off
REM --- Main script example area ---
CALL :toHex 255 result_hex_FF
ECHO Decimal 255 is Hex: %result_hex_FF%

CALL :toHex -1 result_hex_neg1
ECHO Decimal -1 is Hex: %result_hex_neg1%

ECHO Hex for 42 is:
CALL :toHex 42
ECHO ---
REM --- End of main script example, use GOTO :EOF or EXIT /B for real scripts ---
GOTO :EOF


:toHex dec hex -- convert a decimal number to hexadecimal, i.e. -20 to FFFFFFEC or 26 to 0000001A
:: -- dec [in] - decimal number to convert
:: -- hex [out,opt] - variable to store the converted hexadecimal number in
SETLOCAL ENABLEDELAYEDEXPANSION
set /a dec_input_val=%~1
set "hex_output_str="
set "hex_map=0123456789ABCDEF"
for /L %%N in (1,1,8) do (
set /a "current_nibble=dec_input_val&15,dec_input_val>>=4"
for %%D in (!current_nibble!) do set "hex_output_str=!hex_map:~%%D,1!!hex_output_str!"
)
rem !!!! To remove leading zeros (e.g., 1A instead of 0000001A), uncomment the next line.
rem for /f "tokens=* delims=0" %%A in ("!hex_output_str!") do (set "hex_output_str=%%A" & if not defined hex_output_str set "hex_output_str=0")

( ENDLOCAL & REM RETURN VALUES
IF "%~2" NEQ "" (SET %~2=%hex_output_str%) ELSE ECHO.%hex_output_str%
)
EXIT /b

Output:

Decimal 255 is Hex: 000000FF
Decimal -1 is Hex: FFFFFFFF
Hex for 42 is:
0000002A
---

How :toHex Works: Bitwise Operations, Mapping, and Delayed Expansion

  • SETLOCAL ENABLEDELAYEDEXPANSION: Essential because variables (current_nibble, hex_output_str) are modified and read within the FOR loop. Delayed expansion (!variable!) allows accessing the current runtime value of a variable inside a code block.
  • set /a dec_input_val=%~1: Ensures the input decimal is treated as a 32-bit signed integer.
  • hex_map=0123456789ABCDEF: A lookup string that maps decimal values 0-15 to their hexadecimal character representations.
  • for /L %%N in (1,1,8) do (...): This loop runs exactly 8 times, once for each of the 8 hexadecimal digits (nibbles) in a 32-bit integer.
    • set /a "current_nibble=dec_input_val&15,dec_input_val>>=4": This is the core calculation, performing two operations at once:
      • dec_input_val&15: A bitwise AND with 15 (binary 1111) isolates the lowest 4 bits of dec_input_val. This gives a decimal value from 0 to 15, which is the value of the current hex digit (nibble). The result is stored in current_nibble.
      • dec_input_val>>=4: This performs a bitwise arithmetic right shift on dec_input_val by 4 positions. This discards the 4 bits we just processed and prepares the next 4 bits for the next loop iteration. For negative numbers, this correctly propagates the sign bit, maintaining the proper two's complement representation.
    • for %%D in (!current_nibble!) do set "hex_output_str=!hex_map:~%%D,1!!hex_output_str!": This is a classic batch trick used because you cannot directly use one variable as an index for a substring operation on another variable (e.g., !hex_map:~!current_nibble!,1! is invalid).
      • !current_nibble!: Gets the 0-15 value calculated in the previous step.
      • The FOR %%D loop iterates just once, with its variable %%D taking on the value of !current_nibble!.
      • !hex_map:~%%D,1!: Now that the numeric value is in %%D, we can use it to extract one character from the hex_map. For example, if current_nibble is 10, %%D becomes 10, and this extracts 'A'.
      • The new hex character is prepended to hex_output_str. This correctly builds the final hex string because we are processing nibbles from least significant (rightmost) to most significant (leftmost).

Understanding the 32-bit Hexadecimal Output and Optional Leading Zero Removal

This script generates an 8-character hexadecimal string, representing the full 32-bit integer value.

  • Positive numbers will have leading zeros (e.g., decimal 26 becomes 0000001A).
  • Negative numbers are represented in their 32-bit two's complement form (e.g., decimal -1 becomes FFFFFFFF). The commented-out line rem for /f "tokens=* delims=0" %%A_val in ("!hex_output_str!") do ... can be uncommented (remove rem) to strip these leading zeros for positive numbers, resulting in, for example, 1A instead of 0000001A. It also handles the case where the result is 0.

Usage Examples for :toHex

  • Store result in a variable:
    CALL :toHex 4095 myHexVar
    ECHO Decimal 4095 is Hex: %myHexVar%
    Output:
    Decimal 4095 is Hex: 00000FFF
  • Direct output (echo):
    ECHO Hex for 160:
    CALL :toHex 160
    Output:
    Hex for 160:
    000000A0

Key Batch Scripting Concepts Used in Both Subroutines

SETLOCAL and ENDLOCAL for Scope Management

These commands are crucial for creating modular subroutines. SETLOCAL starts a new environment scope. Variables created or modified within this scope are local and do not affect the caller's environment, unless explicitly passed back. ENDLOCAL discards this local scope.

Parameter Handling (%~1, %~2)

Batch subroutines receive arguments as positional parameters: %1 for the first, %2 for the second, and so on. The ~ modifier (e.g., %~1) removes any surrounding quotation marks from the passed argument, which is useful for handling inputs that might be quoted.

"Returning" Values from Subroutines

Batch doesn't have direct function return statements like other languages. Values are typically "returned" by:

  1. Setting an environment variable in the caller's scope. This is achieved by the ( ENDLOCAL & IF "%~2" NEQ "" (SET %~2=%value_to_return%) ...) construct. The assignment SET %~2=%value_to_return% happens after ENDLOCAL but within the same command processing block, allowing it to affect the parent environment.
  2. ECHOing the value to standard output, which the caller can capture if needed (e.g., using a FOR /F loop). Both subroutines do this if an output variable name is not provided.

Conclusion: A Complete Hex-Decimal Conversion Toolkit

These two subroutines, :toDec and :toHex, provide a powerful and self-contained toolkit for converting numbers between hexadecimal and decimal representations directly within your Windows Batch scripts.

  • :toDec leverages set /a's built-in understanding of the 0x prefix for straightforward hex-to-decimal conversion.
  • :toHex employs bitwise arithmetic and string manipulation with delayed expansion to convert decimal numbers (including negatives via two's complement) to their 32-bit hexadecimal string form.

By understanding their mechanisms and how to call them, you can greatly enhance the numerical processing capabilities of your batch scripts when dealing with these common number bases.