Jump to content
  • Advertisement
Sign in to follow this  
Xentropy

Conversion of Pascal real48 (48-bit float) to C++ double

This topic is 4773 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have a need to load data from a Pascal data file using C++, and some of the fields in the file are 48-bit floating point values. Since C++ uses 32- and 64-bit floats, I know of no way to directly convert the data and obtain accurate results. I've been fiddling around with really messy code manually calculating the exponent and mantissa bit by bit and so on, but I'm sure there's got to be a far more elegant way to do this. Does anyone have any suggestions? Thanks in advance.

Share this post


Link to post
Share on other sites
Advertisement
If you were to use some straight C function like fscanf to read the data in and make the recieving type and escape characters long, then it might well chop the end off the incoming float/long.

ace

Share this post


Link to post
Share on other sites
Is the data stored in binary, or textual form?

If it's stored in binary (and by the sounds of it, it is) then you've got a little bit of 48-bit float to double conversion code to write.
What you need to know is hoiw many bits are the mantissa, and how many are the exponent, and what it the base and bias for the exponent.

My sources tell me that it's probably 40-bits (5 bytes) for the mantissa, 7-bits for the exponent, and 1-bit for the sign. The exponent is base 8.

IEEE754 Double's use 52(+1 implied) bits for the mantissa, 11 bits for the exponent, and 1 bit for the sign. The exponent is base 2 and is biased by 1023.

Doesn't sound like a terribly easy conversion, but I'd give it a shot. I'll give you a hand if you like.

Share this post


Link to post
Share on other sites
The Pascal "real" format is rather old, dating back the pre-FPU days. It's maintained for backwards compatbility, I think I have some documentation on how it's formatted. It was obviously designed for fast integer emulation of floating point values.

Share this post


Link to post
Share on other sites
Yeah, in this case it's being used as basically a large int. I googled around and discovered the format is actually 1 sign bit, followed by 39-bit mantissa, followed by 8-bit exponent, with a bias of 129. It's not close enough to IEEE to just tack on a couple of zero bytes and call it a double. :)

It's stored in a binary file, but I'm loading it into a char [6] and then dealing with it from there. From the responses so far, I guess calculating it manually is pretty much my only option. For now I'm not coming up with correct values, but I'm sure that's just an issue with my code and math. Is there any way to do the mantissa conversion without a lot of expensive calls to pow()?

Edit: Well, I found my error. I wasn't testing for zero and zero was turning into 1 * 2^127. Thought I was way off. But after correcting for that, all of the test values I've tried have come out on the money. Note that the way the data is stored in the file, the most significant byte is the 6th, and it works its way backwards from there, which is why there's all the weird modulus stuff to work on the bits in the right order. Here's my messy code:


double real48ToDouble(char *realValue) {

double mantissa = 1.0;
for (int i = 46; i >= 8; i--) {
if ((realValue[i / 8] >> (i % 8)) & 0x01)
mantissa += pow(2.0, i - 47);
}

char exponent = realValue[0] - 129;

if (mantissa == 1.0 && exponent == 127) // Test for null value
return 0.0;

if (realValue[5] & 0x80) // Sign bit check
mantissa *= -1.0;

return mantissa * pow(2.0, exponent);
}


It works perfectly, I just don't like the LOOK of it. Too many magic numbers abound and such. Oh well, at least since I know it works it can lurk and I don't have to look at the function again.

[Edited by - Xentropy on June 18, 2005 9:30:14 PM]

Share this post


Link to post
Share on other sites
What you have does indeed look correct, however to remove the calls to pow in the loop, see the changes below:
double real48ToDouble(const char *realValue) {
double exponent = double((unsigned char)realValue[0]) - 129.0;
double mantissa = 1.0;
double power = 1.0;

for (int i = 46; i >= 8; i--) {
power *= 0.5;
if ((realValue[i >> 3] >> (i & 7)) & 0x01)
mantissa += power;
}

if (mantissa == 1.0 && realValue[0] == 0) // Test for null value
return 0.0;

if (realValue[5] & 0x80) // Sign bit check
mantissa = -mantissa;

return mantissa * pow(2.0, exponent);
}





Take note of the other minor changes I made too.
There are more optimsations that could be done too I think, but this would have already made a huge impact.

[Edited by - iMalc on June 19, 2005 3:12:46 AM]

Share this post


Link to post
Share on other sites
Instead of working with pow(), might I suggest bit-shifting things around within a 64-bit integer type (depending on your platform you may need to do some work to acquire such a beast) and then reinterpret-casting that bit pattern to a double?

Share this post


Link to post
Share on other sites
Thank you, iMalc! That's so simple I feel like an idiot for not thinking of it.

I also considered the alternative of reading each byte instead of bit and dividing the most significant byte by 256, the next most by 256 twice, the next most by 256 three times, and so on. It would come to the same conclusion with less iterations through the loop. I suspect compiler optimizations may have already taken care of things like changing / 8 to >> 3, but I'll still implement those in case it doesn't.

I wasn't too concerned with speed since these real48 fields in the files I'm reading are not very common. Most of the fields are ints, and maybe 2% of the files are real48s. But hey, free speed is always nice, so I thank you!

Share this post


Link to post
Share on other sites
Quote:
Original post by Xentropy
[...]It's not close enough to IEEE to just tack on a couple of zero bytes and call it a double. :)[...]
You know the format of a double is a sign bit, the exponent (11 bits) and the mantissa(52 bits), right? All you have to do is swap a few bits around, prepend some zeroes to the exponent, and append some zeroes to the mantissa.

Share this post


Link to post
Share on other sites
The bias is also different (129 instead of 1023 for a double, IIRC), so I'd still have to do some math on it or it'd end up off by 2, basically. The order of the fields is also different, exponent at the most significant bits of a double, least significant of a real48.

You're right, though, it may be even faster to work directly with bit shifts on an int64. The question becomes what is the best way to cast a char [6] to an int64, and what kind of speed penalty might that incur? Since the fields are 6 bytes in the files, I can't just change the data types to int64 in the record structures without messing up all my offsets and making the file loading more complex (likely removing any speed improvement I could get on these conversions in the first place).

Err, nevermind that question, I can just append two nulls to the end of the char array and then reintrpret_cast those 8 bytes of memory to an int64. I'll mess around with it and profile the speed difference.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!