4

I've read several links discussing storing 2 or 3 floats in one float. Here's an example:

Storing two float values in a single float variable

and another:

http://uncommoncode.wordpress.com/2012/11/07/float-packing-in-shaders-encoding-multiple-components-in-one-float/

and yet another:

decode rgb value to single float without bit-shift in glsl

I've seen others but all of them use the same principle. If you want to encode x and y, they multiply y by some factor and then add x to it. Well this makes since on paper, but I don't understand how in the world it can work when stored to a floating value. Floating values only have 7 significant digits. If you add a big number and a small number, the small number is just truncated and lost. The precision only shows the value of the big number.

Since everyone seems to prescribe the same method, I tried it myself and it did exactly what I thought it would do. When I decoded the numbers, the number that wasn't multiplied turned out as 0.0. It was completely lost in the encoded float.

Here's an example of some MaxScript I tried to test it:

cp = 256.0 * 256.0
scaleFac = 16777215

for i = 1 to 20 do (
    for j = 1 to 20 do (
            x = (i as float / 20.01f) as float;
            y = (j as float / 20.01f) as float;
            xScaled = x * scaleFac;
            yScaled = y * scaleFac;

            f = (xScaled + yScaled * cp) as float
            print ("x[" + xScaled as string + "] y[" + yScaled as string + "]" + " e[" + f as string + "]")

            dy = floor(f / cp)
            dx = (f - dy * cp)

            print ("x[" + dx as string + "] y[" + dy as string + "]" + " e[" + f as string + "]")
    )
)

dx is 0.0 everytime. Can anyone shed some light on this? NOTE: It doesn't matter whether I make cp = 128, 256, 512 or whatever. It still gives me the same types of results.

Community
  • 1
  • 1
JamesHoux
  • 2,999
  • 3
  • 32
  • 50
  • Is this purely an academic exercise or do you actually expect to get some value out of it? – evanmcdonnal Dec 23 '13 at 22:55
  • It's not academic. I'm trying to figure out how to legitimately pack two floats into one. If you look at any of the links, you'll see other people showing similar code and claiming it works fine, but I'm baffled as to how it could actually work. I can't seem to make it work myself. Maybe I keep missing some math error in my own implementation, but the whole theory behind it doesn't seem like it should ever work. – JamesHoux Dec 23 '13 at 22:57
  • If I have the time I'll look over those links in detail. However, if I were in your shoes I would not touch that with a ten foot pole. I have `Tuple` to store two floats in a single value. – evanmcdonnal Dec 23 '13 at 22:59
  • It's a real-time rendering limitation issue. I need to pack the data for decoding in a vertex shader. Other people have done the same thing in real world application. It's very practical when you make it work. – JamesHoux Dec 23 '13 at 23:01

4 Answers4

5

This method works for storing two integers. You're effectively converting your floating point numbers to large integers by multiplying by scaleFac, which is good, but it would be better to make it explicit with int(). Then you need to make sure of two things: cp is greater than the largest number you're working with (scaleFac), and the square of cp is small enough to fit into a floating point number without truncation (about 7 digits for a single precision float).

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • Huh. Those two things weren't mentioned in any of the other material I've looked at. Is this just head knowledge or is it mentioned in a book on encoding techniques somewhere? – JamesHoux Dec 23 '13 at 22:59
  • I got it working just using the two points you made. Thanks. I don't think the other examples I looked at were using realistic ranges or something. I don't know what the heck lead me down the wrong path with the numbers I was using. I'll take a look again. I think I'm all good for now! – JamesHoux Dec 23 '13 at 23:07
  • @Jim, one more thing - if you used `int` instead of `float` you'd have a couple more digits to play with. – Mark Ransom Dec 23 '13 at 23:48
4

Here is a working code in C to pack two floats into one float and unpack them.

You should change scaleFactor and cp parameters as according to your possible value ranges (yourBiggestNumber * scaleFactor < cp). It is a precision battle. Try printing a few results to find good values for your case. The example below allows floats in [0 to 1) range.

#include <math.h>

/* yourBiggestNumber * scaleFactor < cp */
double scaleFactor = 65530.0;
double cp = 256.0 * 256.0;

/* packs given two floats into one float */
float pack_float(float x, float y) {
    int x1 = (int) (x * scaleFactor);
    int y1 = (int) (y * scaleFactor);
    float f = (y1 * cp) + x1;
    return f;
}

/* unpacks given float to two floats */
int unpack_float(float f, float* x, float* y){
  double dy = floor(f / cp);
  double dx = f - (dy * cp);
  *y = (float) (dy / scaleFactor);
  *x = (float) (dx / scaleFactor);
  return 0;
}
bekce
  • 3,782
  • 29
  • 30
0

It will work only if your individual floats are small enough to be packed into one float location.

So you can pack 2 numbers by "dividing" this into two to store 2 numbers that can be represented by half the space.

George Philip
  • 704
  • 6
  • 21
0

Here's the code I use for packing and unpacking floats. It works by packing first float (0..1) into the first four bytes of a 8-bit (0..256) number, and the next float into the remaining 4 bits. The resulting numbers have 16 possible combinations each (2^4). In some cases this is good enough:

private float PackFloatsInto8Bits( float v1, float v2 )
{
    var a = Mathf.Round( v1 * 15f );
    var b = Mathf.Round( v2 * 15f );

    var bitShiftVector = new Vector2( 1f/( 255f/16f ), 1f/255f );

    return Vector2.Dot( new Vector2( a, b ), bitShiftVector );
}

private Vector2 UnpackFloatsFrom8Bits( float input )
{
    float temp = input * 15.9375f;

    float a = Mathf.Floor(temp) / 15.0f;
    float b = Frac( temp ) * 1.0667f;

    return new Vector2(a, b);
}
Ilya Suzdalnitski
  • 52,598
  • 51
  • 134
  • 168