4

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?

Rog
  • 17,070
  • 9
  • 50
  • 73
user3840530
  • 290
  • 1
  • 4
  • 14
  • 8
    The size of `float` is typically 4 bytes, and the size of `short` is typically 2 bytes. How exactly are you hoping to avoid loss of precision when squeezing 4 bytes of data into 2 bytes of data? – barak manos Aug 08 '14 at 10:24
  • 3
    To help understand the problem you're trying to solve can you explain why you need to store a float in a short? – Sean Aug 08 '14 at 10:25
  • Plus, how are you going to "convert" a floating-point value to a non-floating-point? – BackSlash Aug 08 '14 at 10:26
  • You will need to decide either a certain point to use for the lowest bit, or write some code to store the `float` like the FPU normally does. – PurkkaKoodari Aug 08 '14 at 10:26
  • @BackSlash: That's not 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`. – barak manos Aug 08 '14 at 10:26
  • Impossible. All you will get is zero. The question doesn't make sense. – user207421 Aug 08 '14 at 10:28
  • @barakmanos I meant, how can you store `0.37885` into a type which doesn't have decimal numbers? All you will get is `0`... Does it make sense? – BackSlash Aug 08 '14 at 10:28
  • @BackSlash: Use `i = *(int*)&f`, and at a later point, use `f = *(float*)&i`. The variable `f` will retain its original value. – barak manos Aug 08 '14 at 10:30
  • I understand that there will be loss. I have this sine wave which generates values like the one mentioned above, but I want them as shorts. Direct casting with short gives me a value of 0. so what is the solution? – user3840530 Aug 08 '14 at 10:30
  • @barakmanos Did you forget that this is ***java***? – BackSlash Aug 08 '14 at 10:30
  • float has 23 bits of mantissa, how can you fit that into 16 bits, not even counting the extra range due to 8 bits of exponent – phuclv Aug 08 '14 at 10:32
  • @BackSlash: No, I was careless enough not to notice that, assuming that the question was tagged as C (and you know what they say about the motherhood of assumptions)... – barak manos Aug 08 '14 at 10:32
  • A sine wave takes values in the range [-1,1]. There are only three values in that range that can be represented in any integer type, -1, 0, and 1. – Patricia Shanahan Aug 08 '14 at 13:31
  • @user3840530: I assume you have values between -1 and 1 want to have short integers to turn them into a wave file or pass them to a sound driver. In that case, simply do: `intvalue = floatvalue * 65536.0f - 32768.0f;`. You may have to add code to cut off any excess. – Rudy Velthuis Aug 10 '14 at 07:19
  • @RudyVelthuis Thanks for the info. I am now scaling them as Short.MaxValue for the android audioTrack sound functinality as it accepts a short buffer for audio playback. – user3840530 Aug 18 '14 at 11:49

4 Answers4

6
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);
Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
5

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.

Arwin
  • 973
  • 1
  • 10
  • 21
  • 1
    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. – Rudy Velthuis Aug 10 '14 at 07:22
  • 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. – Stéphane Mar 29 '20 at 09:32
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;
icza
  • 389,944
  • 63
  • 907
  • 827
1

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.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 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`. – barak manos Aug 08 '14 at 10:28
  • 1
    @barakmanos What is that `&`? – BackSlash Aug 08 '14 at 10:29
  • @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`. :-) – T.J. Crowder Aug 08 '14 at 10:30
  • 1
    Ooops, sorry (@BackSlash - same apology to you) ... :) – barak manos Aug 08 '14 at 10:30
  • @barakmanos funny, you know that just stores the bits, but the mathematical value then is totally different. Java has [Float.floatToRawIntBits](http://docs.oracle.com/javase/7/docs/api/java/lang/Float.html#floatToRawIntBits(float)) – Joop Eggen Aug 08 '14 at 10:34
  • @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. – barak manos Aug 08 '14 at 10:38