The purpose of this tutorial is to help you understand how processors understand the hexadecimal and binary number systems. The tutorial talks about how the number systems are all related to eachother, and how they are stored and arithmetic performed. All you need to know is basic algebra skills to learn this.
The decimal numbering system is base 10, meaning it uses 10 different digits to describe numbers (0, 1, 2, 3, 4, 5, 6, 7, 8, 9). The binary number system is very similar only it is a base 2 system, using only 2 different digits to describe numbers, 0 and 1. The smallest value in a computer is a bit, a bit is a binary digit. A bit of 0 represents off, and a bit of 1 represents on.
The bits in a binary number are numbered from right to left starting at 0. Here is a table demonstrating how the binary number 01010101 would be bit numbered:
Bit Number | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
Bit Value | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
Converting between decimal and binary is easy. The bits in a binary number each represent a decimal number, each as the next power of 2. To find the decimal value you would add all the numbers represented by the on bits together. Here is an example of what decimal number each bit represents:
Bit Number | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
Decimal Value | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
Decimal Power | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 |
Here is an example of converting the binary number 01010101 into a decimal number:
Bit Number | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
Bit Value | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
Decimal Numbers | - | 64 | - | 16 | - | 4 | - | 1 |
The hexadecimal number system is a base 16 system, consisting of 16 different digits to represent a number (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F). Hexadecimal is the chosen numbering system to represent many things in a processor including code instructions and data. There are 2 main reasons for using hexadecimal to represent binary numbers:
The hexadecimal digits 0 to F are the same as decimal 0 to 15, and the same as binary 0000 to 1111. Hexadecimal numbers typically have a prefix of 0x or a suffix of h to show that they are hexadecimal. Converting from hexadecimal to binary is a very easy task. For example, here is how you would convert the hexadecimal number 0xB5 to binary:
Step 1: Break it up:
B = 11
5 = 5
Step 2: Convert to binary:
11 = 1011
5 = 0101
Step 3: Put it together:
0xB5 = 10110101
Note: Steps 1, 2, and 3 could easily be combined together.
Converting from hexadecimal to binary is as easy as that. Converting directly from hexadecimal to decimal is a bit harder though. The steps to convert the number 0xF83A03C1 directly to decimal are as follows:
Step 1: Determine the digit place of each:
Digit Place | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
Digit Value | F | 8 | 3 | A | 0 | 3 | C | 1 |
Step 2: Determine the decimal value of each:
Hexadecimal Value | F | 8 | 3 | A | 0 | 3 | C | 1 |
---|---|---|---|---|---|---|---|---|
Decimal Value | 15 | 8 | 3 | 10 | 0 | 3 | 12 | 1 |
Step 3: Multiply each decimal value by 16(Digit Place):
Decimal Value | 15 | 8 | 3 | 10 | 0 | 3 | 12 | 1 |
---|---|---|---|---|---|---|---|---|
Multiple Power | 167 | 166 | 165 | 164 | 163 | 162 | 161 | 160 |
Multiple Value | 268,435,456 | 16,777,216 | 1,048,576 | 65,536 | 4,096 | 256 | 16 | 1 |
Product | 4,026,531,840 | 134,217,728 | 3,145,728 | 655,360 | 0 | 768 | 192 | 1 |
Step 4: Add all the products together:
4,026,531,840 +
134,217,728 + 3,145,728 + 655,360 + 0 + 768 + 192 + 1 =
4,164,551,617
0xF83A03C1 = 4,164,551,617
I gave you a pretty big example there. Fortunately, if you have to convert numbers this huge while programming, you could use the built-in calculator application on your current operating system.
All data in computers is represented by a bunch of basic data types. These data types can be of the same length, but they are just interpreted differently. Here is a list of the basic data type categories:
When a number is "unsigned", that means it doesn't have a special bit which specifies whether it is positive or negative. All unsigned numbers are considered positive. Unsigned numbers can be stored in several data structures of different length. The bigger the structure, the higher the number it can store. Some data structures may be machine dependent, but everything here is based on the Intel x86 architecture.
When a number is "signed", that means there is a special bit that is in the highest position specifying whether the number is positive or negative. A value of 0 indicates a positive number, a value of 1 indicates a negative number. Here is a table showing which bit is the sign bit:
Bit Position | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
Bit Value | * | - | - | - | - | - | - | - |
* = indicates the "sign" bit
Bit Position | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Bit Value | * | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
* = indicates the "sign" bit
You should also know that what type of number or data structure a set of bytes somewhere is intended to be is not kept of by the processor. It is up to you to know where everything is and what it is.
Positive signed numbers are read as expected, in a byte bits 0-6 could be from 0 to 127. However, negative signed numbers are read differently. I can't explain how they are read, but I can explain the method to convert between positive and negative numbers. Suppose you wanted to know the negative equivelant of 56, here is how you would do it:
Step 1: Flip the values of all the bits:
Original: 56 | Bit Position | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|
Bit Value | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | |
New Value | Bit Position | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Bit Value | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 |
Step 2: Add 1 to the new value:
11000111 + 1 = 11001000
11001000 = -56
This method of converting is called Two's Complement. It allows for the following range of numbers in data structures:
This method also allows the standard Intel ADD and SUB instructions to work with no problem while adding or subtracting two signed numbers or two unsigned numbers. Just make sure you never add an unsigned and a signed number together. You may get unexpected results.
I will discuss floating-point numbers and letters in other tutorials, because they tie in more with other stuff. I will discuss the string data structure now though. Notice that I said data structure and not data type. I usually refer to data types as the most basic data structures, and data structures as more complex combinations of the data types.
A string is basically a bunch of similar data types all grouped together. For example, there can be a string of bytes, a string of words, but not a string of bytes and words. Strings should be able to be of any length, and should have a way for the processor to determine what the length is.
There are currently two types of strings that I know of, here they are:
NULL-terminated strings are the most simple, their length and ending point is determined by the character NULL (0). When going through a NULL-terminated string the processor will know when to stop when a 0 is encountered. I prefer this type of string because of its simplicity. The NULL character is not considered part of the string and should not be interpreted as such for compatibility.
Length prefixed strings (as I call them), are not terminated by a character, but their length and ending point is determined by a number at the beginning which says how many data blocks (bytes, words, etc., depends on string) in length the string is.
All strings starting points are determined by a pointer to them. A pointer is simply a number which says where the first memory block or the length number of the string is. Pointers are discussed in much more detail in another tutorial.
- | - | - | - | - | NULL, string ends |
---|---|---|---|---|---|
0x03 | 0x27 | 0xEE | 0x5D | 0xE3 | 0x00 |
Length | - | - | - | - |
---|---|---|---|---|
0x04 | 0x32 | 0x0E | 0xAB | 0x34 |
That concludes the section on strings. The reason I have chosen to discuss them here is that the processor actually has built-in instructions for strings. But it is your job to choose length prefixed or NULL-terminated and pass how many bytes to read onto the processor.
This last section deals with arithmetic operations on bits. Not stuff like addition and subtraction, as you should be able to figure those ones out. But special operations used on bits. Here is a list of the different bit operations:
The AND, OR, and XOR operations operate on two numbers and combine them to produce a third number. They are combined by comparing the individual bits in a number to the individual bits in another number. The corresponding bit in the third number is then set according to the comparison results. Here are tables showing how the different operations compare the bits:
0 | 1 | 0 |
---|---|---|
0 | 1 | 0 |
0 | 1 | - |
0 | 1 | 1 |
---|---|---|
0 | 1 | 1 |
0 | 1 | - |
0 | 1 | 1 |
---|---|---|
0 | 1 | 1 |
0 | 0 | - |
For example, if we did the following operations between the numbers 10010010 and 10110101, we would get the following results:
First Number | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
Second Number | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 1 |
Result | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
First Number | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
Second Number | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 1 |
Result | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 1 |
First Number | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
Second Number | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 1 |
Result | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 |
Bitwise NOT is fairly simple and does not need much explaining. It simply performs a One's Complement on a single number. A One's Complement is like a Two's Complement only in a One's Complement a 1 isn't added after the bits are flipped.
Shifts are quite simple. When performing a right shift you are shifting all the bits to the right and zeros come in on the left. When doing a left shift, all bits go to the left and zeros come in from the right. Shift operations take two operands, a number, and the number of places to shift it. For example, if we wanted to shift the number 00001111 to the left three places, the result would be 01111000. If we shifted 10100000 to the right two places, the result would be 00101000.
Rotations are very similar to shifts, only instead of zeros coming in on one side, the bits that were shifted out on the other side come in. For example, if we rotated 10000001 to the left one place, the result would be 00000011. If we rotated 00001111 to the right four places, the result would be 11110000.
This concludes the number systems tutorial. After reading this you should have a good understanding of binary and hexadecimal, some basic data structures and types, and bit operations. If you have noticed any errors or you have any suggestions, please e-mail me at the address provided on my site.