3.4 The Hexadecimal Numbering System
A big problem with the binary system is verbosity. To represent the value 20210 requires eight binary digits. The decimal version requires only three decimal digits and, thus, represents numbers much more compactly than does the binary numbering system. This fact was not lost on the engineers who designed binary computer systems. When dealing with large values, binary numbers quickly become too unwieldy. Unfortunately, the computer thinks in binary, so most of the time it is convenient to use the binary numbering system. Although we can convert between decimal and binary, the conversion is not a trivial task. The hexadecimal (base 16) numbering system solves these problems. Hexadecimal numbers offer the two features we're looking for: they're very compact, and it's simple to convert them to binary and vice versa. Because of this, most computer systems engineers use the hexadecimal numbering system. Since the radix (base) of a hexadecimal number is 16, each hexadecimal digit to the left of the hexadecimal point represents some value times a successive power of 16. For example, the number 123416 is equal to:1 * 163 + 2 * 162 + 3 * 161 + 4 * 160
or4096 + 512 + 48 + 4 = 466010.
Each hexadecimal digit can represent one of sixteen values between 0 and 1510. Since there are only ten decimal digits, we need to invent six additional digits to represent the values in the range 1010 through 1510. Rather than create new symbols for these digits, we'll use the letters A through F. The following are all examples of valid hexadecimal numbers:
123416 DEAD16 BEEF16 0AFB16 FEED16 DEAF16
Since we'll often need to enter hexadecimal numbers into the computer system, we'll need a different mechanism for representing hexadecimal numbers. After all, on most computer systems you cannot enter a subscript to denote the radix of the associated value. We'll adopt the following conventions:
- All hexadecimal values begin with a "$" character, e.g., $123A4.
- All binary values begin with a percent sign ("%").
- Decimal numbers do not have a prefix character.
- If the radix is clear from the context, this text may drop the leading "$" or "%" character.
Examples of valid hexadecimal numbers:
$1234 $DEAD $BEEF $AFB $FEED $DEAF
As you can see, hexadecimal numbers are compact and easy to read. In addition, you can easily convert between hexadecimal and binary. Consider the following table:
Table 4: Binary/Hex Conversion Binary Hexadecimal %0000 $0 %0001 $1 %0010 $2 %0011 $3 %0100 $4 %0101 $5 %0110 $6 %0111 $7 %1000 $8 %1001 $9 %1010 $A %1011 $B %1100 $C %1101 $D %1110 $E %1111 $F
This table provides all the information you'll ever need to convert any hexadecimal number into a binary number or vice versa.
To convert a hexadecimal number into a binary number, simply substitute the corresponding four bits for each hexadecimal digit in the number. For example, to convert $ABCD into a binary value, simply convert each hexadecimal digit according to the table above:
0 A B C D Hexadecimal
0000 1010 1011 1100 1101 Binary
To convert a binary number into hexadecimal format is almost as easy. The first step is to pad the binary number with zeros to make sure that there is a multiple of four bits in the number. For example, given the binary number 1011001010, the first step would be to add two bits to the left of the number so that it contains 12 bits. The converted binary value is 001011001010. The next step is to separate the binary value into groups of four bits, e.g., 0010_1100_1010. Finally, look up these binary values in the table above and substitute the appropriate hexadecimal digits, i.e., $2CA. Contrast this with the difficulty of conversion between decimal and binary or decimal and hexadecimal!
Since converting between hexadecimal and binary is an operation you will need to perform over and over again, you should take a few minutes and memorize the table above. Even if you have a calculator that will do the conversion for you, you'll find manual conversion to be a lot faster and more convenient when converting between binary and hex.
3.5 Arithmetic Operations on Binary and Hexadecimal Numbers
There are several operations we can perform on binary and hexadecimal numbers. For example, we can add, subtract, multiply, divide, and perform other arithmetic operations. Although you needn't become an expert at it, you should be able to, in a pinch, perform these operations manually using a piece of paper and a pencil. Having just said that you should be able to perform these operations manually, the correct way to perform such arithmetic operations is to have a calculator that does them for you. There are several such calculators on the market; the following table lists some of the manufacturers who produce such devices:
Some manufacturers of Hexadecimal Calculators (circa 2002):
This list is by no means exhaustive. Other calculator manufacturers probably produce these devices as well. The Hewlett-Packard devices are arguably the best of the bunch . However, they are more expensive than the others. Sharp and Casio produce units which sell for well under $50. If you plan on doing any assembly language programming at all, owning one of these calculators is essential.
To understand why you should spend the money on a calculator, consider the following arithmetic problem:$9 + $1 ----
You're probably tempted to write in the answer "$10" as the solution to this problem. But that is not correct! The correct answer is ten, which is "$A", not sixteen which is "$10". A similar problem exists with the arithmetic problem:$10 - $1 ----
You're probably tempted to answer "$9" even though the true answer is "$F". Remember, this problem is asking "what is the difference between sixteen and one?" The answer, of course, is fifteen which is "$F".
Even if the two problems above don't bother you, in a stressful situation your brain will switch back into decimal mode while you're thinking about something else and you'll produce the incorrect result. Moral of the story - if you must do an arithmetic computation using hexadecimal numbers by hand, take your time and be careful about it. Either that, or convert the numbers to decimal, perform the operation in decimal, and convert them back to hexadecimal.
3.6 A Note About Numbers vs. Representation
Many people confuse numbers and their representation. A common question beginning assembly language students have is "I've got a binary number in the EAX register, how do I convert that to a hexadecimal number in the EAX register?" The answer is "you don't." Although a strong argument could be made that numbers in memory or in registers are represented in binary, it's best to view values in memory or in a register as abstract numeric quantities. Strings of symbols like 128, $80, or %1000_0000 are not different numbers; they are simply different representations for the same abstract quantity that we often refer to as "one hundred twenty-eight." Inside the computer, a number is a number regardless of representation; the only time representation matters is when you input or output the value in a human readable form.
Human readable forms of numeric quantities are always strings of characters. To print the value 128 in human readable form, you must convert the numeric value 128 to the three-character sequence `1' followed by `2' followed by `8'. This would provide the decimal representation of the numeric quantity. If you prefer, you could convert the numeric value 128 to the three character sequence "$80". It's the same number, but we've converted it to a different sequence of characters because (presumably) we wanted to view the number using hexadecimal representation rather than decimal. Likewise, if we want to see the number in binary, then we must convert this numeric value to a string containing a one followed by seven zeros.
By default, HLA displays all byte, word, and dword variables using the hexadecimal numbering system when you use the stdout.put routine. Likewise, HLA's stdout.put routine will display all register values in hex. Consider the following program that converts values input as decimal numbers to their hexadecimal equivalents:program ConvertToHex; #include( "stdlib.hhf" ); static value: int32; begin ConvertToHex; stdout.put( "Input a decimal value:" ); stdin.get( value ); mov( value, eax ); stdout.put( "The value ", value, " converted to hex is $", eax, nl ); end ConvertToHex; Program 3.11 Decimal to Hexadecimal Conversion Program
In a similar fashion, the default input base is also hexadecimal for registers and byte, word, or dword variables. The following program is the converse of the one above- it inputs a hexadecimal value and outputs it as decimal:program ConvertToDecimal; #include( "stdlib.hhf" ); static value: int32; begin ConvertToDecimal; stdout.put( "Input a hexadecimal value: " ); stdin.get( ebx ); mov( ebx, value ); stdout.put( "The value $", ebx, " converted to decimal is ", value, nl ); end ConvertToDecimal; Program 3.12 Hexadecimal to Decimal Conversion Program
Just because the HLA stdout.put routine chooses decimal as the default output base for int8, int16, and int32 variables doesn't mean that these variables hold "decimal" numbers. Remember, memory and registers hold numeric values, not hexadecimal or decimal values. The stdout.put routine converts these numeric values to strings and prints the resulting strings. The choice of hexadecimal vs. decimal output was a design choice in the HLA language, nothing more. You could very easily modify HLA so that it outputs registers and byte, word, or dword variables as decimal values rather than as hexadecimal. If you need to print the value of a register or byte, word, or dword variable as a decimal value, simply call one of the putiX routines to do this. The stdout.puti8 routine will output its parameter as an eight-bit signed integer. Any eight-bit parameter will work. So you could pass an eight-bit register, an int8 variable, or a byte variable as the parameter to stdout.puti8 and the result will always be decimal. The stdout.puti16 and stdout.puti32 provide the same capabilities for 16-bit and 32-bit objects. The following program demonstrates the decimal conversion program (Program 3.12 above) using only the EAX register (i.e., it does not use the variable iValue ):program ConvertToDecimal2; #include( "stdlib.hhf" ); begin ConvertToDecimal2; stdout.put( "Input a hexadecimal value: " ); stdin.get( ebx ); stdout.put( "The value $", ebx, " converted to decimal is " ); stdout.puti32( ebx ); stdout.newln(); end ConvertToDecimal2; Program 3.13 Variable-less Hexadecimal to Decimal Converter
Note that HLA's stdin.get routine uses the same default base for input as stdout.put uses for output. That is, if you attempt to read an int8, int16, or int32 variable, the default input base is decimal. If you attempt to read a register or byte, word, or dword variable, the default input base is hexadecimal. If you want to change the default input base to decimal when reading a register or a byte, word, or dword variable, then you can use stdin.geti8, stdin.geti16, or stdin.geti32.
If you want to go in the opposite direction, that is you want to input or output an int8, int16, or int32 variable as a hexadecimal value, you can call the stdout.putb, stdout.putw, stdout.putd, stdin.getb, stdin.getw, or stdin.getd routines. The stdout.putb, stdout.putw, and stdout.putd routines write eight-bit, 16-bit, or 32-bit objects as hexadecimal values. The stdin.getb, stdin.getw, and stdin.getd routines read eight-bit, 16-bit, and 32-bit values respectively; they return their results in the AL, AX, or EAX registers. The following program demonstrates the use of a few of these routines:program HexIO; #include( "stdlib.hhf" ); static i32: int32; begin HexIO; stdout.put( "Enter a hexadecimal value: " ); stdin.getd(); mov( eax, i32 ); stdout.put( "The value you entered was $" ); stdout.putd( i32 ); stdout.newln(); end HexIO; Program 3.14 Demonstration of stdin.getd and stdout.putd