printing bit representation of floats

B

ben

i'm learning about the floating point format that's used on my computer
(apple mac so powerpc)

i've written a function to print out the bits in a float to see how
floats are represented and i also have a software programmer's
calculator called BinCalc which shows the bits of whatever number.

the bits that my code and the bits that the calculator show, for the
same value, don't match. for example for the number 1.0 my code says:
10111111_00000000_00000000_10000000

and the calculator says:
00111111_10000000_00000000_00000000


and for the value 1.6 my code prints:
10111111_11001101_11001100_11001100

and the calculator says:
00111111_11001100_11001100_11001101

the code that's used to print the float bits is below.

the first obvious difference is the left bit, the high order bit.
that's the bit that says if the value's negative or posative, the sign
bit right? so it really looks like there's something wrong with my code
becuase neither values in the above two examples are negative but both
print outs from my code has the left most bit set to 1. and you'd have
thought that the format that the software calculator is using would be
the same format that my computer's using, so the two representations
should tally i'd have thought. anyone know what's going on?

thanks, ben.


#include <stdio.h>

void bitfloatprint(float f)
{
unsigned bytes = sizeof(float); // number of bytes in a float
char bits; // number of bits to shift mask over by

while( bytes ) { // one loop per byte in the float (HO to LO)
for( bits = 7; bits >= 0; bits-- ) {
putchar(
( *((unsigned char *)&f + bytes) & 1 << bits ) == 0 ? '0' : '1'
);
// casting in above line so that +1 means plus
// one byte rather than plus one float
}
if( bytes != 1 )
putchar('_');
bytes--;
}
putchar('\n');
}

int main(void)
{
float f = 1.0;
bitfloatprint(f);
return 0;
}
 
B

ben

ahh! typical. sorry, forget this. sussed it just after i posted. the
main while loop wasn't going from HO byte to LO byte like it should
have been -- it was actually going the other way. so it should be:


#include <stdio.h>

void bitfloatprint(float f)
{
unsigned bytes = 0;
char bits;

while( bytes < sizeof(float) ) {
for( bits = 7; bits >= 0; bits-- ) {
putchar(
( (*((unsigned char *)&f + bytes)) & 1 << bits ) == 0 ? '0' : '1'
);
}
if( bytes != 3 )
putchar('_');
bytes++;
}
putchar('\n');
}

int main(void)
{
float f = 1.6;
bitfloatprint(f);
return 0;
}
 
M

Martin Ambuhl

ben said:
i'm learning about the floating point format that's used on my computer
(apple mac so powerpc)

i've written a function to print out the bits in a float to see how
floats are represented and i also have a software programmer's
calculator called BinCalc which shows the bits of whatever number.

the bits that my code and the bits that the calculator show, for the
same value, don't match.


/* There is no reason to suppose that your calculator and your Apple
* have the same representation for floating point numbers. Try the
* following code out. Note that the output of my implementation is
* very different from what you report. */
#include <stdio.h>
#include <string.h>

#define showhex(x, s)\
{\
unsigned char c[sizeof x];\
size_t i;\
memcpy(c,&x,sizeof x);\
printf("%Lg (%s, size = %lu) as hex: \n%4s",\
(long double)x, s, \
(unsigned long) sizeof x, "");\
for (i = 0; i < sizeof x; i++)\
printf("%02x ", c);\
printf("\n");\
}

int main(void)
{
long double xl;
double x;
float xf;
xf = x = xl = 1.0;
printf("[Output for this implementation]\n\n");
printf("For 1.0, Ben's program would yield in hex: "
"BF 00 00 80\n" "and his calculator: 3F 00 00 80\n");
showhex(xf, "float");
showhex(x, "double");
showhex(xl, "long double");
xf = x = xl = 1.6;
printf("\nFor 1.6, Ben's program would yield in hex: "
"BF CD CC CC\n" "and his calculator: 3F CC CC CD\n");
showhex(xf, "float");
showhex(x, "double");
showhex(xl, "long double");
return 0;
}

[Output for this implementation]

For 1.0, Ben's program would yield in hex: BF 00 00 80
and his calculator: 3F 00 00 80
1 (float, size = 4) as hex:
00 00 80 3f
1 (double, size = 8) as hex:
00 00 00 00 00 00 f0 3f
1 (long double, size = 12) as hex:
00 00 00 00 00 00 00 80 ff 3f 00 00

For 1.6, Ben's program would yield in hex: BF CD CC CC
and his calculator: 3F CC CC CD
1.6 (float, size = 4) as hex:
cd cc cc 3f
1.6 (double, size = 8) as hex:
9a 99 99 99 99 99 f9 3f
1.6 (long double, size = 12) as hex:
00 d0 cc cc cc cc cc cc ff 3f 00 00
 
B

ben

/* There is no reason to suppose that your calculator and your Apple
* have the same representation for floating point numbers. Try the
* following code out. Note that the output of my implementation is
* very different from what you report. */

Martin,

as you've probably seen, i made a mistake in the code and was correct
to assume that the softaware calculator's format is the same as the
computer's format (which does make sense as that calculator is designed
for programming on this computer, so you'd hope they'd be using the
same representation). here's the output from your code:

my output:
3f 80 00 00
3f f0 00 00 00 00 00 00
3f f0 00 00 00 00 00 00

3f cc cc cd
3f f9 99 99 99 99 99 9a
3f f9 99 99 99 99 99 9a

your output:
00 00 80 3f
00 00 00 00 00 00 f0 3f
00 00 00 00 00 00 00 80 ff 3f 00 00

cd cc cc 3f
9a 99 99 99 99 99 f9 3f
00 d0 cc cc cc cc cc cc ff 3f 00 00

(note the hex value you'd noted in the print statement for the value of
1.0 from my calculator: 3F 00 00 80 was slightly wrong. it was 3F 80 00
00 from the calculator)

the only difference between the float and double versions is endianess
-- mine big, yours little. but long double's pretty different to say
the least. i got this warning for that: warning: use of `long double'
type; its size may change in a future release. i'm using a slightly old
compiler (2002) so maybe that has changed by now. who knows.

thanks a lot, ben.
 
M

Michael Mair

ben said:
Martin,

as you've probably seen, i made a mistake in the code and was correct
to assume that the softaware calculator's format is the same as the
computer's format (which does make sense as that calculator is designed
for programming on this computer, so you'd hope they'd be using the
same representation). here's the output from your code:

my output:
3f 80 00 00
3f f0 00 00 00 00 00 00
3f f0 00 00 00 00 00 00

3f cc cc cd
3f f9 99 99 99 99 99 9a
3f f9 99 99 99 99 99 9a

your output:
00 00 80 3f
00 00 00 00 00 00 f0 3f
00 00 00 00 00 00 00 80 ff 3f 00 00

cd cc cc 3f
9a 99 99 99 99 99 f9 3f
00 d0 cc cc cc cc cc cc ff 3f 00 00

(note the hex value you'd noted in the print statement for the value of
1.0 from my calculator: 3F 00 00 80 was slightly wrong. it was 3F 80 00
00 from the calculator)

the only difference between the float and double versions is endianess
-- mine big, yours little. but long double's pretty different to say
the least. i got this warning for that: warning: use of `long double'
type; its size may change in a future release. i'm using a slightly old
compiler (2002) so maybe that has changed by now. who knows.

Umh, have you actually run Martin's program on your machine?
Then the output should be the same.
That the representation of floating point types is (but for
endianness) the same across different machines is not at all
guaranteed!


Cheers
Michael
 
M

Michael Mair

ben said:
ahh! typical. sorry, forget this. sussed it just after i posted. the
main while loop wasn't going from HO byte to LO byte like it should
have been -- it was actually going the other way. so it should be:


#include <stdio.h>

void bitfloatprint(float f)
{
unsigned bytes = 0;
char bits;

It is possible that char is an unsigned integer type, so you
should say explicitly that you want the signed flavour:
signed char bits;
Apart from that: bytes and bits both are loop counters for
you, having similar ranges. It makes not much sense to use
so much different types. Either use unsigned or signed int
for both or unsigned or signed char. For unsigned integer
loop variables, you would have to change the bits loop condition.
while( bytes < sizeof(float) ) {
If you make this sizeof f, your code is essentially completely
independent of the type of f. This makes copy&paste errors
much less likely.
for( bits = 7; bits >= 0; bits-- ) {
bits = CHAR_BIT-1;
for the portable variant.
Considering that you used a while outer loop, you may want
to look at
bits = CHAR_BIT;
while (bits-- != 0)
which works also for unsigned types.
putchar(
( (*((unsigned char *)&f + bytes)) & 1 << bits ) == 0 ? '0' : '1'
);

There are different opinions about readability but another variant
is
'0' + ( ( ((unsigned char *)&f)[bytes] & 1<<bits ) != 0 )

my preference would be rather to introduce an unsigned char *c
with c=&f and use this instead for any of the both ways or a
mixture.
}
if( bytes != 3 )
putchar('_');
bytes++;
}
putchar('\n');
}

int main(void)
{
float f = 1.6;
bitfloatprint(f);
return 0;
}


Cheers
Michael
 
M

Michael Mair

ben said:
yes i have. that's it there where it says my output.

Ok, so I misunderstood; from what you wrote I got the impression
that you are comparing the hex-output of your calculator with
Martin's original output instead of comparing the output of
your calculator, programm and Martin's programm...


Cheers
Michael
 
B

ben

Michael Mair said:
ben said:
ahh! typical. sorry, forget this. sussed it just after i posted. the
main while loop wasn't going from HO byte to LO byte like it should
have been -- it was actually going the other way. so it should be:


#include <stdio.h>

void bitfloatprint(float f)
{
unsigned bytes = 0;
char bits;

It is possible that char is an unsigned integer type, so you
should say explicitly that you want the signed flavour:
signed char bits;
Apart from that: bytes and bits both are loop counters for
you, having similar ranges. It makes not much sense to use
so much different types. Either use unsigned or signed int
for both or unsigned or signed char. For unsigned integer
loop variables, you would have to change the bits loop condition.
while( bytes < sizeof(float) ) {
If you make this sizeof f, your code is essentially completely
independent of the type of f. This makes copy&paste errors
much less likely.
for( bits = 7; bits >= 0; bits-- ) {
bits = CHAR_BIT-1;
for the portable variant.
Considering that you used a while outer loop, you may want
to look at
bits = CHAR_BIT;
while (bits-- != 0)
which works also for unsigned types.
putchar(
( (*((unsigned char *)&f + bytes)) & 1 << bits ) == 0 ? '0' : '1'
);

There are different opinions about readability but another variant
is
'0' + ( ( ((unsigned char *)&f)[bytes] & 1<<bits ) != 0 )

my preference would be rather to introduce an unsigned char *c
with c=&f and use this instead for any of the both ways or a
mixture.
}
if( bytes != 3 )
putchar('_');
bytes++;
}
putchar('\n');
}

int main(void)
{
float f = 1.6;
bitfloatprint(f);
return 0;
}


ok thanks. not sure why i used byte size chunks and two loops. one's
got to be better than two.


#include <stdio.h>
#include <limits.h>

int main(void)
{
void bitfloatprint(float f);
float f;

f = 1.9;
bitfloatprint(f);
return 0;
}

void bitfloatprint(float f)
{
unsigned char bits = sizeof f * CHAR_BIT;

while( bits-- > 0 ) {
putchar( ( *((unsigned *) &f) >> bits & 1 ) + '0' );
if( !(bits % CHAR_BIT) && bits != 0 )
putchar('_');
}
putchar('\n');
}
 
B

ben

ok thanks. not sure why i used byte size chunks and two loops. one's
got to be better than two.


#include <stdio.h>
#include <limits.h>

int main(void)
{
void bitfloatprint(float f);
float f;

f = 1.9;
bitfloatprint(f);
return 0;
}

void bitfloatprint(float f)
{
unsigned char bits = sizeof f * CHAR_BIT;

while( bits-- > 0 ) {
putchar( ( *((unsigned *) &f) >> bits & 1 ) + '0' );
if( !(bits % CHAR_BIT) && bits != 0 )
putchar('_');
}
putchar('\n');
}


ah, i've just thought, that won't work on little endian machines i
don't think. oh well, not to worry, that's enough on this i think.
 
M

Michael Mair

ben said:
ok thanks. not sure why i used byte size chunks and two loops. one's
got to be better than two.


#include <stdio.h>
#include <limits.h>

int main(void)
{
void bitfloatprint(float f);
[/QUOTE]

It is not considered good practice to put function
declarations/prototypes within another function.
ah, i've just thought, that won't work on little endian machines i
don't think. oh well, not to worry, that's enough on this i think.

Apart from that, the cast to (unsigned *) is not portable, as you may
well have machines with sizeof(unsigned int)<sizeof(float).


Cheers
Michael
 

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,755
Messages
2,569,536
Members
45,019
Latest member
RoxannaSta

Latest Threads

Top