convert float to short with minimal loss of precision

12,616

Solution 1

public static short floatToShort(float x) {
    if (x < Short.MIN_VALUE) {
        return Short.MIN_VALUE;
    }
    if (x > Short.MAX_VALUE) {
        return Short.MAX_VALUE;
    }
    return (short) Math.round(x);
}

You'll loose the fractional part:

float    4 byte floating-point
double   8 byte floating-point (normal)
short    2 byte integer
int      4 byte integer (normal)
long     8 byte integer

Edit:

Maybe you wanted to know how to save the bits of a float (4 bytes) into an int (4 bytes): (http://docs.oracle.com/javase/7/docs/api/java/lang/Float.html#floatToRawIntBits(float))

float x = 0.1f;
int n = Float.floatToRawIntBits(x);
float y = Float.intBitsToFloat(n);

Solution 2

In principle, you could just multiply it by 100000, convert it to int, then subtract -32,767 and convert it to short. If that still puts it in the -32,767 to 32,767 range for all your values, that's likely the best you can do. Otherwise, you'll have to limit your precision and multiply by 10000.

And when you use the short of course you have to remember to divide it back down.

Solution 3

If your input float values are in a defined range (for now let's assume they're in the range of -1..1, exclusive), you can multiply them to get a value whose fraction you'll throw away.

Valid short range is: -32768..32767 so you can multiple with 32768 in this case (max short / max input value).

For example:

float f = 0.23451f;
short s = (short) (f * 32768);

To decode a short value to float:

float f2 = s / 32768f;

Solution 4

short is an integral type, so it can only contain whole numbers. The only two choices for 0.37885 in a short are 0 or 1, both of which (it seems to me) lose quite a bit of precision.

So the answer is: If you're okay with losing all fractional values, either use a cast, Float#shortValue, or Math.round(float) (and cast the resulting int to short).

Example: Live Copy

float f1 = 0.37885f;
short s1 = (short)Math.round(f1);
System.out.println("s1 = " + s1);

float f2 = 27.67885f;
short s2 = (short)Math.round(f2);
System.out.println("s2 = " + s2);

Output:

s1 = 0
s2 = 28

In a comment you said:

I have this sine wave which generates values like the one mentioned above, but I want them as shorts.

Ah, now, we can do something with that. Presumably the values you're getting are all between 0 and 1. You can store them as shorts by multiplying. Since the range of a short is -32,768 to 37,767, a convenient number to multiply them by might be 10000:

short s = Math.round(floatValue * 10000);

The number we'd get for your example would be 3789. Example: Live Copy

float floatValue = 0.37885f;
short s = (short)Math.round((double)floatValue * 10000);
System.out.println("s = " + s);

That isn't the same value, of course, it's the value multipled by ten thousand, so anywhere you're going to use it, you'd have to allow for that.

Share:
12,616
user3840530
Author by

user3840530

Updated on June 28, 2022

Comments

  • user3840530
    user3840530 almost 2 years

    I have this sine wave which generates floating point values (e.g. 0.37885) but I want them as shorts. Direct casting with short gives me a value of 0. so what is the solution?

    Can anyone tell me how to do it - ideally without loss of precision - or minimal loss of precision if this is all that is possible?

  • barak manos
    barak manos over 9 years
    Why is that an issue? If you take an integer of the same size (int would do on most platforms) then there's no problem with regards to the type of data. For example, given float f and int i, you can do i = *(int*)&f.
  • BackSlash
    BackSlash over 9 years
    @barakmanos What is that &?
  • T.J. Crowder
    T.J. Crowder over 9 years
    @barakmanos: This is Java, not C. You could probably do something similar to that in Java as well, but A) float and short are not the same size, and B) It's extremely unlikely that the OP wants the short (or int) view of the bits that made up the float. :-)
  • barak manos
    barak manos over 9 years
    Ooops, sorry (@BackSlash - same apology to you) ... :)
  • Joop Eggen
    Joop Eggen over 9 years
    @barakmanos funny, you know that just stores the bits, but the mathematical value then is totally different. Java has Float.floatToRawIntBits
  • barak manos
    barak manos over 9 years
    @JoopEggen: 1. In my answer I was referring to C/C++ (my mistake). 2. If you consider storing that value for later use, and for some reason you want to store it in an integer-type value, then you can simply "store the bits" in an int. Once you read them back into a float, it will retain its original (floating-point) value.
  • user3840530
    user3840530 over 9 years
    I agree that there will be loss of precision but all I wanted was a workaround. I will mark this as the answer( if it allows me to).
  • user3840530
    user3840530 over 9 years
    Thanks for the Save the bits method. That will be helpful.
  • Rudy Velthuis
    Rudy Velthuis over 9 years
    I think that is the only correct answer, but assuming his sine wave returns values between -1 and 1 (inclusive), I would multiply them by 65535 and then subtract 32767.
  • Stéphane
    Stéphane about 4 years
    This won't work. Ex for the value of 1: 1 * 65535 - 32767 = 32768. This is wrong, it is outside of short range. Other example: -1 * 65535 - 32767 will give -98302.