dtostrf() conversion function problems?

While playing around with a ESP8266 DevBoard and Arduino IDE 1.6.4 found that it's returning wrong values for any float with more than 1 "0" after "." aka small values like 0.0256.

Try and check the result with the simple code below for 0.256, 0.0256, 0.00256.

void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
}

// the loop routine runs over and over again forever:
void loop() {

 char charVal[10];          //temporarily holds data from vals 
 String stringVal = "";     //data on buff is copied to this string
 
 float fVal = 0.0256;
 Serial.print("Value :");
 Serial.println((float)fVal);
 
 dtostrf(fVal, 8, 4, charVal);  //4 is mininum width, 3 is precision; float value is copied onto buff

 stringVal = charVal;
 int strl = stringVal.length()-1;

 Serial.print("Conv val :"); Serial.println(charVal); 
 Serial.print("Length: ");Serial.println(strl); //display string 
 Serial.print("Conv val :"); Serial.println(stringVal);
 Serial.println("\n");
  delay(3000);        // delay in between reads for stability
}

For reference: MAX7219 8 Bit Display module driver - ESP8266.

Not sure what the problem is, but if you want to print floats with more than 2 decimal places you add a parameter to the print statement.

Serial.print(someFloat, 6);

will print to 6 decimal places.

The problem is that is not returning proper values in the string/char variable after conversion.

I am using the resulted string to print it on a MAX7219 8 Digit display module as referenced above.

Actually try please also 1.023 and 1.23. Same story. Any "0" after dot is a problem.
I think is a bug if I'm not using it totaly wrong above.

Please post the output that you get from the program that you posted.

UKHeliBob:
Please post the output that you get from the program that you posted.

Let's try the code below, much simpler:

void setup() {
 // initialize serial communication at 9600 bits per second:
 Serial.begin(9600);
}

void print_float(int no, float fVal, String sVal )
{
char charVal[12];          //temporarily holds data from vals 
String stringVal = "";     //data on buff is copied to this string

dtostrf(fVal, 8, 4, charVal);  //4 is mininum width, 3 is precision; float value is copied onto buff

stringVal = charVal;
int strl = stringVal.length()-1;

Serial.print(no);  //just a order number
Serial.print(". - Expected value :"); Serial.print(sVal);  //the number represented as string for reference
Serial.print(" - String Length: ");Serial.print(strl); //display lenght of the obtained string after conversion 
Serial.print(" - Conversion value in stringVal :"); Serial.println(stringVal); //display the value as stored in stringVal
}

void loop() {
   print_float(1,1.256,"1.2560");   //No 1
   print_float(2,1.0256,"1.0256");  //No 2
   print_float(3,1.00256,"1.0026"); //No 3
   Serial.println();  
   delay(5000);        // delay in between reads for stability
}

The result:

1. - Expected value :1.2560 - String Length: 5 - Conversion value in stringVal :1.2560
2. - Expected value :1.0256 - String Length: 4 - Conversion value in stringVal :1.256
3. - Expected value :1.0026 - String Length: 3 - Conversion value in stringVal :1.26

Smells like a bug for me.

    • Expected value :1.2560 - String Length: 7 - Conversion value in stringVal : 1.2560
    • Expected value :1.0256 - String Length: 7 - Conversion value in stringVal : 1.0256
    • Expected value :1.0026 - String Length: 7 - Conversion value in stringVal : 1.0026

Is what I get when I run the latest code that you posted.

    • Expected value :1.2560 - String Length: 5 - Conversion value in stringVal :1.2560
    • Expected value :1.0256 - String Length: 4 - Conversion value in stringVal :1.256
    • Expected value :1.0026 - String Length: 3 - Conversion value in stringVal :1.26

Not the same, hmmm?

Are you running that code on the 8266 dev board or a on an Arduino?

groundfungus:
Are you running that code on the 8266 dev board or a on an Arduino?

On a ESP8266 CBDBv2 EVO DevBoard (ESP07 module on it)
Hardware it's working OK, after dropped the dtostrf() function and done a separate conversion function.

For reference: MAX7219 8 Bit Display module driver - ESP8266.

On what Arduino version did you run it succesfully?
From what I see from your result above looks maybe like a ESP8266 lib only bug.

I ran it on an Uno with IDE ver 1.04.

groundfungus:
I ran it on an Uno with IDE ver 1.04.

ESP8266 with Arduino IDE 1.6.4 here

trackerj:
ESP8266 with Arduino IDE 1.6.4 here

Not exactly a direct comparison then...

UKHeliBob:
Not exactly a direct comparison then...

If somebody has IDE 1.6.4 and a Arduino board please run a test and post the results here. Latest Code from above.

I am quite far this days from any Arduino board sources.

Thank you.

If somebody has IDE 1.6.4 and a Arduino board please run a test and post the results here.

I just ran your code with IDE 1.6.4 and a UNO and got the correct output

    • Expected value :1.2560 - String Length: 7 - Conversion value in stringVal : 1.2560
    • Expected value :1.0256 - String Length: 7 - Conversion value in stringVal : 1.0256
    • Expected value :1.0026 - String Length: 7 - Conversion value in stringVal : 1.0026

cattledog:
I just ran your code with IDE 1.6.4 and a UNO and got the correct output

Thank you.

As the hardware is working OK without the dtostrf() usage, so is not a hardware problem, the BUG might be in the ESP8266 dtostrf() implementation.

For ESP8266 extension the "dtostrf()" function is hidding in
"core_esp8266_noniso.c" and is called by "#include<stdlib_noniso.h>"

If anybody want to have fun, a copy below:

char * dtostrf(double number, signed char width, unsigned char prec, char *s) {

    if(isnan(number)) {
        strcpy(s, "nan");
        return s;
    }
    if(isinf(number)) {
        strcpy(s, "inf");
        return s;
    }

    if(number > 4294967040.0 || number < -4294967040.0) {
        strcpy(s, "ovf");
        return s;
    }
    char* out = s;
    // Handle negative numbers
    if(number < 0.0) {
        *out = '-';
        ++out;
        number = -number;
    }

    // Round correctly so that print(1.999, 2) prints as "2.00"
    double rounding = 0.5;
    for(uint8_t i = 0; i < prec; ++i)
        rounding /= 10.0;

    number += rounding;

    // Extract the integer part of the number and print it
    unsigned long int_part = (unsigned long) number;
    double remainder = number - (double) int_part;
    out += sprintf(out, "%d", int_part);

    // Print the decimal point, but only if there are digits beyond
    if(prec > 0) {
        *out = '.';
        ++out;
    }

    while(prec-- > 0) {
        remainder *= 10.0;
    }
    sprintf(out, "%d", (int) remainder);

    return s;
}

Delta_G:
Well that's interesting. Definitely a bug. They turn the remainder part into an integer, but they don't add any leading zeroes to it when they print it into the buffer.

So if the number is 15.00256 with a precision of 5. First they put in the 15, that's fine. Then they print a decimal, that's OK. Then they multipy the 0.00256 by 10 5 times to get 256. They then just add that to the buffer as an int. So it just adds "256" where it should add "00256".

It would be easy enough to fix by changing this section:

while(prec-- > 0) {

remainder *= 10.0;
    }




To:



while(prec-- > 0) {
        remainder *= 10.0;
        if((int)remainder == 0){
                *out = '0';
                ++out;
        }
    }




That would keep adding zeroes to the buffer until the remainder part gets at least one non-zero digit. 

Try that and see if it works. 


EDIT: I missed the closing brace on my if block. Derp. Fixed now.

I was looking in the same direction on that piece of code, well spotted.

I can confirm that now dtostrf() is working OK also on ESP8266 DevBoard.

Credit goes to you so ++karma :slight_smile:

Delta_G:
Glad I could help. Thanks for the karma bump.

I assume that you can report the bug and fix somewhere so they can fix their core library.

Yep, done it already, will see if they will update the main code with the changes or not.
In my particular application case, will keep my own function as is working very well and doesn't need to add any extra libs.

Thank you all for your prompt response that has helped to confirm the ESP8266 lib bug and fix it.

I really don't know much about pointers and type coercion, but here's my attempt.
Would this be any good?

char * dtostrf(double number, signed char width, unsigned char prec, char *s) {

    if(isnan(number)) {
        strcpy(s, "nan");
        return s;
    }
    if(isinf(number)) {
        strcpy(s, "inf");
        return s;
    }

    if(number > 4294967040.0 || number < -4294967040.0) {
        strcpy(s, "ovf");
        return s;
    }
    char* out = s;
    // Handle negative numbers
    if(number < 0.0) {
        *out = '-';
        ++out;
        number = -number;
    }

    // Round correctly so that print(1.999, 2) prints as "2.00"
    double rounding = 0.5;
    for(uint8_t i = 0; i < prec; ++i)
        rounding /= 10.0;

    number += rounding;

    // Extract the integer part of the number and print it
    unsigned long int_part = (unsigned long) number;
    double remainder = number - (double) int_part;
    out += sprintf(out, "%d", int_part);

    // Print the decimal point, but only if there are digits beyond
    if(prec > 0) {
        *out = '.';
        ++out;
    }

    // Print the digits after the decimal point
    int8_t digit = 0;
    while(prec-- > 0) {
        remainder *= 10.0;
        digit = (int8_t)remainder;
        if (digit > 9) digit = 9; // insurance
        *out = (char)('0' | digit);
        ++out;
        remainder -= digit;
    }
    
    return s;
}

I made another modification: I optimized out most of the divisions.
(I assume divisions are much slower than multiplications.)

char * dtostrf(double number, signed char width, unsigned char prec, char *s) {

    if(isnan(number)) {
        strcpy(s, "nan");
        return s;
    }
    if(isinf(number)) {
        strcpy(s, "inf");
        return s;
    }

    if(number > 4294967040.0 || number < -4294967040.0) {
        strcpy(s, "ovf");
        return s;
    }
    char* out = s;
    // Handle negative numbers
    if(number < 0.0) {
        *out = '-';
        ++out;
        number = -number;
    }

    // Round correctly so that print(1.999, 2) prints as "2.00"
    // I optimized out most of the divisions
    double rounding = 2.0;
    for(uint8_t i = 0; i < prec; ++i)
        rounding *= 10.0;        
    rounding = 1.0 / rounding;    

    number += rounding;

    // Extract the integer part of the number and print it
    unsigned long int_part = (unsigned long) number;
    double remainder = number - (double) int_part;
    out += sprintf(out, "%d", int_part);

    // Print the decimal point, but only if there are digits beyond
    if(prec > 0) {
        *out = '.';
        ++out;
    }

    // Print the digits after the decimal point
    int8_t digit = 0;
    while(prec-- > 0) {
        remainder *= 10.0;
        digit = (int8_t)remainder;
        if (digit > 9) digit = 9; // insurance
        *out = (char)('0' | digit);
        ++out;
        remainder -= digit;
    }
   
    return s;
}

odometer:
I made another modification: I optimized out most of the divisions.
(I assume divisions are much slower than multiplications.)

char * dtostrf(double number, signed char width, unsigned char prec, char *s) {

if(isnan(number)) {
        strcpy(s, "nan");
        return s;
    }
    if(isinf(number)) {
        strcpy(s, "inf");
        return s;
    }

if(number > 4294967040.0 || number < -4294967040.0) {
        strcpy(s, "ovf");
        return s;
    }
    char* out = s;
    // Handle negative numbers
    if(number < 0.0) {
        *out = '-';
        ++out;
        number = -number;
    }

// Round correctly so that print(1.999, 2) prints as "2.00"
    // I optimized out most of the divisions
    double rounding = 2.0;
    for(uint8_t i = 0; i < prec; ++i)
        rounding *= 10.0;       
    rounding = 1.0 / rounding;

number += rounding;

// Extract the integer part of the number and print it
    unsigned long int_part = (unsigned long) number;
    double remainder = number - (double) int_part;
    out += sprintf(out, "%d", int_part);

// Print the decimal point, but only if there are digits beyond
    if(prec > 0) {
        *out = '.';
        ++out;
    }

// Print the digits after the decimal point
    int8_t digit = 0;
    while(prec-- > 0) {
        remainder *= 10.0;
        digit = (int8_t)remainder;
        if (digit > 9) digit = 9; // insurance
        *out = (char)('0' | digit);
        ++out;
        remainder -= digit;
    }
 
    return s;
}

Just tested your solution and I can confirm that also looks as solving the problem, right values are printed for the above test numbers.

Delta_G one remain the most elegant until now, but, yes, it is a solution so ++karma also to you.

Anybody with a ESP8266 module around, please feel free to run tests and post here the results.
Anybody that want to post a solution for the bug, plese post here and I will run the code to test it.

Thank you for your great feedback.

Here are some funny edge cases to try for your testing:

 0.004999999
 0.0049999995
 0.005
 0.0050000005
 0.005000001

 0.04999999
 0.049999995
 0.05
 0.050000005
 0.05000001

 0.09499999
 0.094999995
 0.095
 0.095000005
 0.09500001

 0.19499999
 0.195
 0.19500001

 0.9499999
 0.94999995
 0.95
 0.95000005
 0.9500001 

 0.9949999
 0.99499995
 0.995
 0.99500005
 0.9950001