Go Down

### Topic: Full double to String implementation (Read 4892 times)previous topic - next topic

#### Raeki

##### Jul 18, 2012, 05:32 am
While working on a project with the Arduino, I was frustrated by the lack of double-to-string functionality. I scoured the web and only found "dumb implementations" that only worked to a fixed precision and did not support larger ranges (outside the (long/int) boundaries).

So I've written one myself. I haven't had any problems with it, but if anyone notices any bugs it'd be greatly appreciated. Hopefully this will help others in the future , would've saved me quite some time. And also hope I put this in the right section :/ . Let me know what you guys think!

This will use "E+" or "E-" when the numbers start going out of range, and will shift accordingly to get the best precision (it's not "125333.00" and "0.32" and "235.30", it's just "12533" and "0.32" and "235.3").

Code: [Select]
`#include <Arduino.h>#include <math.h>#define INTEGER_MAX (pow(2,31)-1)#define E_MAX (pow(10, 7))#define E_MIN (pow(10, -6))#define EPSILON 0.000000119209int compareNums(double x, double y) { if (abs(x - y) <= EPSILON) { return 0; } else if (x > y) { return 1; } else { return -1; }}String doubleToString(double d) { String bin = ""; if (!isfinite(d)) { return "NaN"; } if (d < 0) { d *= -1; bin += "-"; } if (compareNums(d, E_MAX) >= 0) { //use E+ int e = 0; while (compareNums(d, 10) >= 0) { //while d is greater than or equal to 10 (not in scientific form) d /= 10; e++; } while (compareNums(d, (long) d) != 0) { //while not an integer d *= 10; } String str = String((long) d); bin += str.substring(0, 1) + "." + str.substring(1) + "E+" + String(e); } else if (compareNums(d, 1) == -1) { //between 0-1 if (compareNums(d, E_MIN) == -1) { //use E- int e = 0; while (d < 1) { //while d is not in scientific form d *= 10; e++; } while (compareNums(d, (long) d) != 0) { //while not an integer d *= 10; } String str = String((long) d); bin += str.substring(0, 1) + "." + str.substring(1) + "E-" + String(e); } else { //regular decimal less than 0 int decimals = 0; while (compareNums(d, (long) d) != 0) { //while not an integer d *= 10; decimals++; } String str = String((long) d); bin += "0."; for (int i = 0; i < decimals - 1; i++) { bin += "0"; } bin += str; } } else { //regular decimal int decimals = 0; while (compareNums(d, (long) d) != 0) { //while not an integer d *= 10; decimals++; } String str = String((long) d); if (decimals == 0) { bin += str; } else { bin += str.substring(0, str.length() - decimals) + "." + str.substring(str.length() - decimals); } } return bin;}`

#### PaulS

#1
##### Jul 18, 2012, 01:36 pmLast Edit: Jul 18, 2012, 01:38 pm by PaulS Reason: 1
Code: [Select]
` String str = String((long) d);`
Two constructors and a copy operation to perform what could be done with a single constructor. Why?

Code: [Select]
` String str = String((long) d);`
You did it again. A total of 4 times.

Better would have been to use dtorstre() to convert the value to a char array. If necessary (and it really isn't), you could then make a String from that character array.

#### Raeki

#2
##### Jul 19, 2012, 12:23 am

Code: [Select]
` String str = String((long) d);`
Two constructors and a copy operation to perform what could be done with a single constructor. Why?

How can I simplify it?

Quote

Better would have been to use dtorstre() to convert the value to a char array. If necessary (and it really isn't), you could then make a String from that character array.

What do you mean? My problem with dtostre() was that it had fixed precision and always used scientific mode.

#### WizenedEE

#3
##### Jul 19, 2012, 08:03 am

Code: [Select]
` String str = String((long) d);`
Two constructors and a copy operation to perform what could be done with a single constructor. Why?

Don't think so. Wouldn't it be a constructor and then a copy constructor?

How can I simplify it?

Code: [Select]
`String str((long) d);`

The pow() function operates on floats only, so your INTEGER_MAX is going to be wrong. Furthermore, there are already predefined things for MAX_INT of various sizes.

#### PaulS

#4
##### Jul 19, 2012, 02:14 pm
Quote
Don't think so. Wouldn't it be a constructor and then a copy constructor?

Maybe. I'd need to instrument the code to be sure. Still more than is necessary, though.

Quote
What do you mean? My problem with dtostre() was that it had fixed precision and always used scientific mode.

From 1.2.5/group__avr__stdlib.html#ga37]https://ccrma.stanford.edu/courses/250a/docs/avr-libc-user-manual-
Quote
1.2.5/group__avr__stdlib.html#ga37[/url]
char* dtostre    (     double     __val,
char *     __s,
unsigned char     __prec,
unsigned char     __flags
)

The dtostre() function converts the double value passed in val into an ASCII representation that will be stored under s. The caller is responsible for providing sufficient storage in s.

Conversion is done in the format "[-]d.ddde?dd" where there is one digit before the decimal-point character and the number of digits after it is equal to the precision prec; if the precision is zero, no decimal-point character appears. If flags has the DTOSTRE_UPPERCASE bit set, the letter 'E' (rather than 'e' ) will be used to introduce the exponent. The exponent always contains two digits; if the value is zero, the exponent is "00".

If flags has the DTOSTRE_ALWAYS_SIGN bit set, a space character will be placed into the leading position for positive numbers.

If flags has the DTOSTRE_PLUS_SIGN bit set, a plus sign will be used instead of a space character in this case.

The dtostre() function returns the pointer to the converted string s.

char* dtostrf    (     double     __val,
char     __width,
char     __prec,
char *     __s
)

The dtostrf() function converts the double value passed in val into an ASCII representationthat will be stored under s. The caller is responsible for providing sufficient storage in s.

Conversion is done in the format "[-]d.ddd". The minimum field width of the output string (including the '.' and the possible sign for negative values) is given in width, and prec determines the number of digits after the decimal sign.

The dtostrf() function returns the pointer to the converted string s.

So, you should be able to vary the format of the string returned by dtostre(), but it seems more likely that you want to use dtostrf().

#### cmiyc

#5
##### Jul 20, 2012, 01:54 am
How useful is this since double isn't implemented?
Capacitor Expert By Day, Enginerd by night.  ||  Personal Blog: www.baldengineer.com  || Electronics Tutorials for Beginners:  www.addohms.com

#### Raeki

#6
##### Jul 21, 2012, 12:15 am
@PaulS

As far as I understand dtostrf() doesn't use "E" when numbers get too big.
dtostre() has a fixed precision after the decimal point (specified clearly in the documentation). So you're forced to have stuff like 1.00000E6 if you want to make sure you cover everything (my version will give 1E6 and 2.345E8 and 2.34567E4; expands and shrinks as necessary).

So those aren't viable replacements for my use. dtostre() (essentially scientific notation) isn't bad for most uses, but sometimes (at least the project I'm working on) you don't want stuff like 6.00000E0 to display 6. You only want the scientific notation for big or small numbers, and as I said I also don't want the extra 0s if they aren't necessary.

How useful is this since double isn't implemented?

Um... double is most certainly implemented. If you mean it's the same as float, this function doesn't change since it works for floats and doubles (there's no similar float-to-string)

Go Up