I am able to extract values from modbus as 16-bit shorts (unsigned) or as ints (should be treated as 16-bit words). I am tasked to combine two values to create a single 32 bit float value using java.
some example values I observed using a gui program:
int + int = float
0 + 16256 = 1
0 + 17096 = 100
0 + 17097 = 100.5
0 + 17530 = 1000
8192 + 17530 = 1000.5
I attempted bit wise operators but that didn't seem to do the trick.
leaves me scratching my head!
You can use Float.intBitsToFloat(int bits) to build a float from the bits of an int.
short high = ... // the high 16 bits
short low = ... // the low 16 bits
int combined = (high << 16) | low;
float num = Float.intBitsToFloat(combined);
for example:
short high = 17530;
short low = 8192;
produces the float 1000.5.
Related
I am trying to convert values from Modbus addresses on a PLC to a float value on my program. Inside the PLC, the value is represented as 32 bit float, but to send it through Modbus, they convert it to pair of 16 bit integers. Now when I retrieve the value, I have to convert them back to 32 bit float. I already have working code (in the picture), but some low bit values are represented with '-' first and I can't convert them. Does anyone know how I should deal with this?
**For Example: -14.066963 in PLC is converted into low 16bit: 4680 and high 16bit: -16031. I can convert this back to its original value.
74.81565 in PLC is converted into low 16bit: -24163 and high 16bit: 17045. I can't convert this back to its original value.**
public class HelloWorld{
public static void main(String []args){
short high = 17045; // the high 16 bits
short low = -24163; // the low 16 bits
int first_part = high << 16;
int second_part = first_part | low;
float num = Float.intBitsToFloat(second_part);
System.out.println(first_part + " " + second_part + " " + num + '\n');
//Working values
short high1 = -16031; // the high 16 bits
short low1 = 4680; // the low 16 bits
int combined1 = (high1 << 16) | low1;
float num1 = Float.intBitsToFloat(combined1);
System.out.println(num1 + " " + combined1);
}
}
Picture example
Change the short's to int's. Make their values be in the range of an unsigned short 0..65535 (instead of signed short -32768..+32767). You will need to change your negative short values to their unsigned equivalent (e.g. -24163 to 41373). I prefer to do all this stuff in hexadecimal, e.g. 0xA19D (instead of -24163 or 41373), since the integer values are meaningless - it's really the IEEE 754 32-bit bit pattern you are generating
For example, when I have a number such as 0x54 in binary that would be 01010100. After using the bit-wise operator '>>' this number will turn into 00101010. Instead of the most significant bit being a 0, I need it to be a one. How can i accomplish this?
Is your number always 8 bits wide? If thats the case you can simply have the decimal representation of 10000000 which is 128 and do a bitwise or
so let's take your example
int val = 84; /// 01010100
int newVal = val >> 1; // 00101010
int mostSig = newVal | 128; // 10101010
I have an external system outputting 2 unsigned integers that represent a 64 bit unsigned int. Java picks these up and converts them to signed integers. I have called them lower and upper in java. I would like to put this back into a 64 bit int. I understand I have to use signed int which is ok.
I started off casting them both to long, shift the upper long by 32 bits and then trying to add them but it didn't go so well because the cast moves the sign bit of the integers. So I started to muck around with adding 2^31 and other messy stuff. How can I do this cleanly?
Here is an example, using 2 x 8 bit numbers to 16 bits for simplicity
Higher = 00000000 lower = 11111111
Desired result
0000000011111111
Result I get
1000000001111111
EDIT: This is code that I believe works ok (I haven't fully tested) but I was hoping for something cleaner.
private static long combineCounters(int lower, int upper) {
long lower2 = (long)lower;
if(lower2 < 0) lower2 += (2L<<31);
long upper2 = (long)upper;
if(upper2 < 0) upper2 += (2L<<31);
long result = lower2 | (upper2<<32);
return result;
}
For your case, first of all, you should store your integer values correctly into a long. To do so, you can AND your value with 0xFFFFFFFFL (a long with first 32 bit as 1).
Here is an example which works:
int upperInt = ...
int lowerInt = ...
long hi = upperInt & 0xFFFFFFFFL;
long lo = lowerInt & 0xFFFFFFFFL;
long c = (hi << 32) | lo;
System.out.println(String.format("0x%X", c));
int higher = ...
int lower = ...
long result = (((long) higher) << 32) | ((long) lower) & 0xFFFFFFFFL;
In my Java application I need to interpret a 32 Bit Fixed Point value. The number format is as follows: The first 15 bits describe the places before the comma/point, the 16th bit represents the sign of the value and the following 16 bits describe the decimal places (1/2,1/4,1/8,1/16,...).
The input is a byte array with four values. The order of the bits in the byte array is little endian.
How can I convert such a number into Java float ?
Just do exactly what it says. Assume x is the 32bit fixed point number as int.
So, put the bits after the point, after the point, and don't use the sign here:
float f = (float)(x & 0x7fff_ffff) / (float)(1 << 16);
Put back the sign:
return x < 0 ? -f : f;
You will lose some precision. A float does not have 31 bits of precision, but your input does. It's easily adapted to doubles though.
Since the sign bit is apparently really in the middle, first get it out:
int sign = x & (1 << 16);
Join the two runs of non-sign bits:
x = (x & 0xFFFF) | ((x >> 1) & 0x7fff0000);
Then do more or less the old thing:
float f = (float)x / (float)(1 << 16);
return sign == 0 ? f : -f;
In case your input is little endian format, use the following approach to generate x:
int x = ByteBuffer.wrap(weirdFixedPoint).order(ByteOrder.LITTLE_ENDIAN).getInt();
where weirdFixedPoint is the byte array containing the 32 bit binary representation.
I have a formula in a specification for a binary file. The spec gives details of the meaning of the various bytes in the heading.
In particular, one formula states this about 2 of the bytes:
Byte 1 --> 7 6 5 4 3 2 1 0
Byte 2 --> 7 6 5 4 3 2 1 0
R Roll
If 'R' = 0, Roll Angle not available
If 'R' = 1, Roll Angle = [((Byte 84 & 0x7F)<<8) | (Byte 85) – 900] / 10
I need to take a value such as 4.3 and convert it to two bytes such that it will be able to be converted back to 4.3 using the above formula. The part that puzzles me the most is subtracting the 900.
This is what I have so far:
private byte[] getRollBytes(BigDecimal[] positionData) {
BigDecimal roll = positionData[4];
roll = roll.multiply(BigDecimal.TEN);
roll = roll.add(new BigDecimal(900));
short shortval = roll.shortValue();
byte[] rollBytes = new byte[2];
ByteBuffer headingbuf = ByteBuffer.wrap(rollBytes);
headingbuf.order(ByteOrder.BIG_ENDIAN);
headingbuf.putShort(shortval);
//set the leftmost bit of the two bytes to 'on', meaning data is available
rollBytes[0] = (byte) (rollBytes[0] | (0x80));
//testing the result with my formula doesn't give me the right answer:
float testFloat = (float) (((((rollBytes[0] & 0x7F) <<8) | rollBytes[1]) - 900) /10);
return rollBytes;
}
I think something is getting lost in the conversion between short and byte...
One problem is that you are casting to a short. This can't be correct, because a value like 4.3 is not an integer. You probably want to convert back to a BigDecimal:
BigDecimal testVal = ((rollBytes[0] & 0x7F) <<8) | rollBytes[1]) - 900;
testVal = val.divide(BigDecimal.valueOf(10));
Another problem is that you seem to be adding 900 twice—once when computing shortVal and once again with rollBytes[1] = (byte) (rollBytes[1] + 900);