Link to home
Start Free TrialLog in
Avatar of MrMichael
MrMichael

asked on

Output files in hexadecimal

How do I output numerical values as hexadecimal representations?  I am most interested in outputting 64-bit double hexadecimal format, but I would also like to know the basics of how the other numbers work as well.
Avatar of cup
cup

How are your 64 bit numbers stored?  Is it long int or long long int or as an array?  Are you running on 32 or 64 bit architecture?
>> Is it long int or long long int or as an array?

It might be an _int64.

>> Is it long int or long long int or as an array?

Neither of these create a 64 bit integer, on VC++ v6.0 anyway. "long int" is the same size as int, in fact long and int are synonymous terms. Additionally, "long long int" generates the following error,

test.cpp(77) : error C2632: 'long' followed by 'long' is illegal

MrMichael, for info on the hexadecimal numbering system,

http://webster.cs.ucr.edu/Page_asm/ArtofAssembly/ch01/CH01-2.html#HEADING2-1

Exceter
Avatar of DanRollins
The easiest way to output in hex is as follows:

for each byte that you want to output:
  convert to 2-digit hex code
  write thes two bytes to the output file
continue until all done!

Now here is one way to convert one byte to two hex digits:

BYTE n; // the byte to convert
char szOneByte[3];
sprintf( szOneByte, "%02x", n );

The two hex digits are the first two bytes in szOneByte.  If you will describe exactly what you are attempting to do, and WHY you want to do it, then if necessary, I will describe about byte-ordering, "Big Endian" vs "Little Endian," and all that.  But for simple one-to-one conversion it is not needed.

-- Dan
As I interpreted your question the type you wonder about is a 64 bit double. I.e. floating point format.

This is very machine dependent but in general all floating point formats are stored by decoding a floating point number into 3 parts:

f = s * m * B^e

f is the floating point number.

s = sign = 1 if the number is positive >= 0 and -1 otherwise. Note that it can also be -1 for 0 so -0 is different from +0 (bitwise) in floating point format. Most machines treat them as equal in the sense that a compare of -0 and +0 will report "equal" though even though they are bitwise different. Since s has one of two values it is usually stored using 1 bit with 0 meaning 'positive' and 1 meaning 'negative'.

m is a mantissa, it is  stored as an positive integer number and stores the 'value' of the number. It is usually scaled so that m is in the range 0.5 <= m < 1.0.

e is exponent it is also stored as an integer number, it is typically stored with a bias, i.e. the value 0 isn't stored as 0 but as the value 'bias' and 1 is then 'bias + 1' etc.
so that -1 is stored as 'bias - 1'.

B is the power of the value or scale factor. This is usually the value 2 and isn't stored in the number (it is always fixed) but on some older machines the value could be 16 or some other value.

So a value such as:

s e8..e0 m2 m3 m4..m55  (64 bit)

with e = e8 * 256 + e7 * 128 + e6 * 64 + e5 * 32
          + e4 * 16 + e3 * 8 + e2 * 4 + e1 * 2 + e0

m = 0.5 + m2/4 + m3/8 + m4/16....+m55/2^55

Then the number f equals (-1)^s * m * 2^(e - bias)

For an example, let us examine the number 3.14 (close to pi).

Let's start by finding the binary representation of 3.14

3.14 = 11.001000111101011
3.14 = 3 + 0.14
3 in binary is 11

0.14 in binary is:
0.14 * 2 = 0.28 -> next digit 0
0.28 * 2 = 0.56 -> next digit 0 (*)
0.56 * 2 = 1.12 -> next digit 1
0.12 * 2 = 0.24 -> next digit 0
0.24 * 2 = 0.48 -> next digit 0
0.48 * 2 = 0.96 -> next digit 0
0.96 * 2 = 1.92 -> next digit 1
0.92 * 2 = 1.84 -> next digit 1
0.84 * 2 = 1.68 -> next digit 1
0.68 * 2 = 1.36 -> next digit 1
0.36 * 2 = 0.72 -> next digit 0
0.72 * 2 = 1.44 -> next digit 1
0.44 * 2 = 0.88 -> next digit 0
0.88 * 2 = 1.76 -> next digit 1
0.76 * 2 = 1.52 -> next digit 1
0.52 * 2 = 1.04 -> next digit 1
0.04 * 2 = 0.08 -> next digit 0
0.08 * 2 = 0.16 -> next digit 0
0.16 * 2 = 0.32 -> next digit 0
0.32 * 2 = 0.64 -> next digit 0
0.64 * 2 = 1.28 -> next digit 1
0.28 * 2 = 0.56 -> next digit 0 (**)

Since 0.28 repeats at the (**) the sequence is an infinite
sequence of those bits (i.e. the finite decimal sequence 0.14 cannot be represented in a finite binary sequence.

so:
3.14 = 11.0 [ 01000111101011100001 ]

where the [ ] means repeating binary digits.

The value is then:

3.14 = 1.10 [ 01000111101011100001 ] * 2
     = 0.110 [ 01000111101011100001 ] * 2^2

The inital digit after the binary point is 0.5 as is always 1 due to the way we scale the value so it is not stored and the exponent is 2 + bias.

If bias is 255 then the value stored for exponent is 255 + 2 = 257 = 100000001 in binary.

the sign is positive so the sign bit is 0.

So the value stored is

3.14 = 0 100000001 10 0100 0111 1010 1110 0001
 0100 0111 1010 1110 0001 0100 0111 1010 1

Here I have 1 bit extra at the bottom, normally the floating point uses rounding so the since the rounding bit is set we round the lowest bit up to 1 and so the final bit sequence is:

3.14 = 0 100000001 10 0100 0111 1010 1110 0001 0100 0111 1010 1110 0001 0100 0111 1011

If you represent this in hex you get:

3.14 = 0100 0000 0110 0100 0111 1010 1110 0001 0100 0111 1010 1110 0001 0100 0111 1011
     = 0x4064 7ae1 47ae 147b

Now, to make things more complicated this might not be exactly what you see when you look at a memory dump where a floating point number is stored even if it uses exactly this bias and this representation I have here selected (which is very common). The reason is what is known as endianess of your computer. Computers may store numbers so that more significant bytes are stored at high addresses or they may choose to let less significant bytes be stored at high addresses. For example the number 258 which in hex is 0x0102 may either be stored as the two bytes: 01 02 or it may be stored as 02 01. In other words the value may be stored as: 7b 14 ae 47 e1 7a 64 04 instead of the sequence I indicated. Some machines uses even stranger sequences in which they first group the numbers in two bytes and then store the two bytes sequences in the opposite order. I.e. the value 0x12345678 may be stored as 0x12 0x34 0x56 0x78 or as 0x78 0x56 0x34 0x12 or as 0x56 0x78 0x12 0x34.

Of course, if the computer uses a different bias, the exponent will be represented differently.

In addition to the above I also need to say a few things about 0. 0 is a common value and yet the above representation cannot represent this number, the mantissa is always in the range 0.5 <= m < 1.0.

The solution that most computers uses is to have two special exponent values which aren't treated as other values. The values are the one with all bits set and the one with all bits cleared. An exponent of 0 is equal to 255 and an exponent of -1 is represented as 254, however, all this stops at the exponent value of -254 which is represented as the value '1'. The value is 0 is special and does NOT represent -255.

Instead the value 0 is used to represent two types of numbers:

If m == 0, the value is 0, so (s = ?, M = 0 and E = 0) does not represent the value (-1)^s * M/2^-55 * 2^(E - bias) instead it is used to represent 0.

if the exponent field is 0 and m is not 0 the value is a very small number close to 0. This allows the floating point arithmetic to represent very small values that would otherwise not be represented.

Similarly if the exponent field has the value 256 it represent an exponent of 1, a value of 257 represent a value of 2 etc and a value of 510 represent an exponent of 257. However, a value of 511 does NOT represent 258, instead it is used to repsent the values "infinite" and "not a number". This allows the floating point arithmetic to handle properly values such as atan(inf) and give proper result in this case without going to exception incase you get floating point infinity in a result.

This allows the floating point unit to be programmed to either throw an exception or to simply return the value "infinity" in these cases.

Square root of negative numbers can get the value "not a number" instead of throwing excpetion. Some times this is preferred by the programmer.

Hope this explains how floating point works.

If you want to find the representation on your particular machine you can do it this way:

void hexdump(void * ptr, size_t len)
{
   unsigned char * p = reinterpret_cast<unsigned char *>(ptr);
   unsigned char * end = p + len;

   char buf[3];

   while (p < end) {
      sprintf( buf, "%02x", *p++);
      cout << buf;
   }
}

void do_something()
{
   double d = 3.14;

   cout << "Hexdump of 3.14 is: ";
   hexdump(& d, sizeof(d));
   cout << endl;
}

Alf
Avatar of MrMichael

ASKER

The reason for the 64-bit double numbers is that I am editing a graphics file that has each x,y coordinate stored as a hexadecimal 64-bit double number.  The program then stiches the images together based on the tag coordinates.  Since the images I'm stiching do not overlap, the outer x or y coordinates serve as the tags.  Since I know the coordinates in decimal format (ie. 146) I need to output it in the file with the hexadecimal value of "0000000000406240"

This is done because it would take hours upon hours to move each little tag in every image that you want to stich together.  Editing the file directly can result in this being done as fast as your computer can re-write in that file format.
ASKER CERTIFIED SOLUTION
Avatar of DanRollins
DanRollins
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
This is exactly what I was looking for.  Thank you very much!