I try to convert a value from modbus.
The device show "-1.0", the retourned value is 65535 (uint16).
I try now to convert this value retour in double.
I have tried it with different cast's.
It gives me always 65353.00 :(
How do we convert negative uint values in double?
typedef unsigned short uint16;
int main() {
double dRmSP = -1.0; //-1.0000 ok
uint16 tSP = static_cast<uint16>(dRmSP); // = 65535 ok
// retour
double _dRmSP = static_cast<double>(tSP); // = 65535.0000 why??
// try
double _dRmSP_ = static_cast<double>(static_cast<int>(tSP)); // =65535.0000 why??
return 0;
}
You're taking the uint16 value 65535 and turning it into a double. This is 65535.0.
There is no other valid expectation.
The variable tSP does not "remember" that its value originally came from a double of value -1.0. tSP is the unsigned integer value 65535; period.
How do we convert negative uint values in double?
There are no "negative uint values". The "u" stands for unsigned which means negative values are not in the domain of values of that type.
If you wish to use dRmSP then use dRmSP, not some other variable with a different type and value.
Negative unsigned values, by definition do not exist. So you can't convert one to anything.
Your actual situation is that - in getting data from your device - the value of -1.0 is converted to an unsigned value first. The logic, since -1.0 is outside the range of values that an unsigned can represent is to use modulo arithmetic.
The way this works, for a negative input value (like -1.0) and an unsigned variable with maximum value 65535 (corresponding to a 16-bit unsigned) is to keep adding 65536 = 65535 + 1 until a result is obtained between 0 and 65535. For -1.0 this produces a result of 65535.0. When that value is converted to an unsigned, the result is therefore 65535.
That explains why you are getting a value of 65535 when your device displays -1.0.
What you are trying to do with the "retour" is reverse the process. It is not enough to convert an unsigned to a double (as you are) since a double can represent 65535.0 (at least, within limits of numerical precision).
The first step is to convert your value to a double (which will convert 65535 to 65535.0, because a double can represent values like that (again within limits of floating point precision).
The next step - which you are not performing - requires you need to have some idea of what the minimum (or maximum) value is that your device actually supports - which you need to get from documentation. For example, if the minimum value your device can represent is -100.0 (or the maximum is 65435.0) then you reverse the process - keep subtracting 65536.0 until a result is obtained between -100.0 and 65435.0.
In code, this might be done by
double dRmSP = -1.0; //-1.0000 ok
uint16 tSP = static_cast<uint16>(dRmSP); // = 65535 ok
// retour
double dRmSP = static_cast<double>(tSP); // = 65535.0000 - as described above
while (dRmSP > 65435.0) dRmSP -= 65536.0; // voila! -1.0 obtained
First of all, there are no negative unsigned int values. Unsigned means there is no sign bit.
What you did was:
uint16 t1(-1.0); // wraps around to positive 65535
auto t2 = static_cast<double>(t1); // turns 65535 to 65535.0 (no wrapping)
If you want this to work for negative values use an int or comparable (non unsigned integral) type. But if you do this then remember that you will lose a bit for the value (if you use int16).
Related
I am trying to sent multiple float values from an arduino using the LMIC lora library. The LMIC function only takes an uint8_t as its transmission argument type.
temp contains my temperature value as a float and I can print the measured temperature as such without problem:
Serial.println((String)"Temp C: " + temp);
There is an example that shows this code being used to do the conversion:
uint16_t payloadTemp = LMIC_f2sflt16(temp);
// int -> bytes
byte tempLow = lowByte(payloadTemp);
byte tempHigh = highByte(payloadTemp);
payload[0] = tempLow;
payload[1] = tempHigh;
I am not sure if this would work, it doesn't seem to be. The resulting data that gets sent is: FF 7F
I don't believe this is what I am looking for.
I have also tried the following conversion procedure:
uint8_t *array;
array = (unit8_t*)(&f);
using arduino, this will not even compile.
something that does work, but creates a much too long result is:
String toSend = String(temp);
toSend.toCharArray(payload, toSend.length());
payloadActualLength = toSend.length();
Serial.print("the payload is: ");
Serial.println(payload);
but the resulting hex is far far too long to when I get my other values that I want to send in.
So how do I convert a float into a uint8_t value and why doesn't my original given conversion not work as how I expect it to work?
Sounds like you are trying to figure out a minimally sized representation for these numbers that you can transmit in some very small packet format. If the range is suitably limited, this can often best be done by using an appropriate fixed-point representation.
For example, if your temperatures are always in the range 0..63, you could use a 6.2 fixed point format in a single byte:
if (value < 0.0 || value > 63.75) {
// out of range for 6.2 fixed point, so do something else.
} else {
uint8_t bval = (uint8_t)(value * 4 + 0.5);
// output this byte value
}
when you read the byte back, you just multiply it by 0.25 to get the (approximate) float value back.
Of course, since 8 bits is pretty limited for precision (about 2 digits), it will get rounded a bit to fit -- your 23.24 value will be rounded to 23.25. If you need more precision, you'll need to use more bits.
If you only need a little precision but a wider range, you can use a custom floating point format. IEEE 16-bit floats (S5.10) are pretty good (give you 3 digits of precision and around 10 orders of magnitude range), but you can go even smaller, particularly if you don't need negative values. A U4.4 float format give you 1 digit of precision and 5 orders of magnitude range in 8 bits (positive only)
If you know that both sender and receiver use the same fp binary representation and both use the same endianness then you can just memcpy:
float a = 23.24;
uint8_t buffer[sizeof(float)];
::memcpy(buffer, &a, sizeof(float));
In Arduino one can convert the float into a String
float ds_temp=sensors.getTempCByIndex(0); // DS18b20 Temp sensor
then convert the String into a char array:
String ds_str = String(ds_temp);
char* ds_char[ds_str.length()];
ds_str.toCharArray(ds_char ,ds_str.length()-1);
uint8_t* data =(uint8_t*)ds_char;
the uint_8 value is stored in data with a size sizeof(data)
A variable of uint8_t can carry only 256 values. If you actually want to squeeze temperature into single byte, you have to use fixed-point approach or least significant bit value approach
Define working range, T0 and T1
divide T0-T1 by 256 ( 2^8, a number of possible values).
Resulting value would be a float constant (working with a flexible LSB value is possible) by which you divide original float value X: R = (X-T0)/LSB. You can round the result, it would fit into byte.
On receiving side you have to multiply integer value by same constant X = R*LSB + T0.
This code gives the meaningful output
#include <iostream>
int main() {
unsigned int ui = 100;
unsigned int negative_ui = -22u;
std::cout << ui + negative_ui << std::endl;
}
Output:
78
The variable negative_ui stores -22, but is an unsigned int.
My question is why does unsigned int negative_ui = -22u; work.
How can an unsigned int store a negative number? Is it save to be used or does this yield undefined behaviour?
I use the intel compiler 18.0.3. With the option -Wall no warnings occurred.
Ps. I have read What happens if I assign a negative value to an unsigned variable? and Why unsigned int contained negative number
How can an unsigned int store a negative number?
It doesn't. Instead, it stores a representable number that is congruent with that negative number modulo the number of all representable values. The same is also true with results that are larger than the largest representable value.
Is it save to be used or does this yield undefined behaviour?
There is no UB. Unsigned arithmetic overflow is well defined.
It is safe to rely on the result. However, it can be brittle. For example, if you add -22u and 100ull, then you get UINT_MAX + 79 (i.e. a large value assuming unsigned long long is a larger type than unsigned) which is congruent with 78 modulo UINT_MAX + 1 that is representable in unsigned long long but not representable in unsigned.
Note that signed arithmetic overflow is undefined.
Signed/Unsigned is a convention. It uses the last bit of the variable (in case of x86 int, the last 31th bit). What you store in the variable takes the full bit length.
It's the calculations that follow that take the upper bit as a sign indicator or ignore it. Therefore, any "unsigned" variable can contain a signed value which will be converted to the unsigned form when the unsigned variable participates in a calculation.
unsigned int x = -1; // x is now 0xFFFFFFFF.
x -= 1; // x is now 0xFFFFFFFE.
if (x < 0) // false. x is compared as 0xFFFFFFFE.
int x = -1; // x stored as 0xFFFFFFFF
x -= 1; // x stored as 0xFFFFFFFE
if (x < 0) // true, x is compared as -2.
Technically valid, bad programming.
I am trying to create a function to find the average of sensor values on the Arduino in order to calibrate it, however the summation is not working properly and therefore the average is not correct. The table below shows a sample of the output. The left column should be the rolling sum of the output which is displayed in the right column (How are negatives getting in there?)
-10782 17112
6334 17116
23642 17308
-24802 17092
-7706 17096
9326 17032
26422 17096
-21986 17128
The calibrateSensors() function, which is supposed to execute this is shown below
void calibrateSensors(int16_t * accelOffsets){
int16_t rawAccel[3];
int sampleSize = 2000;
Serial.println("Accelerometer calibration in progress...");
for (int i=0; i<sampleSize; i ++){
readAccelData(rawAccel); // get raw accelerometer data
accelOffsets[0] += rawAccel[0]; // add x accelerometer values
accelOffsets[1] += rawAccel[1]; // add y accelerometer values
accelOffsets[2] += rawAccel[2]; // add z accelerometer values
Serial.print(accelOffsets[2]);
Serial.print("\t\t");
Serial.print(rawAccel[2]);
Serial.print(" \t FIRST I:");
Serial.println(i);
}
for (int i=0; i<3; i++)
{
accelOffsets[i] = accelOffsets[i] / sampleSize;
Serial.print("Second I:");
Serial.println(i);
}
Serial.println("Accelerometer calibration complete");
Serial.println("Accelerometer Offsets:");
Serial.print("Offsets (x,y,z): ");
Serial.print(accelOffsets[0]);
Serial.print(", ");
Serial.print(accelOffsets[1]);
Serial.print(", ");
Serial.println(accelOffsets[2]);
}
and the readAccelData() function is as follows
void readAccelData(int16_t * destination){
// x/y/z accel register data stored here
uint8_t rawData[6];
// Read the six raw data registers into data array
I2Cdev::readBytes(MPU6050_ADDRESS, ACCEL_XOUT_H, 6, &rawData[0]);
// Turn the MSB and LSB into a signed 16-bit value
destination[0] = (int16_t)((rawData[0] << 8) | rawData[1]) ;
destination[1] = (int16_t)((rawData[2] << 8) | rawData[3]) ;
destination[2] = (int16_t)((rawData[4] << 8) | rawData[5]) ;
Any idea where I am going wrong?
You have two problems:
You do not initialise your arrays. They start with garbage data in them (space is allocated, but not cleared). You can initialise an array to be all zeros by doing:
int array[5] = {};
This will result in a array that initially looks like [0,0,0,0,0]
Your second problem is that you are creating an array of 16-bit signed integers.
A 16-bit integer can store 65536 different values. Problem is that because you are using a signed type, there are only 32767 positive integer values that you can use. You are overflowing and getting negative numbers when you try and store a value larger than that.
I believe the arduino supports 32-bit ints. If you only want positive numbers, then use an unsigned type.
To use an explicit 32-bit integer:
#include <stdint.h>
int32_t my_int = 0;
Some info on standard variable sizes (note that they can be different sizes based on the arduino model the code is built for):
https://www.arduino.cc/en/Reference/Int
On the Arduino Uno (and other ATMega based boards) an int stores a
16-bit (2-byte) value. This yields a range of -32,768 to 32,767
(minimum value of -2^15 and a maximum value of (2^15) - 1). On the
Arduino Due and SAMD based boards (like MKR1000 and Zero), an int
stores a 32-bit (4-byte) value. This yields a range of -2,147,483,648
to 2,147,483,647 (minimum value of -2^31 and a maximum value of (2^31)
- 1).
https://www.arduino.cc/en/Reference/UnsignedInt
On the Uno and other ATMEGA based boards, unsigned ints (unsigned
integers) are the same as ints in that they store a 2 byte value.
Instead of storing negative numbers however they only store positive
values, yielding a useful range of 0 to 65,535 (2^16) - 1).
The Due stores a 4 byte (32-bit) value, ranging from 0 to
4,294,967,295 (2^32 - 1).
https://www.arduino.cc/en/Reference/UnsignedLong
Unsigned long variables are extended size variables for number
storage, and store 32 bits (4 bytes). Unlike standard longs unsigned
longs won't store negative numbers, making their range from 0 to
4,294,967,295 (2^32 - 1).
With this code:
void calibrateSensors(int16_t * accelOffsets){
int16_t rawAccel[3];
// [...]
accelOffsets[0] += rawAccel[0];
There's an obvious problem: You are adding two 16bit signed integers here. A typical maximum value for a 16bit signed integer is 0x7fff (the first bit would be used as the sign bit), in decimal 32767.
Given your first two sample numbers, 17112 + 17116 is already 34228, so you're overflowing your integer type.
Overflowing a signed integer is undefined behavior in c, because different implementations could use different representations for negative numbers. For a program with undefined behavior, you can't expect any particular result. A very likely result is that the value will "wrap around" into the negative range.
As you already use types from stdint.h, the solution is simple: Use uint32_t for your sums, this type has enough bits for values up to 4294967295.
As a general rule: If you never need a negative value, just stick to the unsigned type. I don't see a reason why you use int16_t here, just use uint16_t.
If I have a signed long variable that holds the whole number part of a decimal and another long variable that holds the fraction part, how would I convert that to a float or double type?
The fraction part is scaled to the 9th place.
Example:
signed long h = -5;
long f = 200073490
Result should be -5.20007349
Example 2:
signed long h = 3;
long f = 500100;
Result should be 3.0005001
Edit
Also: looking for a mathematical solution. Converting to string and scanning it back into float/double will not work in my project.
Since the long int, h, representing the fractional part is scaled by 1000,000,000 you just need to divide it by 1000000000 and correct for the sign in the event the integer portion of the pair is negative. That is you add the scaled fractional part when the base number is positive and subtract the scaled fractional part when the base number is negative. Given that h is the integer portion and f is the fractional portion an expression that combines these to produce a double is:
double result=h + (1-2*(h < 0)) * f/1000000000.0;
The expression (1-2*(h < 0)) yields a 1 when h is not negative otherwise a -1.
I am at a loss with what is happening here. I need to convert a float to an int16_t and back. Here is the syntax:
int16_t val = (int16_t)round((float)0xFFFE/100 * angle);
//and back
float angle = ((float)100/0xFFFE * val;
When I use an initial angle value of -0.093081, it converts back. But when I use 182.241211 it converts back to -17.764824?
Any idea what is going on?
0xFFFE is almost the maximum 16-bit number; and only for an unsigned 16-bit number, at that. If you divide it by 100 and then multiply by 182, it's definitely going to overflow.
Let's do it fully in base 10 for clarity (0xFFFE is 65534):
65534 / 100 * -0.093081 = -60.99970254
65534 / 100 * 182.241211 = 119429.95521674
The full range of your signed 16-bit integer is almost certainly [-32768, 32767]. That last result won't fit.