views:

1140

answers:

3

Say I have a float in the range of [0, 1] and I want to quantize and store it in an unsigned byte. Sounds like a no-brainer, but infact it's quite compliated:

The obvious solution looks like this:

unsigned char QuantizeFloat (float a)
{
  return (unsigned char) (a * 255.0f);
}

This works in so far that I get all numbers from 0 to 255, but the distribution of the integers is not even. The function only returns 255 if a is exactly 1.0f. Not a good solution.

If I do proper rounding I just shift the problem:

unsigned char QuantizeFloat (float a)
{
  return (unsigned char) (a * 255.0f + 0.5f);
}

Here the the result 0 only cover half of the float-range than any other number.

How do I do a quantization with equal distribution of the floating point range? Ideally I would like to get a equal distribution of integers if I quantize equal distributed random floats.

Any ideas?


Btw: Also my code is in C the problem is language-agnostic. For the non-c guys: Just assume that float to int conversion just truncates the float.

EDIT: Since we had some confusion here: I need a mapping that maps the smallest input float (0) to the smallest unsigned char, and the highest float of my range (1.0) to the highest unsigned byte (255).

A: 

Convert to a 64-bit representation, and take the last byte of the mantissa. In C#:

BitConverter.DoubleToInt64Bits(d) & 0xff; // d is a double
Jason
That will give me 0 for a input of 0.0, but not 255 (largest unsigned char) for a input of 1.0f (largest float in the range [0, 1].
Nils Pipenbrinck
Is that a requirement?
Jason
yep, it is.. I'll update the question.
Nils Pipenbrinck
+6  A: 

How about a * 256f with a check to reduce 256 to 255? So something like:

return (unsigned char) (min(255, (int) (a * 256f)));

(For a suitable min function on your platform - I can't remember the C function for it.)

Basically you want to divide the range into 256 equal portions, which is what that should do. The edge case for 1.0 going to 256 and requiring rounding down is just because the domain is inclusive at both ends.

Jon Skeet
wow - yes, that's it!
Nils Pipenbrinck
Yes -- (unsigned char)(a * 256.0f) gives you exactly what you want for every input value except for 1.0. C has no built-in min function, so you'll have to write your own if you haven't already.
Adam Rosenfield
John, I came up with the same result, just wasn't able to transfer it correctly from the Excel spreadsheet. I deleted my embarassing answer.
cdonner
A surprising answer, but yes - this seems to be the correct way to do it.
Nils Pipenbrinck
@cdonner: The important bit is that it's dividing the range into *256* equal intervals rather than 255.
Jon Skeet
@Jon: Yes, I understand, and I actually did divide by 256, or I would not have seen an equal distribution between the values of 1 and 255.
cdonner
A: 

I think what you are looking for is this:

unsigned char QuantizeFloat (float a)
{
  return (unsigned char) (a * 256.0f);
}

This will map uniform float values in [0, 1] to uniform byte values in [0, 255]. All values in [i/256, (i+1)/256[ (that is excluding (i+1)/256), for i in 0..255, are mapped to i. What might be undesirable is that 1.0f is mapped to 256.0f which wraps around to 0.

cr333