Forum Rule: Always post complete source code & details to reproduce any issue!
Page 2 of 2 FirstFirst 1 2
Results 26 to 42 of 42

Thread: Teensy3 alternative for dtostrf() ?

  1. #26
    Senior Member+ MichaelMeissner's Avatar
    Join Date
    Nov 2012
    Location
    Ayer Massachussetts
    Posts
    3,269
    Quote Originally Posted by Frank B View Post
    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.

  2. #27
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    5,679
    Teensyduino declares it float. I wrote that.

  3. #28
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    9,770
    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);

  4. #29
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    9,770
    However this code as LOOP COMPILES without any warning and RUNS just fine. Looking HERE 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

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

    Code:
    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
    Last edited by defragster; 10-29-2015 at 05:41 AM. Reason: Float versus double extened example

  5. #30
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    9,770
    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?

    Code:
    	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.

  6. #31
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    5,679
    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)

  7. #32
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    9,770
    Hi Frank - what was your number? If I had it I could tell you . . .

  8. #33
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    5,679
    It was your test in Post#29

  9. #34
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    5,679
    I just checked it
    It showed -0.00001 (5 chars after ".") before your change in post #30

  10. #35
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    5,679
    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..

  11. #36
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    9,770
    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.

    nonstd.c
    avr_functions.h
    Last edited by defragster; 10-30-2015 at 11:49 PM.

  12. #37
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    9,770
    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>
    Last edited by defragster; 10-31-2015 at 12:20 AM. Reason: bolded Updated FIXED code results

  13. #38
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    9,770
    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
    dtostrf Errors list.txt
    dtostrf_TEST.ino
    nonstd.c
    Last edited by defragster; 10-31-2015 at 06:55 AM.

  14. #39
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    9,770
    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.

  15. #40
    Junior Member
    Join Date
    Apr 2016
    Posts
    18
    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:
    Code:
    // 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
    Last edited by ChrisRowland; 05-22-2016 at 12:53 PM. Reason: Add code versions.

  16. #41
    Junior Member
    Join Date
    Apr 2016
    Posts
    18
    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:
    Code:
    #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

  17. #42
    Senior Member
    Join Date
    Jun 2013
    Location
    So. Calif
    Posts
    2,828
    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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •