View previous topic :: View next topic |
Author |
Message |
s_mack
Joined: 04 Jun 2009 Posts: 107
|
converting from float to int and vice versa |
Posted: Sun Aug 30, 2009 1:50 pm |
|
|
I need to convert a float to an hex representation and back again.
Please don't tell me to just typecast
I need to know what is going on in a device. Internally it is working with floating points but it can only communicate with me via CANBUS in 8 byte hex strings. I need to know what its floats are at any given moment without losing (too much) accuracy.
I see in the CCS help file there is a page that reads:
Quote: | What is the format of floating point numbers?
--------------------------------------------------------------------------------
CCS uses the same format Microchip uses in the 14000 calibration constants. PCW users have a utility Numeric Converter that will provide easy conversion to/from decimal, hex and float in a small window in the Windows IDE. See EX_FLOAT.C for a good example of using floats or float types variables. The format is as follows:
|
It shows a diagram and some examples (search the ccs c compiler help for "what is the format of floating point numbers?" to see the diagrams).
But I can't figure the math behind what it is doing. Is there a simple way of converting a PIC18 float into int8s and back again?
Thanks. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Sun Aug 30, 2009 2:08 pm |
|
|
You don't need to start a new thread on every new float question. Just
add them to your existing thread.
Read this thread and download the floatconv utility that it mentions.
https://www.ccsinfo.com/forum/viewtopic.php?p=63690 |
|
|
s_mack
Joined: 04 Jun 2009 Posts: 107
|
|
Posted: Sun Aug 30, 2009 2:17 pm |
|
|
Thanks.
And the moment phpBB can come up with a search that gives reasonable results is the moment people will stop asking repeat questions. |
|
|
s_mack
Joined: 04 Jun 2009 Posts: 107
|
|
Posted: Sun Aug 30, 2009 5:32 pm |
|
|
That tool did exactly what I asked for, but with so much "magic" that it didn't really help (except to check my work below).
So I started with this theory based explanation: http://www.cs.cornell.edu/~tomf/notes/cps104/floating.html#hex2dec
And I whipped up this messy function:
Code: | int32 float2hex(float value)
{
int32 bigHex;
int32 leftH, exponent;
int bitcount;
bool zeroOrOne;
float remainder;
signed int8 remainderPos;
if (value < 0)
{
bit_set(bigHex, 23); //sets the sign bit
value *= -1;
}
leftH = (int32)value; //gets the value to the left of the decimal
bitcount = 32;
while ( bitcount > 0 )
{
bitcount--;
if ( bit_test( leftH, bitcount ) )
{
exponent = bitcount;
bitcount = 0;
}
}
exponent += 0x7F; //adjust for bias
exponent = (exponent << 0x18);
bigHex ^= exponent;
remainder = (value - leftH)*2; //get the remainder now because we're about to rape leftH
zeroOrOne = bit_test( leftH, 0 );
//shift leftH into bigHex until its occupying the most significant bytes
bitcount = 24;
while ( bitcount > 0 )
{
bitcount--;
if ( !bit_test( leftH, 23 ) )
{
leftH <<= 0x01;
}
else
{
bit_clear( leftH, 23 ); //clear the implied bit for the mantissa
bitcount = 0;
}
}
bigHex ^= leftH;
//figure out which byte we continue with
while ( bitcount < 24 )
{
if ( bit_test ( bigHex, bitcount ) )
{
if ( zeroOrOne )
{
remainderPos = bitcount - 1;
}
else
{
remainderPos = bitcount - 2;
}
bitcount = 24;
}
else
{
bitcount++;
}
}
if ( remainderPos < 0 ) remainderPos = 0;
while ( remainderPos >= 0 )
{
if ( (int)remainder ) bit_set( bigHex, remainderPos );
remainderPos--;
if ( remainder >= 1 ) remainder -= 1;
remainder *= 2;
}
return bigHex;
} |
To my amazement, it actually works for the most part. What I can't figure out is why it won't work with a value of < 3?
3 evaluates correctly (0x80400000)
3.01 does too (0x8040A3D6)
as does everything higher that I've tested.... but 2.99 and below seems to quasi-truncate (2.99 evaluates to 0x80000001 instead of 0x803F5C28)
Any glaring errors I made?
Thanks. |
|
|
s_mack
Joined: 04 Jun 2009 Posts: 107
|
|
Posted: Sun Aug 30, 2009 5:35 pm |
|
|
Another oddity... really large numbers don't seem to work right (assuming the tool linked above is correct - I haven't checked manually)
10M works ok, but 100000005678 (for example) does not. It resolves to 0x8D6DFC00 instead of 0xA33A43B6
However for my purposes I'm not concerned with large numbers. The small ones are going to be a problem though.
I'm taking a break. Head hurts. |
|
|
FvM
Joined: 27 Aug 2008 Posts: 2337 Location: Germany
|
|
Posted: Sun Aug 30, 2009 11:50 pm |
|
|
Quote: | 10M works ok, but 100000005678 (for example) does not. |
Without looking too deep in the code details, it's obvious that this can't work
for large numbers beyond the int32 range:
Code: | leftH = (int32)value; //gets the value to the left of the decimal |
But, apart from giving insights in the presentation of float numbers, what's the purpose of this code? CCS C (as nearly all C compilers) is using the standard IEEE float format internally. It can be printed out and read in e.g. by simple union constructs. It's a standard for presentation of float data also in many common file formats.
Code: | union {
float f;
int32 i32;
} f_i;
f_i.f = floatvalue;
bigHex = f_i.i32; |
|
|
|
s_mack
Joined: 04 Jun 2009 Posts: 107
|
|
Posted: Mon Aug 31, 2009 7:49 am |
|
|
The "purpose" is stated in the first post.
I think you're wrong about it using the standard IEEE float format. According to CCS's own help file, it is different (sign bit is in a different local) however that hardly makes a difference really.
I'm unfamiliar with the use of union and the helpfile, while it mentions its existence, doesn't really explain it. From your brief note, I still don't understand how it achieves my goal. I'll have to do some Googling, thanks.
You're right about the large number being out of int32 range and yeah, that's obvious int32 is so huge in the scope of what I'm doing that it didn't occur to me that I was using examples large enough to be out of range. As I said though, it doesn't really matter. I'm not using values that high in practice. I'm more concerned as to why the < 3 numbers don't work! |
|
|
s_mack
Joined: 04 Jun 2009 Posts: 107
|
|
Posted: Mon Aug 31, 2009 9:06 am |
|
|
Ok, now I've read some and I just need help understanding the union implementation.
First, thank you! That is a WHOLE lot easier and likely a lot more reliable than what I was doing.
HOWEVER... it seems to invert the hex value.
Using the floatconv tool or just manually calculating (or just look at the CCS help file) we know that a float of value 123.45 is represented hexidecimally as 0x 85 76 E6 66.
Using the union example you provided: Code: | union {
float f;
int32 i32;
} f_i;
f_i.f = 123.45;
bigHex = f_i.i32; |
we actually get the reverse (sort of) representation of 0x 66 E6 76 85
Any thoughts on why?
(incidentally, the IEEE representation would be 0x 87 24 8F BE so you can see it is different) |
|
|
Ttelmah Guest
|
|
Posted: Mon Aug 31, 2009 9:41 am |
|
|
At the very head of the thread, you have the comment that CCS uses the Microchip 14000 format. It actually saves a tiny amount of time, relative to the IEEE format, on the PIC hardware, hence 'why'.
CCS, actually switches to using the IEEE format, on the very latest larger chips (DSPIC etc.), hence you need to be very careful about assuming what format it is using...
On the byte order, when you print a number, the LSB, is to the right.
Remember you can use a union to a byte array as well:
Code: |
union {
float f;
int32 i32;
int8 b[4];
} f_i;
|
Then you can access the bytes f_i.b[0] to f_i.b[3] in whatever order you want....
Best Wishes |
|
|
s_mack
Joined: 04 Jun 2009 Posts: 107
|
|
Posted: Mon Aug 31, 2009 10:04 am |
|
|
Thanks. I wasn't assuming any format and in fact, until this exercise I've never given any thought to this subject at all. I got the format from the help file, then confirmed it manually and with the floatconv tool linked above.
I'm still not sure why using a union stores/displays the representation in the opposite order though. The LSB would be at the right side if it printed the way it was stored.
According to the help file, 123.45 is represented as:
85 76 E6 66, where:
85 is the 8 bit exponent with bias
76 represents the sign bit at the front and the MSB of the mantissa with the first bit being implied 1
E6 are the next 8 bits of the mantissa
66 finally represents the LSB.
So when using union presents it "backwards" it instead implies... well, I'm not sure? By your explanation the exponent byte is now the LSB? And its of course only backwards in 8-bit blocks. Looking at it as a whole it is all messed up.
Anyway, none of it really matters. As long as I know I have to reverse the 4 blocks prior to decyphering the data its all good. |
|
|
Ttelmah Guest
|
|
Posted: Mon Aug 31, 2009 10:18 am |
|
|
If you look at the help, under 'what is the format of floating point numbers', it shows the format as:
85 76 E6 66 (for the 123.45) example, with the left hand column, labelled 'lowest byte in RAM'.
'66' is _not_ the LSB, but the MSB.
It is a normal 'standard', in computing, when displaying tables of values in memory, to have the LSB to the left.
If you are encyphering/deciphering the data though, why not just work directly with the bytes, which then involves no 'reversal'. b[0], will be the '85' value.
Best Wishes |
|
|
s_mack
Joined: 04 Jun 2009 Posts: 107
|
|
Posted: Mon Aug 31, 2009 10:21 am |
|
|
But you just said LSB to the right!
Anyhoo... yes, your suggestion to call out the bytes directly is probably the way to go to avoid confusion. |
|
|
Ttelmah Guest
|
|
Posted: Mon Aug 31, 2009 1:24 pm |
|
|
s_mack wrote: | But you just said LSB to the right!
Anyhoo... yes, your suggestion to call out the bytes directly is probably the way to go to avoid confusion. |
Yes, when you print a number.....
Best Wishes |
|
|
|