Teensy3 alternative for dtostrf() ?

Status
Not open for further replies.

JBeale

Well-known member
I have some Arduino code working now using the Adafruit 128x64 LCD using their ST7565 library, and want to port it to Teensy 3. The library required two minor fixes to compile:
Code:
(1) They assume they can use 'A0' as a dummy variable in their class definition, but it is already defined as a numeric constant in the T3 environment.
(2) They #include <util/delay.h> to use _delay_ms() instead of just using delay()

Now, to show a floating-point variable on the LCD with the ST7565 library, I need to convert the number into a string. I see that 'dtostrf()' is not available for Teensy 3. Should I instead use 'FloatToString()' http://arduino.cc/playground/Main/FloatToString or is there a better way?
 
I wanted to ask the same thing: I have to convert a float into a string to calculate a checksum before sending it over the line, and dtostrf() works on Teensy 2, but not Teensy 3. What is the best way to convert a float to a string on a Teensy 3?
 
Code:
(1) They assume they can use 'A0' as a dummy variable in their class definition, but it is already defined as a numeric constant in the T3 environment.
(2) They #include <util/delay.h> to use _delay_ms() instead of just using delay()

Are you using Teensyduino version 1.13? I recently made some changes, to support this library. Please give 1.13 a try with Arduino 1.0.4. This library is now supposed to compile and work without any editing.
 
more on dtostrf() and sprintf() for Teensy 2, Teensy 3

I also ran into the issue of dtostrf() not working on T3 after porting some code I wrote while using a different Arduino board. Found that it works most of the time, which made the problem go un-noticed for a while; discovered that it seems to error only on small numbers. As you say, the sprintf() works fine for T3, as does dtostrf() for T2. It is just another thing to remember wrt portability. Here's the code I used to test and document the issue for myself.

Code:
// formatted float printing: Teensy 2 vs. 3
// dtostrf() errors for small numbers ~ < 0.1 on T3
char cbuf[20] = "";

void setup() {
  while (!Serial && millis() < 5000) {}    // with timeout
  Serial.begin(9600);      

  float y;
  // -nn.nnnn

  y = -(sin(3.1416 / 4)) * 10;   // big
  dtostrf(y, 10, 5, cbuf);       //   correct on T3
  Serial.println(cbuf);       
  sprintf(cbuf, "%10.5f", y);    //   correct on T3
  Serial.println(cbuf);

  y = +(sin(3.1416 / 4)) / 200;  // small
  dtostrf(y, 10, 5, cbuf);       // incorrect on T3
  Serial.println(cbuf);
  sprintf(cbuf, "%10.5f", y);    //   correct on T3
  Serial.println(cbuf);
}

void loop() {
  ;
}
 
Indeed it isn't right, but . . .
Perhaps showing the output and indicating where you see the problem would help focus on the nature of the problem. Especially showing the 'right' answer from the T2 as well.

Code:
  Serial.println("-small-");
  y = -0.00123456;  // small

My output - with the code quoted in the USB output:
-small-
dtostrf(y, 10, 5, cbuf); == -12346
sprintf(cbuf, "%10.5f", y); == -0.00123

Also the + or - before the sin is a red herring - it is wrong in both cases leaving them both the same would have been clearer.
And the sin() itself is another - it makes the expected value unclear using this constant instead would have helped me: "y = -0.00353554; // small"

I had to build the sketch and add to it before it became clear where the issue is. I was busy staring at the trees and missed the forest, I thought the problem was the last digit rounding, and wholly skipped the missing decimal place as not normal behavior for dtostrf() - because the actual number has a '3535' pattern, so this value is even better: "y = -0.00123456; // small"

My not knowing dtostrf() I didn't know what to expect - you asked for 5 digits and got five? This is probably behind the error in dtostrf(), once it passes the decimal - it is out of sight and out of mind - and starts focusing on the first digit it finds since the 'leading' zeroes are boring to look at once the decimal passes because counting zeroes takes effort.
 
Yup, there's probably a bug in my attempt to emulate dtostrf. Here's the code.

https://github.com/PaulStoffregen/cores/blob/master/teensy3/nonstd.c#L69

Ultimately it uses fcvt(), which is a standard C function, but quite difficult to use correctly. I must have missed some of the cases. I've put this on my list of bugs to investigate, but realistically it's unlikely I'll be able to spend time on this until mid-November.

If anyone has any ideas about where I went wrong in trying to emulate avr-libc's dtostrf(), please speak up!
 
Reading the manual:
The string itself does not contain a decimal point; however, the position of the decimal point relative to the start of the string is stored in *decpt.

I'll go glance at GitHub now
 
I have NO IDEA of any side effects - or other cases this may affect - but on my sample this works - adding the ELSE at line 97 {"I:\Teensy165\hardware\teensy\avr\cores\teensy3\nonstd.c"}:

Code:
	if (decpt == 0 && precision > 0) {
		*p++ = '0';
		*p++ = '.';
	}
	[B]else if (decpt < 0 && precision > 0) {
		*p++ = '0';
		*p++ = '.';
		e++;
		while ( decpt < 0 ) {
			decpt++;
			*p++ = '0';
		}
	}[/B]

-small-
dtostrf(y, 10, 5, cbuf); == -0.00123
sprintf(cbuf, "%10.5f", y); == -0.00123
 
Last edited:
My sample test looks right - not showing anything odd (using edited nonstd.c code from post #10):

<EDIT>: Sample INO starts with a SMALL negative Y and then loops to multiply it by 10 each loop to go from SMALL float to LARGER float.
It then repeats with a same positive value for SMALL y
In both cases it shows reference values from sprintf with the same %10.5f and printf for the full number value

Code:
char cbuf[20] = "";

void setup() {
  while (!Serial && millis() < 5000) {}    // with timeout
  Serial.begin(9600);
  float y;
  Serial.println("------");
  y = -0.000012345678L;  // small
  for ( int jj = 0; jj < 2; jj++ ) {
    if ( 1 == jj ){
      y = 0.000012345678L;  // small
      Serial.println("\n=================------\n");
    }
    for ( int ii = 0; ii < 10; ii++ ) {
      Serial.print( "dtostrf(y, 10, 5, cbuf); == ");
      dtostrf(y, 10, 5, cbuf);
      Serial.println(cbuf);
      Serial.print( "sprintf(cbuf, \"%10.5f\", y); == ");
      sprintf(cbuf, "%10.5f", y);
      Serial.println(cbuf);
      Serial.print("printf(\"%f\", y);");
      Serial.printf("%f", y);
      y *= 10;
      Serial.println("\n------");
    }
  }
}

void loop() {
  ;
}

------
dtostrf(y, 10, 5, cbuf); == -0.00001
sprintf(cbuf, "%10.5f", y); == -0.00001
printf("%f", y);-0.000012
------
dtostrf(y, 10, 5, cbuf); == -0.00012
sprintf(cbuf, "%10.5f", y); == -0.00012
printf("%f", y);-0.000123
------
dtostrf(y, 10, 5, cbuf); == -0.00123
sprintf(cbuf, "%10.5f", y); == -0.00123
printf("%f", y);-0.001235
------
dtostrf(y, 10, 5, cbuf); == -0.01234
sprintf(cbuf, "%10.5f", y); == -0.01235
printf("%f", y);-0.012346
------
dtostrf(y, 10, 5, cbuf); == -0.12346
sprintf(cbuf, "%10.5f", y); == -0.12346
printf("%f", y);-0.123457
------
dtostrf(y, 10, 5, cbuf); == -1.23457
sprintf(cbuf, "%10.5f", y); == -1.23457
printf("%f", y);-1.234568
------
dtostrf(y, 10, 5, cbuf); == -12.34568
sprintf(cbuf, "%10.5f", y); == -12.34568
printf("%f", y);-12.345678
------
dtostrf(y, 10, 5, cbuf); == -123.45679
sprintf(cbuf, "%10.5f", y); == -123.45679
printf("%f", y);-123.456787
------
dtostrf(y, 10, 5, cbuf); == -1234.56787
sprintf(cbuf, "%10.5f", y); == -1234.56787
printf("%f", y);-1234.567871
------
dtostrf(y, 10, 5, cbuf); == -12345.67871
sprintf(cbuf, "%10.5f", y); == -12345.67871
printf("%f", y);-12345.678711
------

=================------

dtostrf(y, 10, 5, cbuf); == 0.00001
sprintf(cbuf, "%10.5f", y); == 0.00001
printf("%f", y);0.000012
------
dtostrf(y, 10, 5, cbuf); == 0.00012
sprintf(cbuf, "%10.5f", y); == 0.00012
printf("%f", y);0.000123
------
dtostrf(y, 10, 5, cbuf); == 0.00123
sprintf(cbuf, "%10.5f", y); == 0.00123
printf("%f", y);0.001235
------
dtostrf(y, 10, 5, cbuf); == 0.01234
sprintf(cbuf, "%10.5f", y); == 0.01235
printf("%f", y);0.012346
------
dtostrf(y, 10, 5, cbuf); == 0.12346
sprintf(cbuf, "%10.5f", y); == 0.12346
printf("%f", y);0.123457
------
dtostrf(y, 10, 5, cbuf); == 1.23457
sprintf(cbuf, "%10.5f", y); == 1.23457
printf("%f", y);1.234568
------
dtostrf(y, 10, 5, cbuf); == 12.34568
sprintf(cbuf, "%10.5f", y); == 12.34568
printf("%f", y);12.345678
------
dtostrf(y, 10, 5, cbuf); == 123.45679
sprintf(cbuf, "%10.5f", y); == 123.45679
printf("%f", y);123.456787
------
dtostrf(y, 10, 5, cbuf); == 1234.56787
sprintf(cbuf, "%10.5f", y); == 1234.56787
printf("%f", y);1234.567871
------
dtostrf(y, 10, 5, cbuf); == 12345.67871
sprintf(cbuf, "%10.5f", y); == 12345.67871
printf("%f", y);12345.678711
------
 
Last edited:
I have NO IDEA of any side effects - or other cases this may affect - but on my sample this works - adding the ELSE at line 97 {"I:\Teensy165\hardware\teensy\avr\cores\teensy3\nonstd.c"}:

Code:
    if (decpt == 0 && precision > 0) {
        *p++ = '0';
        *p++ = '.';
    }
    [B]else if (decpt < 0 && precision > 0) {
        *p++ = '0';
        *p++ = '.';
        e++;
        while ( decpt < 0 ) {
            decpt++;
            *p++ = '0';
        }
    }[/B]

Seems to work!:

You can omit the previous "if":
just delete it (incl. else) and write if (decpt <= 0 && precision > 0) { instead

so the complete function is now:

Code:
char * dtostrf([B]double val[/B], int width, unsigned int precision, char *buf)
{
    int decpt, sign, reqd, pad;
    const char *s, *e;
    char *p;

    s = fcvt(val, precision, &decpt, &sign);
    if (precision == 0 && decpt == 0) {
        s = (*s < '5') ? "0" : "1";
        reqd = 1;
    } else {
        reqd = strlen(s);
        if (reqd > decpt) reqd++;
        if (decpt == 0) reqd++;
    }
    if (sign) reqd++;
    p = buf;
    e = p + reqd;
    pad = width - reqd;
    if (pad > 0) {
        e += pad;
        while (pad-- > 0) *p++ = ' ';
    }
    if (sign) *p++ = '-';
[B]    if (decpt <= 0 && precision > 0) {
        *p++ = '0';
        *p++ = '.';
        e++;
        while ( decpt < 0 ) {
            decpt++;
            *p++ = '0';
        }
    }    [/B]
    while (p < e) {
        *p++ = *s++;
        if (p == e) break;
        if (--decpt == 0) *p++ = '.';
    }
    if (width < 0) {
        pad = (reqd + width) * -1;
        while (pad-- > 0) *p++ = ' ';
    }
    *p = 0;

    //char format[20];
    //sprintf(format, "%%%d.%df", width, precision);
    //sprintf(buf, format, val);
    return buf;
}

of course, avr_functions.h needs to be edited:
char * dtostrf(double val, int width, unsigned int precision, char *buf);
(line101)
 
Last edited:
@Frank: I thought about that clause being suspect <edit: subject to removal or inclusion> with my change - but I modified the value of 'e' and didn't want to actually look any further in the code for side effects. Without that change it came up one byte short on output - with it in any other case it might start adding digits.
 
Last edited:
Yes, the function takes a double argument. However, since C++ requires prototypes in scope, the compiler will automatically convert a float passed to dtostrf to double.

Even in the case of ancient C without prototypes, it would have worked, since if you don't have a prototype in scope, the compiler will create a prototype for you, and char/short arguments will be converted to int, and float arguments are converted to double.
 
Yes, the function takes a double argument. However, since C++ requires prototypes in scope, the compiler will automatically convert a float passed to dtostrf to double.

Even in the case of ancient C without prototypes, it would have worked, since if you don't have a prototype in scope, the compiler will create a prototype for you, and char/short arguments will be converted to int, and float arguments are converted to double.

@Michael:
I don't understand.. sometimes my english is not good enough..

In avr_functions.h (teensy3-code): char * dtostrf(float val, int width, unsigned int precision, char *buf);
The function in nonstd.c (teensy3-code) is the same:
char * dtostrf(float val, int width, unsigned int precision, char *buf) {...}

So, that float is correct, and instead float the compiler uses double? :confused:

I thought, if you call this function with a double, tha value is converted to float, and then later in the function the float value is again converted to double for fcvt()
So i am wrong ? Changing it to char * dtostrf(double val, is not needed ??

Why is in the AVR-LIBC (which is NOT usd here, or is it??) a different prototype?
 
Last edited:
The documentation that I've seen has a double as the first argument.

Note, in the AVR system, double is not 64-bits, but it is 32-bits, just like float (presumably to save space). AVR programmers tend to be somewhat lax about the difference between float and double. So, I could imagine that in the AVR specific section, they use float instead of double.
 
I'm still confused!
Was i wrong with my previous posting or not ? This is no rhetorical question, really, i have problems to understand(=translating to german) your post #17.

The name is DOUBLEtostrf() . Does it convert DOUBLE or FLOAT (i'm speaking of pauls version) ??
Yes i know that double is the same as float on avr. But wer're talking about teensy3.
 
AVR programmers tend to be somewhat lax about the difference between float and double.

Yes, exactly!

I think it's important to keep in mind dtostrf() is AVR specific. While it could be useful on other platforms, the fact is it only exists on AVR. The avr-libc developers made it up. We're trying to emulate it on ARM only because some authors of Arduino sketches and libraries have used this AVR-only function.

Since AVR only has single precision, I defined this function with float instead of double. The idea was to get as close as reasonably possible to what AVR actually provides. Ideally, I'd like to replace fcvt() with a single precision function. But not enough to write one myself!
 
just edit fcvt() to fcvtf()

ok, i understand the compatibility-reason

so, i'm right that the existing dtostrf really uses float and not double as Michael said ?
Or did i understand him wrong ?
 
Last edited:
just to make it more clear: the fcvtf() is already existing.
(This evening i'm a bit afraid that everybody is understanding me wrong.... perhaps i should go back to German forums..)
 
Status
Not open for further replies.
Back
Top