PDA

View Full Version : Teensy3 alternative for dtostrf() ?



JBeale
11-27-2012, 06:13 PM
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:

(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?

ausserirdischegesund
03-16-2013, 07:25 AM
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?

ausserirdischegesund
03-16-2013, 08:11 AM
Sorry for the noise: Solved it with using sprintf(), and %f format strings, and the code is easier to read too.

PaulStoffregen
03-16-2013, 11:43 AM
(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.

David Smith
10-28-2015, 08:15 AM
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.


// 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() {
;
}

defragster
10-28-2015, 09:45 AM
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.



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.

PaulStoffregen
10-28-2015, 03:12 PM
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 (http://linux.die.net/man/3/fcvt), 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!

Frank B
10-28-2015, 03:19 PM
Problay not part of the problem, but shouldnt "val" be double?

defragster
10-28-2015, 05:16 PM
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

defragster
10-28-2015, 05:28 PM
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\non std.c"}:



if (decpt == 0 && precision > 0) {
*p++ = '0';
*p++ = '.';
}
else if (decpt < 0 && precision > 0) {
*p++ = '0';
*p++ = '.';
e++;
while ( decpt < 0 ) {
decpt++;
*p++ = '0';
}
}



-small-
dtostrf(y, 10, 5, cbuf); == -0.00123
sprintf(cbuf, "%10.5f", y); == -0.00123

defragster
10-28-2015, 05:52 PM
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



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
------

defragster
10-28-2015, 06:17 PM
Issued PULL request. GitHub was harder to navigate than editing the code was - but maybe I'm learning.

https://github.com/PaulStoffregen/cores/pull/89

Frank B
10-28-2015, 06:20 PM
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\non std.c"}:



if (decpt == 0 && precision > 0) {
*p++ = '0';
*p++ = '.';
}
else if (decpt < 0 && precision > 0) {
*p++ = '0';
*p++ = '.';
e++;
while ( decpt < 0 ) {
decpt++;
*p++ = '0';
}
}


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:



char * dtostrf(double val, 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++ = '-';
if (decpt <= 0 && precision > 0) {
*p++ = '0';
*p++ = '.';
e++;
while ( decpt < 0 ) {
decpt++;
*p++ = '0';
}
}
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)

defragster
10-28-2015, 06:26 PM
@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.

defragster
10-28-2015, 06:45 PM
Cool! The PULL is done!

Frank B
10-28-2015, 07:12 PM
cool :O)

but the "float val" parameter still seems not to be correct for me .

i just found the declaration of avr-libc:
http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html#ga060c998e77fb5fc0d3168b3c e8771d42
search for dtostrf





char* dtostrf
(
double
__val,




signed char
__width,




unsigned char
__prec,




char *
__s



)






The dtostrf() (http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html#ga060c998e77fb5fc0d3168b3c e8771d42) function converts the double value passed in val

MichaelMeissner
10-28-2015, 07:53 PM
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.

Frank B
10-28-2015, 08:50 PM
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?

MichaelMeissner
10-28-2015, 09:52 PM
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.

Frank B
10-28-2015, 10:01 PM
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.

Frank B
10-28-2015, 10:09 PM
This is the Arduino-code for AVR_SAM, which is 32BIT too:
https://github.com/arduino/ArduinoCore-samd/blob/master/cores/arduino/avr/dtostrf.c



char *dtostrf (double val, signed char width, unsigned char prec, char *sout) {
char fmt[20];
sprintf(fmt, "%%%d.%df", width, prec);
sprintf(sout, fmt, val);
return sout;
}

PaulStoffregen
10-28-2015, 10:47 PM
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!

Frank B
10-28-2015, 10:55 PM
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 ?

Frank B
10-28-2015, 11:14 PM
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..)

Frank B
10-28-2015, 11:18 PM
I'm now pause and hear some RAMSTEIN CDs

MichaelMeissner
10-28-2015, 11:25 PM
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 ?
No, it was somebody else that said it used float. I said it used double, but I was responding that in the AVR environment, even if it was declared using float, it would still 'work'.

On my Arduino 1.6.5 installation, dtostrf is declared in hardware/tools/avr/avr/include/stdlib.h, and the first argument is a double. Even in my Arduino 1.0.6 installation, it is declared double. Perhaps some ancient Arduino library declared it float.

Frank B
10-28-2015, 11:34 PM
Teensyduino declares it float. I wrote that.

defragster
10-29-2015, 12:21 AM
Just above my changed code in nonstd.c is this handy function declaration - all I see are FLOATS in use?

char * dtostrf(float val, int width, unsigned int precision, char *buf) {...}

and in the avr_functions.h file it is this: char * dtostrf(float val, int width, unsigned int precision, char *buf);

In the sketch provided and I modified it this is the usage:
float y;
dtostrf(y, 10, 5, cbuf);

defragster
10-29-2015, 12:49 AM
However this code as LOOP COMPILES without any warning and RUNS just fine. Looking HERE (http://www.cplusplus.com/reference/cstdio/printf/) as printf is defined there is no DOUBLE type - just FLOAT, that can be printed in exponential form. So practically they are the same . . . <edit> interchangeable thanks to the compiler for this purpose. LINK: float-vs-double-precision (http://stackoverflow.com/questions/5098558/float-vs-double-precision)

<EDIT>: Added new cases to actually show that the dtostrf() truly gets a FLOAT worth of data - though it will "process" a DOUBLE worth just like printf()



int stopp = 0;
void loop() {
if ( !stopp ) {
stopp++;
double dd = -0.0000123456789012345678901234L;
float ff = -0.0000123456789012345678901234L;
dtostrf(dd, 10, 5, cbuf);
Serial.println(cbuf); // -0.00001
dtostrf(dd, 10, 29, cbuf);
Serial.println(cbuf); // -0.00001234567935171071439981460
dtostrf(ff, 10, 29, cbuf);
Serial.println(cbuf); // -0.00001234567935171071439981460
Serial.printf("%f\n", dd); // -0.000012
Serial.printf("%4.29f\n", dd); // -0.00001234567890123456780746176
Serial.printf("%f\n", ff); // -0.000012
Serial.printf("%4.29f\n", ff); // -0.00001234567935171071439981461
}
}


Output of the prints:


-0.00001
-0.00001234567935171071439981460
-0.00001234567935171071439981460
-0.000012
-0.00001234567890123456780746176
-0.000012
-0.00001234567935171071439981461

Note - of course - being abusive with %d INTEGER in printf does not work - but it gives no warning - it just does the wrong thing when the exchanges values are not DOUBLE .vs. FLOAT:



code:
Serial.printf("%d\n", d);
Serial.printf("%ld\n", d);

output:
536870912
536870912

defragster
10-29-2015, 11:28 AM
The 'else' fix when zeros follow the decimal on a number <0 was good to fix the failed functionality.

However in trying to automate a test to see if dtostrf could work on double as well as float [generally yes as far as I got] I discovered in the existing float code there is a last digit rounding error typically when there is a single zero after the decimal (compared to sprintf), but not for all numbers - with non-integers I suppose this is acceptable? It seems to be a factor of 'precision' as returned by fcvt() as used. Shown here from post #11.


dtostrf(y, 10, 5, cbuf); == -0.01234
sprintf(cbuf, "%10.5f", y); == -0.01235


If WIDTH and PRECISION are mismatched for small numbers resulting in ZERO shown- then WIDTH is violated and extra '0' are added. This is from the ZERO fill I did with a while that needs to read like this: "while ( decpt < 0 && !(p >= e) ) {" as it was running p past e. sprintf() does not place these extra zeroes.

I also noticed in doing the strcmp that in many of the fixed cases there is an extra padding blank making the return string one char longer than 'width', again that is a space padding for the numbers - but only when under 0.1 it seems. So instead of e++ in the else it should be "if ( decpt < 0 ) reqd++;" before the pad is calculated. In which case Frank was right we can make the ELSE clause the primary IF clause and the added while will drop out. Width is MIN not MAX string length to return - but extra pad doesn't match sprintf(), pull request to fix this?



if (sign) reqd++;
p = buf;
if ( decpt < 0 ) reqd++;
e = p + reqd;
pad = width - reqd;
if (pad > 0) {
e += pad;
while (pad-- > 0) *p++ = ' ';
}
if (sign) *p++ = '-';
if (decpt <= 0 && precision > 0) {
*p++ = '0';
*p++ = '.';
while ( decpt < 0 && !(p >= e) ) {
decpt++;
*p++ = '0';
}
}


Other than the last digit comparing when small numbers - it looks like these are improvements.

Frank B
10-30-2015, 11:07 PM
dtostrf(dd, 10,5, cbuf);
hmm.. displays 6 chars afters the decimal-point. I'm not sure if that was the case before the update ? Or is it correct? (i don't know, really)

defragster
10-30-2015, 11:11 PM
Hi Frank - what was your number? If I had it I could tell you . . .

Frank B
10-30-2015, 11:14 PM
It was your test in Post#29 (https://forum.pjrc.com/threads/1227-Teensy3-alternative-for-dtostrf()?p=86723&viewfull=1#post86723)

Frank B
10-30-2015, 11:29 PM
I just checked it :)
It showed -0.00001 (5 chars after ".") before your change in post #30

Frank B
10-30-2015, 11:30 PM
I just checked it :)
It showed -0.00001 (5 chars after ".") before your change in post #30
After that : -0.000012

edit: sry, double-post..

defragster
10-31-2015, 12:03 AM
I don't see that now - maybe I fixed it? My Auto tester does string compares against: sprintf(spbuf, "%10.*f", kk, ff); to catch extra padding and missed or altered last digits, or errors.

<edit> That must have shifted with my recent fix (per above) as that is one of the cases where I get an extra byte of padding [ON other than NEW code].

Attached is nonstd.c and .h file.

You can call the old code side by side as: B1dtostrf(ff, 10, kk, tobuf); for old and for my current of course: dtostrf(ff, 10, kk, tobuf);

I also put in : B3dtostrf(dd, 20, kk2, tobuf); that takes a DOUBLE instead of FLOAT.

5400
5401

defragster
10-31-2015, 12:53 AM
Here is a snippet of my test output - showing GOOD for the current code, and some error on the other old or DOUBLE code.



ll=0 ------dtostrf( << my current updated 'fixed' code
ll=1 ------B1dtostrf( << Beta1 code
ll=2 ------B3dtostrf(
ll=3 ------B3dtostrf( DOUBLE >> WIDTH up to 20 from 10
------
ll ------which function above
ii ------interation multiplying number * 10
jj ------Which number +/-
kk ------precision
------
====== NEGATIVE ===========------
[ll=0 ii=0 jj=2 kk=5] :: GOOD___-0.0000123457, buffers==< -0.00001>
[ll=1 ii=0 jj=2 kk=5] :: BAD_PAD___-0.0000123457:: | @2 | spbuf=< -0.00001>, tobuf=< -12346> << (bad?) PAD???
[ll=2 ii=0 jj=2 kk=5] :: BAD_PAD___-0.0000123457:: | @2 | spbuf=< -0.00001>, tobuf=< -0.00001> << (bad?) PAD???
[ll=3 ii=0 jj=2 kk=5] :: BAD_PAD___-0.0000123457:: | @7 | spbuf=< -0.0000123457>, tobuf=< -0.000012345> << (bad?) PAD???
[ll=0 ii=1 jj=2 kk=5] :: GOOD___-0.0001234568, buffers==< -0.00012>
[ll=1 ii=1 jj=2 kk=5] :: BAD_PAD___-0.0001234568:: | @2 | spbuf=< -0.00012>, tobuf=< -12346> << (bad?) PAD???
[ll=2 ii=1 jj=2 kk=5] :: BAD_PAD___-0.0001234568:: | @2 | spbuf=< -0.00012>, tobuf=< -0.00012> << (bad?) PAD???
[ll=3 ii=1 jj=2 kk=5] :: BAD_PAD___-0.0001234568:: | @7 | spbuf=< -0.0001234568>, tobuf=< -0.000123456> << (bad?) PAD???
[ll=0 ii=2 jj=2 kk=5] :: GOOD___-0.0012345679, buffers==< -0.00123>
[ll=1 ii=2 jj=2 kk=5] :: BAD_PAD___-0.0012345679:: | @2 | spbuf=< -0.00123>, tobuf=< -12346> << (bad?) PAD???
[ll=2 ii=2 jj=2 kk=5] :: BAD_PAD___-0.0012345679:: | @2 | spbuf=< -0.00123>, tobuf=< -0.00123> << (bad?) PAD???
[ll=3 ii=2 jj=2 kk=5] :: BAD_PAD___-0.0012345679:: | @7 | spbuf=< -0.0012345679>, tobuf=< -0.001234567> << (bad?) PAD???
[ll=0 ii=3 jj=2 kk=5] :: LastDigit___-0.0123456789:: [#9 | 5>< 4 ] [ -0.01234]
[ll=1 ii=3 jj=2 kk=5] :: BAD_PAD___-0.0123456789:: | @2 | spbuf=< -0.01235>, tobuf=< -12346> << (bad?) PAD???
[ll=2 ii=3 jj=2 kk=5] :: BAD_PAD___-0.0123456789:: | @2 | spbuf=< -0.01235>, tobuf=< -0.01234> << (bad?) PAD???
[ll=3 ii=3 jj=2 kk=5] :: BAD_PAD___-0.0123456789:: | @7 | spbuf=< -0.0123456789>, tobuf=< -0.012345678> << (bad?) PAD???
[ll=0 ii=4 jj=2 kk=5] :: GOOD___-0.1234567890, buffers==< -0.12346>
[ll=0 ii=5 jj=2 kk=5] :: GOOD___-1.2345678901, buffers==< -1.23457>
[ll=0 ii=6 jj=2 kk=5] :: GOOD___-12.3456789012, buffers==< -12.34568>
[ll=0 ii=7 jj=2 kk=5] :: GOOD___-123.4567890123, buffers==<-123.45679>
[ll=0 ii=8 jj=2 kk=5] :: GOOD___-1234.5678901235, buffers==<-1234.56787>
[ll=0 ii=9 jj=2 kk=5] :: GOOD___-12345.6789012346, buffers==<-12345.67871>
[ll=0 ii=10 jj=2 kk=5] :: GOOD___-123456.7890123457, buffers==<-123456.78906>
[ll=0 ii=11 jj=2 kk=5] :: GOOD___-1234567.8901234567, buffers==<-1234567.87500>
[ll=0 ii=12 jj=2 kk=5] :: GOOD___-12345678.9012345672, buffers==<-12345679.00000>
[ll=0 ii=13 jj=2 kk=5] :: GOOD___-123456789.0123456717, buffers==<-123456792.00000>

defragster
10-31-2015, 07:46 AM
Done with that - not perfect but further improved. Pull request submitted. There are some few low precision cases that were not corrected that show an off by one on the last displayable digit. These are only in places where worse errors existed per my test case code. Many of these showing the wrong digit are GIGO from the fcvt() function as it compares to sprint() output. It may be rounding specified to wrong digit as this is the number in the output string that is copied.

updated nonstd.c file, sample test output showing detected errors, where ll=0 shows remaining, ll=1 shows prior fix with more numerous padding blank errors etc, also my test sketch
5403
5404
5402

defragster
11-01-2015, 02:19 AM
Thanks to Frank (/sarcasm :-) ) :: Heads up - the FUNCTION spec says that this dtostrf() should work on a neg width. I don't see my change having any impact - neither it nor the TD_1.25 code seen to give uniform results in my test app - if I used it right - lots of blank strings with rare showing of numbers.

ChrisRowland
05-22-2016, 01:50 PM
I don't know if this is relevant to this thread but I've found that for small numbers dtostrf will return a string that is too long:


// the setup function runs once when you press reset or power the board
void setup()
{
char str[100];
Serial.begin(9600);
Serial.println("hello");
for (float f = -0.12; f < 0.12; f+= 0.005)
{
dtostrf(f, 4, 1, str);
Serial.print(f); Serial.print("|"); Serial.print(str); Serial.print("|"); Serial.println();
}
Serial.println("done");
}

// the loop function runs over and over again until power down or reset
void loop(){}

This gives a output of:

hello
-0.12|-0.1|
-0.11|-0.1|
-0.11|-0.1|
-0.10|-0.1|
-0.10|-0.1|
-0.09| -0.0|
-0.09| -0.0|
-0.08| -0.0|
-0.08| -0.0|
-0.07| -0.0|
-0.07| -0.0|
-0.06| -0.0|
-0.06| -0.0|
-0.05| -0.0|
-0.05| -0.0|
-0.04| -0.0|
-0.04| -0.0|
-0.03| -0.0|
-0.03| -0.0|
-0.02| -0.0|
-0.02| -0.0|
-0.01| -0.0|
-0.01| -0.0|
-0.00| -0.00|
0.00| 0.0000000|
0.01| 0.00|
0.01| 0.0|
0.02| 0.0|
0.02| 0.0|
0.03| 0.0|
0.03| 0.0|
0.04| 0.0|
0.04| 0.0|
0.05| 0.0|
0.05| 0.0|
0.06| 0.0|
0.06| 0.0|
0.07| 0.0|
0.07| 0.0|
0.08| 0.0|
0.08| 0.0|
0.09| 0.0|
0.09| 0.0|
0.10| 0.1|
0.10| 0.1|
0.11| 0.1|
0.11| 0.1|
0.12| 0.1|
done

Teensyduino 1.28, Arduino 1.6
Chris

ChrisRowland
05-27-2016, 10:48 AM
I ended up doing my own conversion function, it works the way I want and is also much smaller. It's not so universal but copes with the numbers I throw at it.

I found that dtostrf uses about 13924 bytes of program memory and 116 bytes of dynamic while my f2str function adds 144 bytes of program memory and no dynamic. It makes the difference between my program fitting in the LC or not.

My test program is:


#define F2STR

void setup()
{
char str[100];
Serial.begin(9600);
// put your setup code here, to run once:
//dtostrf(3.14192, 5, 1, str);
#ifdef F2STR
f2str(3.14192, 5, 1, str);
#endif
Serial.println(str);
}

void loop()
{
}

#ifdef F2STR

// send a float to a string that's a specified length and with a specified number of decimal places.
// the string is left justified and padded with spaces.
// if dp is -ve a leading space is added for values >= 0
// values that parse to longer than the length are truncated
// does not cope with large numbers
char* f2str(float v, uint8_t len, int8_t dp, char * str)
{
char * c = str;
int8_t l = len;

// handle the sign or possible leading space
if (v < 0)
{
*c++ = '-';
l--;
}
else if (dp < 0)
{
*c++ = ' ';
l--;
}
v = abs(v);
dp = abs(dp);

// move the number into the integer range and
// set up integer equivalents
uint32_t dpn = 1;
for (int8_t i = 0; i < dp; i++)
{
v *= 10.0;
dpn *= 10;
}

uint32_t n = dpn;
// convert to an integer, with rounding. The number of digits must fit into a uint32
uint32_t iv = (int)(v + 0.5);
// this ensures that n is not greater than iv so less chance of an overflow
while (iv / 10 >= n)
n *= 10;

bool fc = false; // set when the first character has been added so that trailing zeroes are added but not leading zeroes
while (n >= 1 && l > 0) // this stops the output overflowing but the value returned may be truncated
{
uint32_t i = iv / n;
// print digits
if (i > 0 || fc)
{
*c++ = '0' + i;
l--;
iv -= i * n; // remove this digit from the number
fc = true;
}
// add the decimal point when we get to it
if (n == dpn)
{
if (!fc)
{
// add a leading 0 if required
*c++ = '0';
l--;
fc = true;
}
if (dp)
{
*c++ = '.';
l--;
}
}
n /= 10;
}
// fill with spaces
while (l > 0)
{
*c++ = ' ';
l--;
}
// add terminator
*c++ = 0;
return str;
};

#endif

I commented out bits of code and did a verify build to get the space used. I have to print the result, if I don't f2str is optimised out.

I guess that dtostrf is pulling in a lot of additional libraries,

Chris

stevech
05-27-2016, 05:54 PM
if pressed, you could omit all floating point in calculations and I/O, and use a fixed point library instead, for real numbers. Much faster, too.