float variable with single decimal place

hello guys,
this thing is killing me.
i'm using a ds18b20 temperature sensor and temperature value is (example) 28.45
i need to store this value in a variable named temp, but it must have only one decimal point, in this case: 28.5
i have searched all over, but can't find how to round this valu to only one decimal point and store in other variable.
hope someone can help

This is the diagram that shows how one can determine the precision (number of digits after the decimal point) of the temperature reading of DS18B20 sensor.
ds18b20accuracy.png

To keep 1-digit after the decimal point, you can operate the sensor at 9-bit resolution.

ds18b20accuracy.png

1 Like

it must have only one decimal point, in this case: 28.5

Can you explain why?

To print a float variable (e.g. temp) with one decimal place, use "Serial.println(temp, 1)"

i have searched all over

Did you try Google? A search for "c/c++ round float value" revealed a bunch of examples for me.

1 Like

can't find how to round this valu to only one decimal point and store in other variable.

What are you going to do with this "other variable" ?

  int16_t otherVariable = temperatureC * 10;

//now otherVariable is a fixed-point representation of the temperature
//with 1 decimal place

Serial.print("Other variable: ");
Serial.print(otherVariable);
Serial.print(" = ");
Serial.print((float)otherVariable/10, 1);
Serial.println("C");

thanks for your replies folks.

using Serial.print is easily done (but i don't want to print it, i want to save it, formated in another variable).
there should be a similar function to specify the number of decimal point
example:

a = function(b,1);

// b = 2.762
// a = 2.8

what am i going to do with it? why i need it?
don't get me wrong, but this are "wrong" questions. i mean, why not!!?
google search does return a lot of examples, but most of them are using Serial.print, and other doesn't apply to this case.

this should be an easy math operation.
this is not fulcral to my project, i just stomp on it. but now i must figure it out.

float x = 123.456;
long val;

val = (long) (x * 10L); // val = 1234
x = (float) val / 10.0; // x = 1234 / 10.0 = 123.4

There are more elegant ways to do this, but this should work.

3 Likes

econjack:
float x = 123.456;
long val;

val = (long) (x * 10L); // val = 1234
x = (float) val / 10.0; // x = 1234 / 10.0 = 123.4

There are more elegant ways to do this, but this should work.

your code almost get it done. i just test it on tinkercad

// if x = 123.456
// Serial.print return 123.5 (which is correct)
// and the code you provide return 123.40 
// (which is not the best answer. it did not round the number and still with 2 decimal places)

there should be a similar function to specify the number of decimal point

The fatal flaw in that argument is that C++ does not have a native float data type with only one decimal place.

One way round this us to do

void setup()
{
  Serial.begin(115200);
  float x = 123.456;
  long val;
  val = (long) (x * 10L);      // val = 1234
  Serial.println(val);
}

void loop()
{
}

and add the decimal place when you need to use the variable, which is why I asked what you were going to do with it

this should be an easy math operation.

It is, but you have not been clear about your goal.

There are different approaches to "rounding". Research those and decide which you prefer, for both positive and negative numbers.

And this will be for display only, because as noted above,

C++ does not have a native float data type with only one decimal place

If you really want to do this:

i want to save it, formated in another variable

use dtostrf() to save the rounded result, formatted as a ASCII C-string.

A float is 4 bytes wide. That holds about 6-7 significant digits. You can't save any space by storing fewer digits. The float is 4 bytes wide no matter what number is held.

On a printed page or a screen, nobody wants to read all those digits. You probably want to show many numbers so printing them "narrower" makes a lot of sense to save space.

Also, the binary float cannot properly represent all decimal numbers. There is no exact representation of 0.1. It can store a number that is very close to 0.1 but it is not equal to 0.1. So rounding off and storing in a float will introduce errors.

It is best to do all calculation with native float and then round for display.

So how does 123.456 become 123.5?
Multiply by 100
You get 12345.6
Add 5
You get 12350.6
Treat as an integer
You get 12350
Treat as a float and divide by 100
You get 123.5

Johan_Ha:
So how does 123.456 become 123.5?
Multiply by 100
You get 12345.6
Add 5
You get 12350.6
Treat as an integer
You get 12350
Treat as a float and divide by 100
You get 123.5

What data type are you going to save the 123.5 in that has only one decimal place ?

Johan_Ha:
So how does 123.456 become 123.5?
Multiply by 100
You get 12345.6
Add 5
You get 12350.6
Treat as an integer
You get 12350
Treat as a float and divide by 100
You get 123.5

Implementation (Option-1)

void setup()
{
  Serial.begin(9600);
  byte myDigit[6] = {0};
  float x = 123.456;
  x = x * 100.0;         //12345.6
  int y = (int)x; //12345 = 0x3039

  //--------rounding process--------------------------------------
  for (int i = 0; i < 5; i++)   //get the digits of the decimal number
  {
    myDigit[i] = y % 10;
    y = y / 10;
  }
  if (myDigit[0] >= 5)     //add 1 with myDigit[1] if myDigt[0] >= 5
  {
    myDigit[0] = 0;
    myDigit[1] = myDigit[1] + 1;
    if (myDigit[1] == 10)
    {
      myDigit[1] = 0;
      myDigit[2] = myDigit[2] + 1;
      if (myDigit[2] == 10)
      {
        myDigit[2] = 0;
        myDigit[3] = myDigit[3] + 1;
        if (myDigit[3] == 10)
        {
          myDigit[3] = 0;
          myDigit[4] = myDigit[4] + 1;
          if (myDigit[4] == 10)
          {
            myDigit[4] = 0;
            myDigit[5] = myDigit[5] + 1;
          }
        }
      }
    }
  }

  //------------------------------------------------------------------------------------
  int z = myDigit[0]*1 + myDigit[1]*10 + myDigit[2]*100 + myDigit[3]*1000 + myDigit[4]*10000 + myDigit[5]*100000;
  float z1 = (float)z/100.0;   // z1 exactly contains: 123.5 ===> 0x42F70000
 //----------------------------------------------------------------------------------
  long *p;
  p = (long*) &z1;
  long m = *p;
  Serial.println(m, HEX);    //m = 0x42F70000 = binary32 formatted 32-bit value
  //---------------------------------------------------------------------------------

  Serial.println(z1, 1);      //shows: 123.5
}

void loop() 
{
 
}

Implementation (Option-2)

void setup()
{
  Serial.begin(9600);
  //---------------------
  float x = 123.456;
  x = x + 0.05;       //123.506
  x = x*10.0; //1235.06
  int y = (int)x; //1235
  float z = (float)y/10.0; //123.5
  Serial.println(z, 1);  //shows: 123.5
  
  //----------------------------------------------------------------------------------
  long *p;
  p = (long*) &z;
  long m = *p;
  Serial.println(m, HEX);    //m = 0x42F70000 binary32 formatted 32-bit value
  //---------------------------------------------------------------------------------
}

void loop()
{

}

Implementation (Option-3)
You want to present the temperature reading of DS18B20 sensor with 1-digit precision for which you can operate the sensor at 9-bit resolution (Bit11 - Bit3). The reading will always be at 0.50 interval.
ds18b20accuracy.png

#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 2

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
DeviceAddress insideThermometer;

void setup(void)
{
  Serial.begin(9600);
  sensors.begin();
  sensors.getAddress(insideThermometer, 0); 
  sensors.setResolution(insideThermometer, 9);
}

void loop(void)
{ 
  sensors.requestTemperatures(); // Send the command to get temperatures
  float tempC = sensors.getTempC(insideThermometer);
  Serial.print("Temp C: ");
  Serial.println(tempC, 1);
  delay(1000);
}

sm143.png

sm143.png

2 Likes

UKHeliBob:
What data type are you going to save the 123.5 in that has only one decimal place ?

There is no such datatype. If that is very important, one has to create such a datatype. Like a C++ class. I only showed how to convert 123.456 to 123.5. Of course, a float can't be trusted to hold exactly 123.5 because the actual data is 2- or 16-based, not 10-based and the value 123.5 is 10-based. Which bring us back to the question why anyone really wants to round off a value to one decimal and continue using the value for other purposes than just showing it.

Johan_Ha:
the value 123.5 is 10-based.

Bad example. The value 123.5 does indeed have an exact representation in binary: 1111011.1

You're right. That would be 00000011011101110000000000000000 as a 32 bit float and that's exactly 123.5.

Johan_Ha:
You're right. That would be 00000011011101110000000000000000 as a 32 bit float and that's exactly 123.5.

How have you found the above bit pattern for the float number: 123.5?
The correct 32-bit value (called binray32) is:0100 0010 1111 0111 0000 000 000 0000

The bit patter should come from either or both of the following two approaches:
A: Using Program Codes

void setup()
{
  Serial.begin(9600);
  float z1 = 123.5;
  long *p;
  p = (long*) &z1;
  long m = *p;
  Serial.println(m, BIN); //m = 0x42F70000 = binary32 formatted 32-bit value
  Serial.println(m, HEX); // m = 0100 0010 1111 0111 0000 000 000 0000
}

void loop() 
{
  
}

B: Using binary32 Template
binary32.png

(a) Calculating Binary of 123
==> 1111011

(b) Calculating Binary of 0.5
==> 0.5 x2 = 1.0

(c) (123.5)10
==> (1111011.1)2
==> 1.1110111)*26

(d) The binray32 format:
Sign (1-bit): b31 : 0 (this is a positive number)
Biased exponent (8-bit: b30 - b23: 6(from Step-c)+127(fixed bias) = 133 = 85h
Binary fraction digits (23-bit: b22-b0) : 11101110000000000000000 (from Step-c)
(e) Arranging as nibbles/bit:
==> 0 10000101 11101110000000000000000
==> 01000010111101110000000000000000
==> 0100 0010 1111 0111 0000 0000 0000 0000
==> 42F70000 (in hex)

C: Reconstruction of float number from binary32 bit pattern of Step-e

==> (0)0*(1 + b23*2-1+, ..., +b0*2-24)*2(e-127)

==> 1*(1 + 12-1 + 12-2 + 12-3 + 02-4+12-5 + 12-6 + 1*2-7 + 0 +,... + 0)*2133-127

==> (1+0.5 + 0.25 + 0.125 + 0 + 0.03125 + 0.015625 + 0.0078125)*64
==> 123.5 (ok!) (in some cases, the float numbers are exact!)

I missed the exponent thing. I just did 610 = 000001102, missing the +127 thing.

thanks you guys for your reply's (sorry for the late reply, busy time at school)
Serial.print is not an option

why!?
i'm trying to automate my aquarium, and one of the features is temperature control. i've create a file to handle the temperature coding, the readings are saved in a structure, one variable (tempVal) of the structure hold the bare temperature (28.34) and other variable (tempTxt) hold the formated temperature (28.34 ºC).

printing the values can be achived by:

lcd.print(dsTemp.tempVal,1);
lcd.print((char)223 + String("C"));
// print to the lcd 28.3 ºC. very good

but outside the temperature file, i don't wan´t to be doing anything but print an already formated value
so rounding the temperature value and formate it to xx.x ºC must be done inside the temperature file.
outside that file i'm just print a ready to use variable. like this:

lcd.print(dsTemp.tempTxt);
// at this time without rounding the value, is printing to the lcd 28.34 ºC. is good but not great

i've tested the sugestions and the best one for my application, so far, is using dtostrf()
inside the temperature file:

char tmpOutput[5];

temp.tempVal = sensors.getTempC(tempSensor);
dtostrf(temp.tempVal, 2, 1, tmpOutput);
temp.tempTxt = (String)tmpOutput + " " + (char)223 + "C";

  return temp;

thanks again you guys

1 Like