Strange long conversion error in ftoa

Hello,

I am using dtostrf() for converting floats to char arrays in order to display them to and OLED display but I was looking for something a bit faster. dtostrf() in a case takes around 32us and the following function takes around 20us so I would prefer to use that instead. I found this code in another thread of the forum..

However the problem is that for example a value of 1.42 converts to 1.41.. I have found the problem in the following line:

wholePart = (long) fraction;

.

It is very strange, the last digit, value of 2, converts to 1! Does anyone have any idea why?

Please find ready-to-run code (Arduino Uno) and give me any suggestion..

Thank you!!

float test=1.42;
char CTEST[7];

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

void loop() {
    ftoa(CTEST,test,2);
    Serial.println(CTEST);
}

char *ftoa(char *buffer, double d, int precision) {

  long wholePart = (long) d;

  // Deposit the whole part of the number.

  itoa(wholePart,buffer,10);

  // Now work on the faction if we need one.

  if (precision > 0) {

    // We do, so locate the end of the string and insert
    // a decimal point.

    char *endOfString = buffer;
    while (*endOfString != '\0') endOfString++;
    *endOfString++ = '.';

    // Now work on the fraction, be sure to turn any negative
    // values positive.

    if (d < 0) {
      d *= -1;
      wholePart *= -1;
    }
    
    double fraction = d - wholePart;
    while (precision > 0) {

      // Multipleby ten and pull out the digit.

      fraction *= 10;
      wholePart = (long) fraction;
      *endOfString++ = '0' + wholePart;

      // Update the fraction and move on to the
      // next digit.

      fraction -= wholePart;
      precision--;
    }

    // Terminate the string.

    *endOfString = '\0';
  }

    return buffer;
}

It is very strange, the last digit, value of 2, converts to 1! Does anyone have any idea why?

If you print 1.41999999, it will print as 1.42. If you isolate the fractional part, and multiply by 100, you will have 41.999999, which, when truncated to an int will be 41. Nothing unexpected there.

If you add 0.05 to the fractional part, after multiplication, it will become 42.049999, which will truncate as 42.

I loaded this into a debugger (VisualMicro) and what's happening is that the float value 2.00 is being converted to 1 in the line

wholePart = (long) fraction;

which means that the float is being stored as 1.99999 (or similar) and not 2 exactly. This is often a problem with floating point arithmetic in processors.
So that's the problem, I'll leave you to fid a solution!

Yes that's exactly the problem, this is what I wrote on my post as well (see the isolated code line)..

So how are we supposed to solve that problem? Never came across with that again...

The solution is to add 0.05 as PaulS said, I tried several values and it seems to be working fine. So the correct function is the following:

char *ftoa(char *buffer, double d, int precision) {

  long wholePart = (long) d;

  // Deposit the whole part of the number.

  itoa(wholePart,buffer,10);

  // Now work on the faction if we need one.

  if (precision > 0) {

    // We do, so locate the end of the string and insert
    // a decimal point.

    char *endOfString = buffer;
    while (*endOfString != '\0') endOfString++;
    *endOfString++ = '.';

    // Now work on the fraction, be sure to turn any negative
    // values positive.

    if (d < 0) {
      d *= -1;
      wholePart *= -1;
    }
    
    double fraction = d - wholePart;
    while (precision > 0) {

      // Multipleby ten and pull out the digit.

      fraction *= 10;
      fraction+=0.05;
      wholePart = (long) fraction;
      *endOfString++ = '0' + wholePart;

      // Update the fraction and move on to the
      // next digit.

      fraction -= wholePart;
      precision--;
    }

    // Terminate the string.

    *endOfString = '\0';
  }

    return buffer;
}

And just to learn, why is it stored as 1.99999999 and not 2?

Thank you!

And just to learn, why is it stored as 1.99999999 and not 2?

Because floating point values can not be stored exactly.

If you really want to know, try here ..... :wink:

So the correct function is the following:

Have you thoroughly tested it?

I tried 10-15 values in the range of 0-2.00 that I will be using and it works for me..

clubman:
I tried 10-15 values in the range of 0-2.00 that I will be using and it works for me..

I'll take that as "no" then.

Do you really expect a value between 1,410-45 and 3,41038 to ever happen? If the answer is no and you don't get the float from some library, don't use a float. Float != decimal point! Float can store a large range of numbers with as a trade off less precision. If you really only want 2 decimals, just store the value in a int x100.

And which oled / library. A lot of libraries for oleds (like the adafruit ones and u8g2) implement the print class which meant you can display floats, int etc out of the box.

*That is
0,0000000000000000000000000000000000000000000014
and
340000000000000000000000000000000000000

Would it not be sufficient to write

      wholePart = (long) fraction + 0.5;

and not add something to fraction at all?

AWOL:
I'll take that as "no" then.

Dear AWOL,

The way you express your thoughts doesn't really help someone who is trying to learn. If you could be more precise I would really appreciate it.

septillion:
Do you really expect a value between 1,410-45 and 3,41038 to ever happen? If the answer is no and you don't get the float from some library, don't use a float. Float != decimal point! Float can store a large range of numbers with as a trade off less precision. If you really only want 2 decimals, just store the value in a int x100.

And which oled / library. A lot of libraries for oleds (like the adafruit ones and u8g2) implement the print class which meant you can display floats, int etc out of the box.

*That is
0,0000000000000000000000000000000000000000000014
and
340000000000000000000000000000000000000

I thought of that but then how would I display the decimal on the display? You mean by dividing it on the fly during displaying?

Whandal, I will try that (note 0.05 instead of 0.5)..

To all, the problem that function gives me now is that dtostrf fills the char array with leading " " and therefore returns a fixed length char array. The function I posted now messes with my display alignment..

Dear AWOL,

The way you express your thoughts doesn't really help someone who is trying to learn. If you could be more precise I would really appreciate it.

You made an assertion - this is the correct function.
I challenged your assertion, that's all.

Correct according to my own needs I would say better then..

clubman:
Correct according to my own needs I would say better then..

...But not correct

Whandall:
Would it not be sufficient to write ... and not add something to fraction at all?

No. There are two points where rounding / truncating are an issue.

The bottom line is that there is a reason dtostrf is slower (if it actually is). Sometimes correct takes time.