Create fixed char array from float

Me again, I'm now trying to create an char array from float value but fixed with a space.
I've tried the safe string library and dtostrf function

This is my test code I only need 1 value but added for so I can see all at once without having to change each value and upload each time.

/*
   Format:   <sign> XXX.XX
   Famtat:     -, ,0,1,.,3,6
   formaat: byte 0 either +/- sign
   foarmat: byte 1 empty space
   foarmat: byte 2 = 0 (this can be value from 0-9)
   foarmat: byte 3 = 1 (this can be value from 0-9)
   foarmat: byte 4 = . decimal point not moved
   foarmat: byte 5 = 3 (this can be value from 0-9)
   foarmat: byte 4 = 6 (this can be value from 0-9)

*/
#include "SafeString.h"
const byte numChars = 9; //Number of bits to get
float floatVal1 = - 1.36; //example of negative counting
float floatVal2 = 1.36; // example of positive couning
float floatVal3 = - 11.36; //example of negative counting
float floatVal4 = 11.36; // example of positive couning

char Incoming1[numChars];   // an array to store the received data
char Incoming2[numChars];   // an array to store the received data
char Incoming3[numChars];   // an array to store the received data
char Incoming4[numChars];   // an array to store the received data

void setup() {
  Serial.begin(115200);
  for (int i = 3; i > 0; i--) {
    Serial.print(' '); Serial.print(i);
    delay(500);
    Serial.println();

  }
  /*
    SafeString::setOutput(Serial); // for error msgs

    createSafeStringFromCharArray(sfReading1, Incoming1); // wraps in a SafeString and  keeps the current text if any
    sfReading1 = "";
    sfReading1.print(floatVal1, 2);
    createSafeStringFromCharArray(sfReading2, Incoming2); // wraps in a SafeString and  keeps the current text if any
    sfReading2 = "";
    sfReading2.print(floatVal2, 2);
    createSafeStringFromCharArray(sfReading3, Incoming3); // wraps in a SafeString and  keeps the current text if any
    sfReading3 = "";
    sfReading3.print(floatVal3, 2);
    createSafeStringFromCharArray(sfReading4, Incoming4); // wraps in a SafeString and  keeps the current text if any
    sfReading4 = "";
    sfReading4.print(floatVal4, 2);
    Serial.print("SFString floatVal 1:");
    Serial.print(Incoming1 ); // Print btye 2
    Serial.println();
    Serial.print("SFStringe floatVal 2:");
    Serial.print(Incoming2 ); // Print btye 2
    Serial.println();
    Serial.print("SFString floatVal 3:");
    Serial.print(Incoming3 ); // Print btye 2
    Serial.println();
    Serial.print("SFString floatVal 4:");
    Serial.print(Incoming4 ); // Print btye 2
    Serial.println();
  */
  dtostrf(floatVal1, 7, 2, Incoming1); // tried chaging 7 to 6 to 5
  dtostrf(floatVal2, 7, 2, Incoming2); // L
  dtostrf(floatVal3, 7, 2, Incoming3); // L
  dtostrf(floatVal4, 7, 2, Incoming4); // L
  Serial.print("dtstore floatVal 1:");
  Serial.print(Incoming1 ); // Print btye 2
  Serial.println();
  Serial.print("dtstore floatVal 2:");
  Serial.print(Incoming2 ); // Print btye 2
  Serial.println();
  Serial.print("dtstore floatVal 3:");
  Serial.print(Incoming3 ); // Print btye 2
  Serial.println();
  Serial.print("dtstore floatVal 4:");
  Serial.print(Incoming4 ); // Print btye 2
  Serial.println();

}

void loop() {

}

This is the output set to seven and five just moves it closer to the beginning and when the value is negative I lose some digits.

dtstore floatVal 1:  -1.36
dtstore floatVal 2:   1.36
dtstore floatVal 3: -11.36
dtstore floatVal 4:  11.36

But I need to it be like this for the output: Note _ is just an empty space.

dtstore floatVal 1:-_01.36 //negative value lower than 10.00
dtstore floatVal 2:+_01.36 // positive value  lower than 10.00
dtstore floatVal 3:-_11.36 //negative value higher than 10.00
dtstore floatVal 4:+_11.36 //negative value higher than 10.00

It's kind of working but I need the space in between and showing - sign (if negative value). I get the same result if I use safestring library.
Is there an easier way to get the float value to match my required output ?

sprintf() gives you complete control over formatting just about anything, without the nonsense added by the String class.

However, on many older Arduinos like the Uno R3, Mega, etc., float values are not directly supported, and in those cases, you have to convert a float into integer and decimal parts before formatting.

Or, you could manipulate any C-string produced by dtostrf() using the simple functions in the string.h library.

3 Likes

Knowing your range of float values would really help.
Turning them into fixed-point integers would make it easier.

The details need more scope than 1 post to cover.
What you know may shorten that greatly.

Does you float value vary between ]-100, +100[?

Do you always want 2 digits after the decimal point?

Do you want rounding?

What’s your arduino?

Thanks all.
I’m using an ESP32 and the value is reading +/-99.99 as it’s reading a voltage but max would be +/-90.00.
I know it would be easier to use integer but this will be sent over Ble and the receiver cannot convert the value from an integer value.
The decimal point will not need to move and only require 2 decimal places and don’t need rounding
I need the format to remain the same, I’ll have a look at sprintf method I went for the way I’ve done in the above code after doing some reading
Thanks

sprintf works with floats on an ESP32 (but won't get you exactly the output format you want)

Can you not take your original voltage reading and convert it to integer millivolts?

1 Like

try something like this

float floatVals[] = {-1.36, 1.36, -11.36, 11.36}; // between -90 and +90
char textBuffer[16]; // has to be large enough for your needs (don't forget the trailing null char). 

void setup() {
  Serial.begin(115200);
  for (float &f : floatVals) { // for each float in the array
    int written = snprintf(textBuffer, sizeof textBuffer, "%+5.2f", f); // see https://cplusplus.com/reference/cstdio/snprintf/
    if (written > 0 &&  written < sizeof textBuffer) {
      // insert enough spaces after the sign to keep the decimal point in the same positon
      memmove(textBuffer + 8 - written, textBuffer + 1, written); //  see https://cplusplus.com/reference/cstring/memmove/
      for (byte i = 1; i <= 7 - written; i++) textBuffer[i] = ' '; // you could use also memset() for this https://cplusplus.com/reference/cstring/memset/
      Serial.print("|");
      Serial.print(textBuffer);
      Serial.print("| with input ");
      Serial.println(f, 2);
    }
  }
}

void loop() {}

you should see in the Serial Monitor

|-  1.36| with input -1.36
|+  1.36| with input 1.36
|- 11.36| with input -11.36
|+ 11.36| with input 11.36

you could make it more resistant to memory overflow by ensuring you have enough space for the memmove and not check just written < sizeof textBuffer but take into account how many spaces will need to be inserted or just ensure the input value is between ]-100, 100[

the buffer I used has 16 bytes, it's oversized for your needs here as you really need only 8 bytes for "± ii.dd\0"

Thank you for your example code I will try it when I get back to computer and update here . Thank you all for helping Really appreciate it.

An alternative that does everything in the snprintf() line, by separating out the sign and printing the absolute value of the float.

    snprintf(textBuffer, sizeof textBuffer, "%c %05.2f", (f < 0.0) ? '-' : '+', abs(f));

that would lead to 0 padding instead of space padding, you'll likely get

|- 01.36| with input -1.36
|+ 01.36| with input 1.36
|- 11.36| with input -11.36
|+ 11.36| with input 11.36

OP can decide if it's acceptable to have the leading 0

That fits the desired output, a space between the sign and the number, with the number having leading zeros.

Be very careful when using abs with floating point types. The C function abs only works on integers. Your code might work for some Arduino boards, because it defines a terrible abs macro, but it won't work in general (especially since some libraries explicitly #undef abs for portability reasons).

#include <iostream>
#include <cmath>

int main() {
    std::cout << abs(-3.14) << std::endl;       // C function (wrong)
    std::cout << fabs(-3.14) << std::endl;      // C function (correct, not recommended)
    std::cout << std::abs(-3.14) << std::endl;  // C++ function (correct)
    using std::abs;
    std::cout << abs(-3.14) << std::endl;       // C++ function (correct, allows for ADL)
}
3
3.14
3.14
3.14

right I had missed that. all good then ! (besides the possible abs stuff)

Maybe a safer method that does not use abs()

    snprintf(textBuffer, sizeof textBuffer, "%c %05.2f", (f < 0.0) ? '-' : '+', (f < 0.0) ? -f : f);

I was aware there could be problems with abs(), but the use of floats in snprintf() is also not going to work on most Arduino boards. Another possible problem is whether to use float or double.

There is no format for a float in printf, because if you attempt to pass a float it will be promoted to double (due to the fact that variadic parameters undergo default promotions )

J-M-L thanks for the code it works great but one slight problem,
I only need one float converting and when I tried to alter and compile the code below I get and
error.
When I change this line

float floatVals[] = {-1.36, 1.36, -11.36, 11.36}; // between -90 and +90

to this

float floatVals = -1.36; // between -90 and +90

I get error

exit status 1
'begin' was not declared in this scope

Here is the altered code:

float floatVals = -1.36; // between -90 and +90
char textBuffer[16]; // has to be large enough for your needs (don't forget the trailing null char).
char textBuffer1[16]; // has to be large enough for your needs (don't forget the trailing null char).
void setup() {
  Serial.begin(115200);
  for (float &f : floatVals) { // for each float in the array
    int written = snprintf(textBuffer, sizeof textBuffer, "%+5.2f", f); // see https://cplusplus.com/reference/cstdio/snprintf/
    if (written > 0 &&  written < sizeof textBuffer) {
      // insert enough spaces after the sign to keep the decimal point in the same positon
      memmove(textBuffer + 8 - written, textBuffer + 1, written); //  see https://cplusplus.com/reference/cstring/memmove/
      //  snprintf(textBuffer1, sizeof textBuffer1, "%c %05.2f", (f < 0.0) ? '-' : '+', (f < 0.0) ? -f : f);
      for (byte i = 1; i <= 7 - written; i++) textBuffer[i] = ' '; // you could use also memset() for this https://cplusplus.com/reference/cstring/memset/
      Serial.print("|");
      Serial.print(textBuffer);
      Serial.print("| with input ");
      Serial.println(f, 2);
      Serial.println("###########");
      Serial.print("|");
      Serial.print(textBuffer1);
      Serial.print("| with input ");
      Serial.println(f, 2);
    }
  }
  // snprintf(textBuffer1, sizeof textBuffer1, "%c %05.2f", (f < 0.0) ? '-' : '+', (f < 0.0) ? -f : f);
}

void loop() {}

Thanks to everyone that has replied I am working my way through your suggestions and trying them. This is a massive help and giving me a greater knowledge and understanding on other functions that I've never used before

You don’t have an array, so you don’t need to go through each value get rid of the for loop

Just use floatVals instead

    int written = snprintf(textBuffer, sizeof textBuffer, "%+5.2f", floatVals); 
    if (written > 0 &&  written < sizeof textBuffer) {
      memmove(textBuffer + 8 - written, textBuffer + 1, written); 
      for (byte i = 1; i <= 7 - written; i++) textBuffer[i] = ' ';
    }
1 Like

Thank you, I kick my self I was going to remove the for loop but would of not thought of replacing the f with vloatvals.
I will be reading up on the links in your code to get a better understanding

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.