Good idea and good start. Thanks.
Your "TODO" list:
@robtillaart:
- remove the static char s[16]; as multiple calls will overwrite it.
Well, the issue to me is not that it is overwritten by subsequent calls. There is no recursion, and "barefoot" Arduino code doesn't have threads or anything else that requires re-entrance.
However...
Having to dedicate 16 bytes of RAM for this function might be an issue with a resource-limited processor.
On the other hand...
Having the user pass the name of an array and the length of the array (like strncpy, for example) would be easy to implement, but would make the user actually think about what is going on. And, maybe, actually to learn some fundamentals about C programming and arrays using array names in function calls. See Footnote.
I think that rounding should be done to make it consistent with standard C (and C++) output functions. I mean, we have enough trouble getting through to inexperienced people that floating point is about significant digits, not decimal places. At least we can present correctly rounded values. So, 3.1415926..., rounded to five significant decimal digits is 3.1416, not 3.1415.
"Corner" tests like rounding 1.999518 E+20 to four decimal places (five significant digits) gives 1.9995 E+20.
Rounding 1.999496 E+20 do four decimal places gives the same answer.
Rounding them to three decimal places gives 2.000 E+20 and 1.999 E+20, respectively.
Stuff like that.
If I am going to use a program even one day after I write it, I put a few comments to remind my tired old brain what the heck I did (and, maybe, even why the heck I did it). Additional comments to help others understand anything is often considered lagniappe (unless it's for public release, or it's a commercial environment and the code must be supported for future debugging or enhancement). I also like to make test program outputs verbose enough to let me see where the heck the numbers came from. I may wont to (or have to) refer back to comments in the code to tell me what the numbers mean (and how to see whether there are any errors).
Here's my test program:
//
// Test of function that builds a C-style "string" for a floating point
// number in scientific notation.
//
// davekw7x
// December, 2010
//
void setup()
{
float f;
Serial.begin(9600);
f = 0.0;
// No "digits" argument means 2 digits
Serial.print("0.0 = ");Serial.println(float2s(f));Serial.println();
// Rounding with leading zeros in the fractional part
f = 10.3456;
Serial.print(f,4);Serial.print(" = ");Serial.println(float2s(f, 4));Serial.println();
/*
For loops 1 and 2, note that floating point
overflows result values of"INF"
*/
f = PI;
Serial.println("Loop 1");
for (int i=1; i< 15; i++)
{
Serial.println(float2s(f, 7));
f *=1000;
}
Serial.println();
f = -PI;
Serial.println("Loop 2");
for (int i=1; i< 15; i++)
{
Serial.println(float2s(f, 4));
f *=1000;
}
Serial.println();
/*
For loops 3 and 4 note that floating point
underflows result in values that go to zero.
*/
f = PI;
Serial.println("Loop 3");
for (int i=1; i< 19; i++)
{
Serial.println(float2s(f, 4));
f /= 1000;
}
Serial.println();
f = -PI;
Serial.println("Loop 4");
for (int i=1; i< 19; i++)
{
Serial.println(float2s(f, 4));
f /= 1000;
}
Serial.println();
/*
Loop 5 shows rounding as follows:
6: 1.999518 E+20
5: 1.99952 E+20
4: 1.9995 E+20
3: 2.000 E+20
2: 2.00 E+20
1: 2.0 E+20
0: 2 E+20
*/
f = 1.999518e20;
Serial.println("Loop 5");
for (int i = 6; i >= 0; i--) {
Serial.print(i);Serial.print(": ");Serial.println(float2s(f, i));
}
Serial.println();
/*
Loop 6 shows rounding as follows:
6: 1.999496 E+20
5: 1.99950 E+20
4: 1.9995 E+20
3: 1.999 E+20
2: 2.00 E+20
1: 2.0 E+20
0: 2 E+2
*/
f = 1.999496e20;
Serial.println("Loop 6");
for (int i = 6; i >= 0; i--) {
Serial.print(i);Serial.print(": ");Serial.println(float2s(f, i));
}
Serial.println();
Serial.println("NaN tests");
f = sqrt(-1);
Serial.print("sqrt(-1) = ");Serial.println(float2s(f, 3));
f = 0.0/0.0;
Serial.print("0.0/0.0 = ");Serial.println(float2s(f, 4));
f = INFINITY * 0;
Serial.print("INFINITY*0 = ");Serial.println(float2s(f, 5));Serial.println();
Serial.println("INFINITY tests");
f = 1.0/0;
Serial.print("1.0/0 = ");Serial.println(float2s(f, 1));
//printBytes(f);
f = -1.0/0;
Serial.print("1.0/-0 = ");Serial.println(float2s(f, 1));
//printBytes(f);
f = -1.0/0L;
Serial.print("1.0/0L = ");Serial.println(float2s(f, 2));
f = 1.0/0UL;
Serial.print("1.0/0UL = ");Serial.println(float2s(f, 3));
Serial.println();
// Note that tan(pi/2) may not result in INF due
// to limited precision.
//
f = tan(HALF_PI);
Serial.print("tan(");Serial.print(HALF_PI, 6);Serial.print(") = ");
Serial.println(float2s(f, 6));
}
void loop()
{
}
With proper implementation of rounding, the output will look something like this:
[color=#0000ff]0.0 = 0.00 E+0
10.3456 = 1.0346 E+1
Loop 1
3.141593 E+0
3.141593 E+3
3.141593 E+6
3.141593 E+9
3.141593 E+12
3.141593 E+15
3.141593 E+18
3.141593 E+21
3.141593 E+24
3.141593 E+27
3.141593 E+30
3.141593 E+33
3.141593 E+36
INF
Loop 2
-3.1416 E+0
-3.1416 E+3
-3.1416 E+6
-3.1416 E+9
-3.1416 E+12
-3.1416 E+15
-3.1416 E+18
-3.1416 E+21
-3.1416 E+24
-3.1416 E+27
-3.1416 E+30
-3.1416 E+33
-3.1416 E+36
-INF
Loop 3
3.1416 E+0
3.1416 E-3
3.1416 E-6
3.1416 E-9
3.1416 E-12
3.1416 E-15
3.1416 E-18
3.1416 E-21
3.1416 E-24
3.1416 E-27
3.1416 E-30
3.1416 E-33
3.1416 E-36
3.1416 E-39
3.1417 E-42
2.8026 E-45
0.0000 E+0
0.0000 E+0
Loop 4
-3.1416 E+0
-3.1416 E-3
-3.1416 E-6
-3.1416 E-9
-3.1416 E-12
-3.1416 E-15
-3.1416 E-18
-3.1416 E-21
-3.1416 E-24
-3.1416 E-27
-3.1416 E-30
-3.1416 E-33
-3.1416 E-36
-3.1416 E-39
-3.1417 E-42
-2.8026 E-45
0.0000 E+0
0.0000 E+0
Loop 5
6: 1.999518 E+20
5: 1.99952 E+20
4: 1.9995 E+20
3: 2.000 E+20
2: 2.00 E+20
1: 2.0 E+20
0: 2 E+20
Loop 6
6: 1.999496 E+20
5: 1.99950 E+20
4: 1.9995 E+20
3: 1.999 E+20
2: 2.00 E+20
1: 2.0 E+20
0: 2 E+20
NaN tests
sqrt(-1) = NAN
0.0/0.0 = NAN
INFINITY*0 = NAN
INFINITY tests
1.0/0 = INF
1.0/-0 = -INF
1.0/0L = -INF
1.0/0UL = INF
tan(1.570796) = -2.287733 E+7[/color]
I'll show my take on a possible implementation of the function in my next post.
Regards,
Dave
Footnote:
Here's my observation based on about six months reading this forum:
The "Arduino Way" is designed to make things "easy to use" for people who don't know much about programming. Easy to get started without having to think about or to learn many of the fundamentals of C and C++. That part of the "Arduino Way" is the thing that drives many programmers crazy when people with no background need (or think they need) to use things that go beyond the fundamentals spelled out in example sketches in the distribution or in the Arduino playground.
I mean, a function that returns something that can be used in a Serial.print (or LiquidCrystal print) statement without making the calling function declare an array and use the name of the array as an argument is, in my opinion, the "Arduino Way." Permanently giving up 15 or 16 bytes of RAM in a given sketch to do things the "Arduino Way" may be acceptable. Or not.
Making this function take the name of an array (and the length) like we do with strncpy() and snprintf() and stuff like that is (probably) the way I would do it for my own use, but...
My final words on the "Arduino Way": Chacun à son goût!