Assigning the value of a "double" variable using HEX

Y

Yipkei Kwok

Hi,

In Fortran, it is possible to assign the value of each bit of a REAL*8
variable using a hexadecimal number with the "equivalence" statement.

For example,

program test_acos

implicit none
real*8 :: x
integer*8 :: i
equivalence (i,x)
i = z'3FE827B9682F47EB'
x = acos(x)
write(*,1400) x
1400 format(Z16)
end program test_acos

How can I do the same thing in C? i.e. How can I assign the exact
value of a "double" value (64-bit) using HEX in C?

Thank you!
 
P

Peter Nilsson

Yipkei Kwok said:
In Fortran, it is possible to assign the value of each
bit of a REAL*8 variable using a hexadecimal number with
the "equivalence" statement.

For example,

      program test_acos

        implicit none
        real*8 :: x
        integer*8 :: i
        equivalence (i,x)
        i = z'3FE827B9682F47EB'
        x = acos(x)
        write(*,1400) x
1400  format(Z16)
      end program test_acos

How can I do the same thing in C? i.e. How can I assign
the exact value of a "double" value (64-bit) using HEX
in C?

C99 has hexadecimal floating point constants, although you
may not have a C99 compiler.

You can overlay a double object with specific bytes using
memcpy, but this ties your code to all sorts of unnecessary
restrictions, like the sizeof a double, the representation
of it, the endianness, etc...

So the questionis: why do you want to do this? Why not just
assign a double value to a double? What is the real problem
you're trying to solve?
 
K

Keith Thompson

Yipkei Kwok said:
In Fortran, it is possible to assign the value of each bit of a REAL*8
variable using a hexadecimal number with the "equivalence" statement.

For example,

program test_acos

implicit none
real*8 :: x
integer*8 :: i
equivalence (i,x)
i = z'3FE827B9682F47EB'
x = acos(x)
write(*,1400) x
1400 format(Z16)
end program test_acos

How can I do the same thing in C? i.e. How can I assign the exact
value of a "double" value (64-bit) using HEX in C?

There are a number of ways to do this.

You can assign the hex constant to a sufficiently large unsigned
integer (probably unsigned long long) and then use memcpy() to copy it
into a double object (that's how I'd probably do it).

You can use a union of an unsigned long long and a double, and assign
the hex constant to the unsigned long long member.

You can use pointer conversions to treat an object of one time
as if it were of another type:
unsigned long long n = 0x3FE827B9682F47EB;
double x;
x = *(double*)&n;
but this can fail badly if the types have different alignment
requirements.

You can use a hexadecimal floating-point constant. It's not the same
as a hexadecimal integer constant; it specifies the floating-point
value, not the representation. But it does let you specify an exact
floating-point value, something that can be difficult or impossible to
do with decimal constants. Note that hexadecimal floating-point
constants are a new feature in C99; not all compilers support them.

Except for the last, all of these techniques are potentially
dangerous. It all assumes that double and unsigned long long (or some
chosen integer type) are the same size. The C language doesn't
guarantee that double is 8 bytes, or even that a byte is 8 bits. It
assumes a certain floating-point representation. Most systems these
days use an IEEE floating-point representation, but some still don't.
Even for those that do, byte ordering can be an issue.

If your program doesn't need to be portable, you might not have to
worry about any of this.
 
K

Keith Thompson

Seebs said:

In the Fortran program, the OP has the hexadecimal *representation* of
the desired double value; C99's hexadecimal floating-point constants
let you specify the *value* in hex. That may well be good enough, but
it's not exactly what the he was asking for.

Note that you might not even need C99-style hex constants for this.
If you use a sufficiently precise decimal floating-point constant,
you're likely to get the right number anyway, though this isn't
quite guaranteed.

Here's an example program based on the number in the original post.
This is horribly non-portable for a variety of reasons, but it does
seem to demonstrate that you can store an exact value using a decimal
literal:

include <stdio.h>
#include <string.h>
int main(void)
{
unsigned long long n;
double x;

n = 0x3FE827B9682F47EB;
memcpy(&x, &n, sizeof x);
printf("n = 0x%llX, x = %.64g\n", n, x);

x = 0.75484915112087713762178964316262863576412200927734375;
memcpy(&n, &x, sizeof n);
printf("n = 0x%llX, x = %.64g\n", n, x);

return 0;
}

The standard says that for a decimal FP constant (C99 6.4.4.2p3):

the result is either the nearest representable value, or the
larger or smaller representable value immediately adjacent to the
nearest representable value, chosen in an implementation-defined
manner.

which I suppose leaves room for error even if the decimal constant
exactly matches the desired representable value.

Note that this method is reasonably portable across systems with
different FP representations (IEEE vs. non-IEEE as well as byte-order
differences).
 
B

bartc

Yipkei Kwok said:
Hi,

In Fortran, it is possible to assign the value of each bit of a REAL*8
variable using a hexadecimal number with the "equivalence" statement.

For example,

program test_acos

implicit none
real*8 :: x
integer*8 :: i
equivalence (i,x)
i = z'3FE827B9682F47EB'
x = acos(x)
write(*,1400) x
1400 format(Z16)
end program test_acos

How can I do the same thing in C? i.e. How can I assign the exact
value of a "double" value (64-bit) using HEX in C?

You can't do the equivalence thing directly. Only by messing about with
casts and addresses and pointers, which is untidy.

I used a macro to access a real*8 as integer*8 (this makes some assumptions
about the availability of these forms in C):

#include <stdio.h>

#define int8 long long int /* adjust as needed */
#define asint8(x) (*(int8*)(&x))

int main(void)
{
double x=1.0;

asint8(x) = 0x400123456789abcd;

printf("X=%f\n",x);
printf("X=%llx\n",asint8(x));

}
 
T

Tim Prince

In the Fortran program, the OP has the hexadecimal *representation* of
the desired double value; C99's hexadecimal floating-point constants
let you specify the *value* in hex. That may well be good enough, but
it's not exactly what the he was asking for.

Note that you might not even need C99-style hex constants for this.
If you use a sufficiently precise decimal floating-point constant,
you're likely to get the right number anyway, though this isn't
quite guaranteed.

Here's an example program based on the number in the original post.
This is horribly non-portable for a variety of reasons, but it does
seem to demonstrate that you can store an exact value using a decimal
literal:

include<stdio.h>
#include<string.h>
int main(void)
{
unsigned long long n;
double x;

n = 0x3FE827B9682F47EB;
memcpy(&x,&n, sizeof x);
printf("n = 0x%llX, x = %.64g\n", n, x);

x = 0.75484915112087713762178964316262863576412200927734375;
memcpy(&n,&x, sizeof n);
printf("n = 0x%llX, x = %.64g\n", n, x);

return 0;
}

The standard says that for a decimal FP constant (C99 6.4.4.2p3):

the result is either the nearest representable value, or the
larger or smaller representable value immediately adjacent to the
nearest representable value, chosen in an implementation-defined
manner.

which I suppose leaves room for error even if the decimal constant
exactly matches the desired representable value.
glibc has the benefit of expert work in this area.
The IEEE 754 standard makes the strongest practical prescription. Among
the requirements (by my personal inexact paraphrase), the "nearest
reprsentable value" must be achieved, e.g. for double x, in the range
0.1 < fabs(x) <= 1/DBL_EPSILON
so in the case above it should be possible to achieve nearest representable.
Outside that range, a tolerance of 0.47 ULP is allowed, which seems to
be slightly stricter than the C standard.
 
K

Keith Thompson

bartc said:
I used a macro to access a real*8 as integer*8 (this makes some
assumptions about the availability of these forms in C):

#include <stdio.h>

#define int8 long long int /* adjust as needed */

Given that the value to be assigned is expressed as a 16-digit
hexadecimal number, unsigned long long int would probably be a better
choice (even though the example in the original post is less than
2**31).

And why not use a typedef rather than a macro? For that matter, why
not juse use uint64_t?
#define asint8(x) (*(int8*)(&x))

I'd probably put parentheses around the x in the definition. I can't
think of a case where it matters, but it can't hurt.
int main(void)
{
double x=1.0;

asint8(x) = 0x400123456789abcd;

As I mentioned earlier, this can fail badly if int8 and double have
different alignment requirements. memcpy() neatly avoids that
problem.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,480
Members
44,900
Latest member
Nell636132

Latest Threads

Top