dtostrf hangs with nan

tni

Well-known member
dtostrf hangs with nan and inf

Code:
// standard Blink example
...
void loop() {
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);               // wait for a second
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);               // wait for a second

  volatile float zero = 0.0;
  char buffer[200];
  float fval = 1 / zero;

  dtostrf(fval,2,0,buffer);
}

This hangs; a nan obtained via sqrt(-1.0) also hangs. The culprit is "fcvtf(val, precision, &decpt, &sign);" used by dtostrf. sprintf handles this properly and prints 'inf'.
 
Last edited:
Last edited:
Would be great if you give it a try and test it..
I'll create a pull-request for Paul if it is ok.
It seems to work. 'isnanf' and 'isinff' are available in newlib math.h.
One minor problem:
I don't know why printing the sign of negative NaN does not work, I did not investigate this. Perhaps a limitation of the float-implementation ?
The sign is there in the floating point nan value, 0x7FC00000 vs. 0xFFC00000.

sprintf on Teensy doesn't print the sign either, neither does GCC with GLIBC nor Visual C++. If AVR libc does it, it's an oddity.
 
It seems to work. 'isnanf' and 'isinff' are available in newlib math.h.

The sign is there in the floating point nan value, 0x7FC00000 vs. 0xFFC00000.

sprintf on Teensy doesn't print the sign either, neither does GCC with GLIBC nor Visual C++. If AVR libc does it, it's an oddity.
The sign is not really part of NaN. If you really need to see if a NaN has the sign bit set, you need to use the signbit or copysign functions. It is like -0.0 in that it mostly acts the same, but there are a few corner cases where it doesn't.

Note, isinf, isnan, fpclassify, isnormal, isfinite, signbit and copysign are generic functions and will operate on any floating point type. Internally, the compiler does have a float version of each, a double version, and a long double.
 
Last edited:
The sign is not really part of NaN. If you really need to see if a NaN has the sign bit set, you need to use the signbit or copysign functions. It is like -0.0 in that it mostly acts the same, but there are a few corner cases where it doesn't.

Note, isinf, isnan, fpclassify, isnormal, isfinite, signbit and copysign are generic functions and will operate on any floating point type. Internally, the compiler does have a float version of each, a double version, and a long double.

Thanks Michael,

I've tried (see link of patch in post #4):
Code:
// if (copysign(0.0, val)<0) *buf++ = '-'; //does not work (?)

but it does not work ?

Edit: I'm speaking of val = -NAN only :)
Edit: oops..would it work with copysignf(1.0f, val) instead ? :) is there a -0.0 ? :)
Edit: Ok... this works... :)
 
Last edited:
String in WString.cpp uses dtostrf.

But I'm not sure if the formatting would stay exactly the same, if Serial / Print switch to dtostrf.
 
No, not with dtostrf. We can add the additional code for NAN / INF.
Maybe, i have some time next weekend to take a closer look..
 
Arduino-AVR uses a rather simple way...
Code:
size_t Print::printFloat(double number, uint8_t digits) 
{ 
  size_t n = 0;
  if (isnan(number)) return print("nan");
  if (isinf(number)) return print("inf");
  if (number > 4294967040.0) return print ("ovf");  // constant determined empirically
  if (number <-4294967040.0) return print ("ovf");  // constant determined empirically
.
.
.

We have to use the same code to be arduino compatible...

DBL_MAX from <float.h> prints as 4294967295.0 (same as FLT_MAX)
What does this "// constant determined empirically" mean ? And why this value: 4294967040.0 ?

Other Question: On AVR, we have Float = Double. (right?)
If we use printFloat(double number, uint...) it is a kind of incompatible anyway.. do we need that "ovf" print ??
 
Last edited:
On ARM and AVR, FLT_MAX is 340282346638528859811704183484516925440.

I think 'ovf' simply means printFloat is too limited to handle something bigger. They cast the value to a uint32_t (4294967295 max). "// constant determined empirically" probably means that they avoid overflows in the rest of the code, determined by trial and error (remainder printing).

Casting a float that's too large to fit in the target integer type is undefined behavior in both C and C++, so that's definitely not something that should remain in the Teensy implementation.

I think it would be worthwhile to at least use uint64_t instead of uint32_t. (There are probably hardcoded buffer sizes somewhere out there that will overflow.)
 
I think the same - we should not care about AVR limitations.

Here's the Teensy 3.x code (see Print.cpp) :
Code:
size_t Print::printFloat(double number, uint8_t digits) 
{
    uint8_t sign=0;
    size_t count=0;
    
    // Handle negative numbers
    if (number < 0.0) {
        sign = 1;
        number = -number;
    }

    // Round correctly so that print(1.999, 2) prints as "2.00"
    double rounding = 0.5;
    for (uint8_t i=0; i<digits; ++i) {
        rounding *= 0.1;
    }
    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;
    count += printNumber(int_part, 10, sign);

    // Print the decimal point, but only if there are digits beyond
    if (digits > 0) {
        uint8_t n, buf[16], count=1;
        buf[0] = '.';

        // Extract digits from the remainder one at a time
        if (digits > sizeof(buf) - 1) digits = sizeof(buf) - 1;

        while (digits-- > 0) {
            remainder *= 10.0;
            n = (uint8_t)(remainder);
            buf[count++] = '0' + n;
            remainder -= n; 
        }
        count += write(buf, count);
    }
    return count;
}


printNumber() uses a buffer (different sizes for DEC, HEX and BIN - we use only DEC here - bufsize 11), and in the code above a buffer of 16 Characters is used.


The easiest way would be to just add the lines for isnan() and isinf().
And in this case, the "empirically determined constant" makes sense.

The better way ..hm.. is more incompatible but more correct..
Since we are incompatible anyway, i'd vote for a simplified dtostrf and use fctv(), with a bufsize of ~40, with checks for NaN and Inf.

What do you think ?
Paul ?
Michael ?
Tni ?

We could limit it with #ifdefs for teensy 3.1.. 3.6 and use the old version for Teensy LC and 3.0..
 
Btw, the output of

Serial.printf("%f\n%f\n%f\n%f\n",NAN, -NAN, FLT_MAX, DBL_MAX);

is:
Code:
nan 
nan
 340282346638528859811704183484516925440.000000
 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000

Edit: DBL_MAX is 316 characters long... :rolleyes:
 
Last edited:
What do you think ?
Paul ?
Michael ?
Tni ?

We could limit it with #ifdefs for teensy 3.1.. 3.6 and use the old version for Teensy LC and 3.0..

I would prefer to get the same answer/output on all Teensy 3.x/LC. While I tend not print floating point all that often, I do use the Teensy LC at times for neopixel/ws2812b work where it is great. In fact, I'm working on soldering (and resoldering and resoldering...) a LC project right now. And I have a few Teensy 3.0's kicking around that sometimes I re-program and/or use.

On the Teensy 3.5/3.6, long term we may want a specialization for float as opposed to double for printNumber (if we don't have one already), so that we can take advantage of the hardware fp support. Otherwise if there is only a double handler and not float, it will convert the argument double and then do the emulation in software.
 
Last edited:
Depends. I learn from this: Don't use print() for floats and use printf() instead....

It depends. If you are doing Teensy only code then yes. If your code still tries to run on other Arduino type platforms, then you might want to limit yourself to using Serial<x>.print and println. That being said, I am trying to remember the last time I actually programmed on a non-Teensy. In fact my project for the day is replacing a squarewear with a Teensy LC.
 
Back
Top