Force to always print the same number of digits for floats

Hello,

I am looking for an elegant way to always print the same number of digits of a float for display purposes. For example, I want to print 5 digits of the incoming floats, so if the incoming float is 1.23654, I want to print “1.236”. But it should also work if the incoming float is larger or smaller; if the incoming float is 11.23456, it should print “11.23” and if the incoming float is 0.01233456 it should print “0.012”

Do you know how to achieve it? sprintf does not work with floats, and dtostrf() allows you to force the minimum number of digits, not the maximum…

Have you looked at the standard print functions. E.g. Serial.println(someFloat,5);?

Does that mean I can't check the value of the number to be printed?

Yes, I did. The problem with Serial. print (float, 5) is that it prints the float with 5 digits after the "." so, if the float is greater than 1, the total number of digits will be greater than 5.

"elegant way" means "short" (roughly)

And it should work for any float (big one, as for the small one). I can always check if the float is greater than 1 and smaller than 10 and so on, but I'll call it rather rathe "force brut."

 1237
123.7
12.37
1.237
0.124

const char *
fmtFl5 (
    float f )
{
    static char  t [20];

    if (f > 1000)
        dtostrf (f, 5, 0, t);
    else if (f > 100)
        dtostrf (f, 5, 1, t);
    else if (f > 10)
        dtostrf (f, 5, 2, t);
    else
        dtostrf (f, 5, 3, t);
    return t;
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (9600);

    Serial.println (fmtFl5(1236.54));
    Serial.println (fmtFl5(123.654));
    Serial.println (fmtFl5(12.3654));
    Serial.println (fmtFl5(1.23654));
    Serial.println (fmtFl5(0.123654));
    Serial.println (fmtFl5(0.0123654));
}

void loop () { }

So I guess what @gcjr suggested is not acceptable.

It does *. You don't mention how you came to that conclusion, but the specifier needs to be 'f' for floating point numbers, e.g. %02.3f (two zero padded digits before and three after the decimal) rather than e.g %05d which would be for integer digits. However, the specifier would need to be manipulated somehow, or several if ... then sprintf() statements for each count of digits after the decimal point.

With Serial.println(myfloat, 5), the '5' can also be a computed integer variable. It does not have to be a literal.

Nevertheless, the solution in post #7 seems rather more elegant.

*may depend on the MCU present on the board

have you tried it with Arduino?

Have a look at my prinHelpers library.

It can print in scientific notation which might be what you're looking for.

When the float is more than your max number of digits you always have a problem with normal printing as one only allows to set the decimals.

Codewise you can calculate the number of decimals. Something like

Serial.print(fl, 4 - floor(log(fl)/log(10)));

If fl == 102.34567, the log will be 2.nnn so decimals = 2

If fl == 23.5678, the log is 1.nnn so dec = 3

Thanks! I guess there is no way to make it shorter. There is no "native" function for it.

I am currently working on a project with floats that it seems to work fine with, however the board has an RP2040 rather than an Atmel chip. Sounds like I may need to test it with an ATmega....

Thank you robtillaart I am using some of your libraries ;)

Possibly depends on the model :wink:

@bart-fr

Try this sketch on UNO R3, which takes input float number as string from Serial Monitor:

char myData[20];

void setup() {
    Serial.begin(9600);
}

void loop() {
  byte n = Serial.available();
  if(n != 0)
  {
    byte m = Serial.readBytesUntil('\n', myData, sizeof myData - 1);
    myData[m] = '\0';
    float myFloat = atof(myData);
    byte p =  getIntegerDigitCount(myFloat); 
    Serial.println(myFloat, 4-p); //p = number of digits in the integr part of float
  }
}

int getIntegerDigitCount(float val) {
  val = fabsf(val);  // Work with absolute value to handle negatives

  if (val < 1.0f) {
    return 1;  // "0.x" has 1 digit in its integer part ('0')
  }

  return (int)floor(log10(val)) + 1;
}

Input/Output:

  1.23654 ---->   1.237
 11.23456 ---->  11.23
  0.01233456 ---> 0.012

yes. not the same on all boards, especially those with limited memory

No there is not.

@bart-fr

how about negative numbers?
does the negative sign also count for a 'digit' position?
Or are negative numbers not possible in your project?

12.345
-12.34

or 

12.345
-12.345

looks good,

return log10(val) + 1; should work to - implicit cast.

note: floor() already returns an int; so casting is not needed
The function getIntegerDigitCount() already returns int so casting is not needed.

note: ``if (val < 1.0f) should be if (val < 10.0f) - performance wise..