Game Development Community

ReadFloat -- why 0 to 1?

by Anthony Lovell · in Torque Game Engine · 10/27/2006 (7:44 am) · 3 replies

I expected readFloat/writeFloat and readSignedFloat/writeSignedFloat to take in and spit out full-ranged floating point values. The fact that they do not do this is not at all what I would have anticipated.

Moreover, doesn't the dynamic range of the values suffer adversely when you send (say) 32 bits through, squish its value to 0->1, and then store it in a 32 bit float?

Has anyone created more general purpose (and endian-safe!) logic for passing floating point values through?

I'd love one that allowed me to express the min and max values that must be represented, as well as being able to specify a precision achievable at a given value , e.g.:

void BitStream::writeReal(double thisValue, double minVal, double maxVal, double withThisPrecision, double atThisValue, U8 *howManyBitsWereUsedInCaseYouCare);

double BitStream::readReal(double minVal, double maxVal, double withThisPrecision, double atThisValue, U8 *howManyBitsWereUsedInCaseYouCare);

tone

#1
10/27/2006 (8:00 am)
Hey Tone,

If you want to do full 32 bit floats, I believe you can just use

bitstream.write()

With writeFloat and signedWriteFloat, I believe you can specify how many bits you'd like to use in the float for optimization purposes.
#2
10/27/2006 (8:19 am)
That was a fast response. But I'm not sure this is really optimal.

I continue to be puzzled by the current coding, which does things like

F32 BitStream::readSignedFloat(U8 bitCount)
{
return readSignedInt(bitCount) / F32((1 << (bitCount - 1)) - 1);
}

To my eye, this would be better written as

F64 BitStream::readSignedFloat(U8 bitCount)
{
return readSignedInt(bitCount) / F64((1 << (bitCount - 1)) - 1);
}

as otherwise, high values of bitCount (consider 32 as the example) will have a needless loss of precision when the resulting 32 bit floating point number is divided by MAX_FLOAT to squeeze it down to the 0-1 range.

I think I am going to code up the following fairly simple call for the time being, which would spare me a longhand scaling down/up on write/read, and yet preserve the fullest dynamic range by using doubles to store the compressed value:

F64 BitStream::readSignedReal(U8 bitCount, F64 maxValue)
{
F64 negativeOneToOne = readSignedInt(bitCount) / F64((1 << (bitCount - 1)) - 1);
return negativeOneToOne * maxValue;
}
#3
10/27/2006 (8:30 am)
I guess one other thing I realize here is that the granularity of the reals transmitted in this manner is uniform, and not higher near zero and lower at the extremes. That's not super desirable in some cases, but I can eventually come up with my own mantissa/exponent representation for the network I suppose.

But for now, here is the culmination of my best effort to make a flexible means of pumping reals through the network with the greatest possible option for precision and sensitivity to the range of values that must be expressed. The nice thing about it is that it spares the coder from having to pre- and post-scale the values. The bad thing is that it requires a little more data on each call.

void
FSBitStream::writeReal(TNL::F64 value, TNL::F64 minValue, TNL::F64 maxValue, TNL::F64 precision)
{
   TNLAssert(value >= minValue, "FSBitStream::writeReal value too low");
   TNLAssert(value <= maxValue, "FSBitStream::writeReal value too high");
   TNLAssert(minValue <= maxValue, "FSBitStream::writeReal bounds error");
   TNLAssert(precision > 0, "FSBitStream::writeReal precision error");

   TNL::F64 span = maxValue - minValue;
   TNL::U32 divisions = span / precision;
   TNL::U32 bitsRequired = TNL::getNextBinLog2(divisions + 1); // N divisions with N+1 fences

   // figure out which fence this value is nearest (precision + 0.5 for rounding)
   TNL::U32 whichFence = ((precision * 0.5) + (value - minValue)) / (precision);

   writeInt(whichFence, bitsRequired);
}


TNL::F64 
FSBitStream::readReal(TNL::F64 minValue, TNL::F64 maxValue, TNL::F64 precision)
{
   TNLAssert(minValue <= maxValue, "FSBitStream::readReal bounds error");
   TNLAssert(precision > 0, "FSBitStream::readReal precision error");
   
   TNL::F64 span = maxValue - minValue;
   TNL::U32 divisions = span / precision;

   bool noDiv = divisions == 0;

   TNL::U32 bitsRequired = TNL::getNextBinLog2(divisions + 1); // N divisions with N+1 fences
   TNL::U32 whichFence = readInt(bitsRequired);

   if (noDiv)
      return whichFence == 0 ? minValue : maxValue;

   return minValue + (whichFence * precision);
}