How to safely and reasonably convert a float or double to string or char array?

It is trivial to fix or work around the limitation of dtostrf.

  1. Use snprintf() with floating point extensions enabled.

I think that is "far from trivial" by Arduino standards, unfortunately.

is it concluded that the description for dtostrf is wrong or dtostrf is implemented wrong?

I think it matches the (avr-libc documentation) description. "width" was documented as "minimum width" and precision is "number of places after decimal." There does not seem to be a "maximum width" parameter, so numbers like "99e20" are a problem.
The floating point format (on AVR) is limited to values <1039, so a number will never take more than sign+39digits+.+ bytes, I think...

It's worth remembering why printf() and floatingPoint printf() are not the defaults. a printf() based implementation of "hello world" is about 1k bigger than the same with Serial.print(), and using the floating point version of printf() expands the difference to about 4kbytes, WHETHER OR NOT YOU USE THE FLOATING POINT FEATURES. Those were big numbers back when a "large" AVR only had 4k to 8k of code space.
(OTOH, the difference in size between using the floating point printf vs using the Arduino Serial.print(float,prec) is only a couple hundred bytes.)
I'm a little disappointed that the Arduino team keeps extending Serial.print() instead of embracing industry standards.
OTOH, I'm also a bit disappointed that dtostr() isn't just a wrapper for __ftoa_engine() or something; it's not clear to me how they're different. And this (in the dtoa_prf.c) seems a bit worrisome:

   unsigned char buf[[color=red]9[/color]];
       :
    [color=red]ndigs = [/color]prec < 60 ? prec + 1 : [color=red]60[/color];
    exp = __ftoa_engine (val, (char *)buf, 7, [color=red]ndigs[/color]);

Looks to me like they just told the engine that their 9byte buffer had room for 60 digits...
And ... It looks like the ftoa_engine output is ... really weird.

One solution is to check every float before calling the dtostrf but as I have mentioned before that would take so much time since you have to make sure each number is within the positive and negative boundary. That is 2 float comparison for each time dtostrf is called

Actually, floating point comparisons are very cheap. MUCH cheaper than the first division that WILL happen if you actually do the conversion. (in fact, most FP formats are cleverly designed so that a floating point comparison can happen nearly as quickly as a fixed point comparison (even without FP hardware.) Except for NaN and other "features." That's why you see things like "excess128" instead of two's complement used for the exponent.)