Round to first significant digit

I want to round a decimal number (x),but I need to do it differently depending on how it is. If it has integer significant digits, we round to the nearest integer, and if not, we round to the first significant digits, So the following would happen with these example numbers: 11.871->12, 11.177->11, 37.233->37, 37.779->38, 0.578->0.6, 0.544->0.5, 0.0257->0.03, 0.0223->0.02... (The numbers could have more or less decimal places than in the examples).

To implement this, I have created a function (type) in which we check if the most significant digit is of type integer or decimal. This function returns this characteristic of the number in the form of a "string". So if the most significant digit is an integer we just use the "round()" function. However, I don't know how to round a number to the first significant digit (roundto_fsd() function). Does anyone know of any function capable of doing this or any method to do it?

float x;

char type(float number) {
  char type[8];
  if (int(number) != 0) {
    type = "integer";
  } else {
    type = "decimal";
  }
  return type;
}

float roundto_fsd(float number) {}

void setup() {
  Serial.begin(9600);
  if (!type(x).compareTo("integer")) {
    Serial.println(round(x));
  } else if (!type(x).compareTo("decimal")) {
    Serial.println(roundto_fsd(x));
  }
}

void loop() {}

You could try

float x;

long y = x + 0.5; // cast to an integer drops the decimal part (truncates)

You might want to fine tune this if x can be negative depending on how you want to round it

Édit: ignore this as I just realized that you just don’t want to round to integer. Forget what I said

for number > 0.1 try round

#include <stdio.h>
#include <math.h>
 int main() {
       float i=5.4;
       while(1) {
           scanf("%f",&i);
           if(i>=1.0)
             printf("round of  %f is  %f\n", i, round(i));
           else
             printf("round of  %f is  %f\n", i, round(i*10.0f)/10.0f);
      }
        return 0;
}

a run gives

11.87
round of  11.870000 is  12.000000
11.17
round of  11.170000 is  11.000000
37.2
round of  37.200001 is  37.000000
37.8
round of  37.799999 is  38.000000
.578
round of  0.578000 is  0.600000
.544
round of  0.544000 is  0.500000

A clumsy way:

Get the ascii representation of the float number and scan it for the first non zero element

PS: your type function should return a const char * (or best make a function returning true or false rather than text)

Maybe something like this?

float myRound(float f) {
  if (f) {
    float m = 1;

    while (abs(f * m) < 1) {
      m *= 10;
    }

    return round(f * m) / m;
  }
  return 0;
}

[note] Suggestion from @J-M-L incorporated.

If you are really serious, then group the input numbers as:
xx.xxxx..
0.xxxx..
0.02xx..

and then execute Serial.print() command; you get the following output:
Given:

float myNum[] =
  {
    11.871, 11.177, 37.233, 37.779, 0.578, 0.544, 0.0257, 0.0223
  };

This is my output; where, the numbers have been grouped into three based on their characteristics.

12
11
37
38

0.6
0.5

0.03
0.02

???

how is that anywhere close to answering OP's request ?

if you do this you'll likely get 0. Make m a float

float myNum[] = {11.871, 11.177, 37.233, 37.779, 0.578, 0.544, 0.0257, 0.0223};

float myRound(float f) {
  if (f == 0) return 0;

  float m = 1;
  while (abs(f * m) < 1)  m *= 10;
  return round(f * m) / m;
}

void setup() {
  Serial.begin(115200); Serial.println();
  for (auto& f : myNum) {
    Serial.print(f, 6);
    Serial.print(F("\t==> "));
    Serial.println(myRound(f),6);
  }
}

void loop() {}

That must be because of how round is defined then, it works fine when using math.h.

I'll change it anyway.

I agree it's weird. round should return a double and thus the math should work

it does work if you do the operation in 2 steps

float myRound(float f) {
  if (f == 0) return 0;

  size_t m = 1;
  while (abs(f * m) < 1)  m *= 10;
  double r = round(f * m);
  return r / m;
}

interesting...

There you have it.

#define round(x)     ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))
1 Like

I have presented in post #6 what OP has requested above. He has neither asked for a unified approach nor for saving the results in variables. (Do you think that there possibly exits a unified approach?)

ah the joy of Arduino's custom macros for standard functions (even exist in avr-libc) :slight_smile:

1 Like

No you did not meet this requirement

you use print() which by default print and round to 2 digits. so your solution does not work for 0.000345 for example. You'll get 0.00

consider

#include <stdio.h>
#include <math.h>
 int main() {
    float i=5.4;
    while(1) {
        scanf("%f",&i);
        int divisor=1;
        while(divisor*i < 1.0f) divisor*=10;
        printf("divisor %d round of  %f is  %f\n", divisor, i, round(i*divisor)/divisor);
    }
    return 0;
}

a run gives

11.871
divisor 1 round of  11.871000 is  12.000000
11.177
divisor 1 round of  11.177000 is  11.000000
37.33
divisor 1 round of  37.330002 is  37.000000
37.78
divisor 1 round of  37.779999 is  38.000000
5.32
divisor 1 round of  5.320000 is  5.000000
5.78
divisor 1 round of  5.780000 is  6.000000
.532
divisor 10 round of  0.532000 is  0.500000
.578
divisor 10 round of  0.578000 is  0.600000
.0532
divisor 100 round of  0.053200 is  0.050000
.0578
divisor 100 round of  0.057800 is  0.060000
.00532
divisor 1000 round of  0.005320 is  0.005000
.00578
divisor 1000 round of  0.005780 is  0.006000
.000532
divisor 10000 round of  0.000532 is  0.000500
.000578
divisor 10000 round of  0.000578 is  0.000600

clearly does not work for negative numbers

For this kind of numbers, there is another group and the result comes alright.

0.0003

In my print(ar1, arg2) method, the value of arg2 changes based on the groupings.

The OP is asking for 37 AND NOT 37.000000?

using dtostre() and atof() probably involves less lines of code! :wink:

void setup()
{
  float myNum[8] = {11.871, 11.177, 37.233, 37.779, 0.578, 0.544, 0.0257, 0.0223};
  float f;

  Serial.begin(115200);

  for (int i = 0; i < 8; ++i) {
    if (myNum[i] >= 1) {
      long y = myNum[i] + 0.5;
      f = y;
    }
    else {
      char s[10];
      dtostre(myNum[i], s, 0, 0); //Round to first significant digit
      f = atof(s);
    }
    Serial.println(f, 4);
  }
}

void loop()
{
}

Output:

12.0000
11.0000
37.0000
38.0000
0.6000
0.5000
0.0300
0.0200

hope that helps...

The OP asks for 0.03 and NOT 0.0300

serial.print() in my shared code is set to print to 4dp!!!!

atof() output is still 0.03!

that is a difference between rounding off and number of decimal places btw

OP also said:

so pending his input, example below could be better suited for his purpose as alternative to reply#18 code.
(version2 section in the code might be more elegant but version1 IMHO is probably more 'efficient' :wink: )

#define Str_Len 10 //size array such that smallest intended decimal value can fit in

void setup()
{
  float myNum[8] = {11.871, 11.177, 37.233, 37.779, 0.578, 0.544, 0.0257, 0.000223};

  Serial.begin(115200);

  for (int i = 0; i < 8; ++i) {
    if (myNum[i] >= 1) {
      long y = myNum[i] + 0.5;
      Serial.println(y); //print out integer
    }
    else {
      char s[Str_Len];
      //create a string of float number in scientific format rounded to first significant digit
      dtostre(myNum[i], s, 0, 0);
      Serial.print(s);
      Serial.print(", ");
      //convert scientific format string to correcponding decimal format (works for up to 9dp)
      //-----------------------------------------------------------------*/
      //version 1
      char d = s[0], j = (s[4] & 0x0F) + 1;
      memset(s, '0', Str_Len);
      s[1] = '.';
      s[j++] = d;
      s[j] = '\0';
      //-----------------------------------------------------------------*/
      //version 2
      //dtostrf(atof(s), 3, s[4] & 0x0F, s);
      //-----------------------------------------------------------------*/
      Serial.println(s);
    }
  }
}

void loop()
{
}

Output:

12
11
37
38
6e-01, 0.6
5e-01, 0.5
3e-02, 0.03
2e-04, 0.0002