Go Down

Topic: Proposed update for the printFloat code of print.cpp (Read 10112 times) previous topic - next topic

robtillaart

May 12, 2013, 07:54 pm Last Edit: May 15, 2013, 09:52 pm by robtillaart Reason: 1
In the Arduino Core library the printing of floats is done with the use of unsigned long numbers.
The current 1.0.4 implementation prints "ovf" when a float is larger that maxlong (approx).
This is not correct.

To solve this I added code to printFloat so it will print print large ( > 1E+9) and small ( < 1E-3) numbers automatically in scientific notation:  x.xxxxE+nn   or   x.xxxxE-nn

There is also the engineering E-notation that only allows multiples of 3 as exponent. Could be implemented in a similar way. However that would require an interface change of print() for floats as one should be able to select the representation. The interface would change to something like print(float number, uint8_t digits, uint8_t repr = 0); For repr there could be defines or an enum { NORMAL, ENGINEERING, SCIENTIFIC, .... } or just 0,1,2

please review and test.

printFloat() can be found in print.cpp - one of the core libs.
update: do not use this code, it has some bugs ; => use zip file from reply #10 instead
Code: [Select]

size_t Print::printFloat(double number, uint8_t digits)
{
 size_t n = 0;
 int exponent = 0;
 
 if (isnan(number)) return print("nan");
 if (isinf(number)) return print("inf");

 if ( abs(number) > 1000000000.0)
 {
   while (abs(number) > 10.0)
   {
     number /= 10.0;
     exponent++;
   }
 }
 else if (abs(number) < 0.001)
 {
   while (abs(number) < 1.0)
   {
     number *= 10.0;
     exponent--;
   }
 }

 // Handle negative numbers
 if (number < 0.0)
 {
    n += print('-');
    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 /= 10.0;
 
 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;
 n += print(int_part);

 // Print the decimal point, but only if there are digits beyond
 if (digits > 0) {
   n += print(".");
 }

 // Extract digits from the remainder one at a time
 while (digits-- > 0)
 {
   remainder *= 10.0;
   int toPrint = int(remainder);
   n += print(toPrint);
   remainder -= toPrint;
 }
 
if (exponent != 0)
 {
   n += print('E');
   if (exponent > 0)
   {
      n += print('+');
   }
   n += print(exponent);
 }

 return n;
}


Simple test sketch to show it works at least for some values
Code: [Select]

//
//    FILE: testEnotation.ino
//  AUTHOR: Rob Tillaart
//    DATE: 2013-05-12
//
// PURPOSE: test scientific notation above 1E9
//
void setup()
{
 Serial.begin(115200);

 float f = 355.0/113.0;
 for (int i = 0; i < 45; i++)
 {
   Serial.println(f, 6);
   f = f * 10;
 }

 f = 355.0/113.0;
 for (int i = 0; i < 45; i++)
 {
   Serial.println(f, 6);
   f = f / 10;
 }  
}

void loop()
{
}


as always comments are welcome
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

also posted as a issue on github - https://github.com/arduino/Arduino/issues/1412 -
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

#2
May 12, 2013, 08:55 pm Last Edit: May 15, 2013, 09:53 pm by robtillaart Reason: 1
I am tinkering with changing the interface for print for floats as mentioned above

Code: [Select]

print(float number, uint8_t digits = 2, uint8_t notation = 0);
println(float number, uint8_t digits = 2, uint8_t notation = 0);

by using the 0 as default parameter for notation, it will be compatible with existing sketches.

For the notation parameter I would like to use terms familiar from calculators.

Code: [Select]
 
#define DEF 0   // my calculator shows nothing so DEF from DEFAULT
#define SCI 1
#define ENG 2
#define FIX 3    // not implemented


is this enough?
are there modes missing?
does one need/like  long symbolic names: DEFAULT, SCIENTIFIC, ENGINEERING, ?
other remarks wrt the interface?
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

Another question: what is prefered:  E or e or nothing, and why?   (in my order of preference)

3.141593E+25 
3.141593+25   // uses 1 char less footprint on e.g. LCD ==> separate notation mode? e.g. println(355.0/113.0, 6, SCI_SHORT);
3.141593e+25  // less readable
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

small update to support negative infinity

Code: [Select]
 
  if (isnan(number)) return print("nan");
  if (isinf(number))
  {
    if (number < 0) print("-");
    return print("inf");
  }
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

Question: with normal printing the print "looses" digits in the output, like this

-3.141593
-0.314159
-0.031416
-0.003142
-3.141593-4
-3.141593-5
-3.141593-6


Question: should the scientific notation be used for all numbers < 1.0 or  < 0.001 or  < 0.1  or 0.5 ? or other value?  Why?

So like this
-3.141593 
-3.141593-1
-3.141593-2
-3.141593-3
-3.141593-4
-3.141593-5
-3.141593-6
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

#6
May 12, 2013, 10:30 pm Last Edit: May 15, 2013, 09:54 pm by robtillaart Reason: 1
Beta of new print.cpp and print.h, to be installed in   --  C:\Program Files (x86)\arduino-1.0.4\hardware\arduino\cores\arduino

disclaimers:
- not tested thoroughly
- not tested with earlier versions,
- use at your own risk...
update: depreciated, use zip from reply #10
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

When testing the beta print.cpp/.h (from the zip above)  one can get a warning about redefine of DEFAULT.

solution: remove the line "#define DEFAULT 0" from print.h file to remove the warning (one can use DEF instead or just use no 3rd param).
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

Quote
Another question: what is prefered:  E or e or nothing, and why?   (in my order of preference)

3.141593E+25 
3.141593+25   // uses 1 char less footprint on e.g. LCD ==> separate notation mode? e.g. println(355.0/113.0, 6, SCI_SHORT);
3.141593e+25  // less readable


Excel gave the answer, when writing to a csv file for Excel it did not recognize the 3.141593+25 formatted value as a valid number.
I'm going for the big E notation as this is more readable on all screens than lower case.
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

#9
May 15, 2013, 08:14 pm Last Edit: May 15, 2013, 08:17 pm by robtillaart Reason: 1
Quote
Question: should the scientific notation be used for all numbers < 1.0 or  < 0.001 or  < 0.1  or 0.5 ? or other value?  Why?

The default for printing floats shows 2 decimal places. That means that values < 0.01 will be printed as 0.00 even when they are not zero.

Because the E notation would use more chars on screen it might screw up the layout on an LCD display  
e.g. 1/1000 => 1.00E-3 so that is 3 chars more than 0.00

So in conclusion:
- for the default mode small numbers will not be printed in E notation, only large numbers will.
 This keeps it backwards compatible in terms of the max #places of the number.
- for the SCI & ENG mode numbers smaller than 1.0 will be printed in E notation.
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

#10
May 15, 2013, 08:49 pm Last Edit: May 15, 2013, 09:50 pm by robtillaart Reason: 1
Quote
does one need/like  long symbolic names: DEFAULT, SCIENTIFIC, ENGINEERING, ?

No,  => DEFAULT is already defined and keeping the SCI and ENG (same as on calculator) is sufficient.




note: did testing, fixed bugs, optimized code ==> Attached new version of print.zip, imho the SCI and ENG work good enough for now

update: test sketch used to test new printFloat routine
Code: [Select]

//
//    FILE: testEnotation.ino
//  AUTHOR: Rob Tillaart
//    DATE: 2013-05-12
//
// PURPOSE: test scientific notation
//
void setup()
{
 Serial.begin(115200);


 Serial.println("\tout of range values\n");
 Serial.println(1.0/0.0, 6);
 Serial.println(-1.0/0.0, 6);
 Serial.println(sin(0)/0, 6);
 Serial.println("---------\n");

 Serial.println("\tprintln(f, 6)\n");
 float f = -PI;
 for (int i = 0; i < 45; i++)
 {
   Serial.println(f, 6);
   f = f * 10;
 }
 Serial.println("---------\n");


 Serial.println("\tprintln(f, 6)\n");
 f = PI;
 for (int i = 0; i < 45; i++)
 {
   Serial.println(f, 6);
   f = f / 10;
 }  
 Serial.println("---------\n");


 Serial.println("\tprintln(f, 6, SCI)\n");
 f = PI;
 for (int i = 0; i < 45; i++)
 {
   Serial.println(f, 6, SCI);
   f = f * 10;
 }
 Serial.println("---------\n");


 Serial.println("\tprintln(f, 6, ENG)\n");
 f = PI;
 for (int i = 0; i < 45; i++)
 {
   Serial.println(f, 6, ENG);
   f = f * 10;
 }
 Serial.println("---------\n");


 Serial.println("\tprintln(f, 6, -1) // not supported -> default\n");
 f = -PI;
 for (int i = 0; i < 50; i++)
 {
   Serial.println(f, 6, -1);  // illegal param
   f = f / 10;
 }  
 Serial.println("---------\n");


 Serial.println("\tnumber without decimal part\n");
 f = 12345.0;
 Serial.println(f); // default 2
 Serial.println(0);  // int converted to float
 Serial.println((float)12345, 2);  // int converted to float
 Serial.println((float)-12345, 2);  
 Serial.println("---------\n");

 Serial.println(-0, 6, SCI);
 Serial.println(12345, 6, SCI);
 Serial.println(-12345, 6, SCI);
 Serial.println("---------\n");

 Serial.println(0, 6, ENG);
 Serial.println(12345, 6, ENG);
 Serial.println(-12345, 6, ENG);
 Serial.println("---------\n");

 Serial.println(0, 6, -1);
 Serial.println(12345, 6, -1);
 Serial.println(-12345, 6, -1);
 Serial.println("---------\n");

 Serial.println("\tspecial number\n");
 f = 10.0;
 Serial.println(f); // default 2
 Serial.println(f, 2);
 Serial.println(f, 2, SCI);  
 Serial.println(f, 2, ENG);
 Serial.println("---------\n");

 Serial.println("\ndone\n");
}

void loop() {}
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

pito

#11
May 15, 2013, 10:33 pm Last Edit: May 16, 2013, 12:25 am by pito Reason: 1
Cool!
It seems there is an issue with when single precision exp < -38 and ENG notation
Code: [Select]
3.1416E-36
314.1593E-39
31.4159E-39
3.1416E-39
314.1600E-42
31.4157E-42
3.1417E-42
313.8908E-45
30.8286E-45


The same with SCI:
Code: [Select]
3.1416E-35
3.1416E-36
3.1416E-37
3.1416E-38
3.1416E-39
3.1416E-40
3.1416E-41
3.1417E-42
3.1389E-43
3.0829E-44

robtillaart

#12
May 16, 2013, 06:52 pm Last Edit: May 16, 2013, 07:03 pm by robtillaart Reason: 1
Quote
It seems there is an issue with when single precision exp < -38 and ENG notation


It is reaching the end of the 32-bit float universe. From - https://en.wikipedia.org/wiki/Single_precision_floating-point_format -
The minimum positive (subnormal) value ? 1.4 × 10^?45.
The minimum positive normal value ? 1.18 × 10^?38.
The maximum representable value ? 3.4 × 10^38.

With ENG notation you have constant a variation of the (significant) digits where SCI has the same every time.
If you round the ENG numbers you should get the SCI ones - check it -

Personally I go for SCI as it has a constant length (except for sign*) and will always fit on a fixed area on a LCD.

(*)maybe add an explicit  leading sign would enforce this for positive and negative numbers? like a multimeter?

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

pito

#13
May 17, 2013, 09:35 am Last Edit: May 17, 2013, 11:58 am by pito Reason: 1
Quote
With ENG notation you have constant a variation of the (significant) digits where SCI has the same every time.

What about "constant significant digits ENG"? For example:
Code: [Select]

3.141E-9
314.1E-12
31.41E-12
3.141E-12

In engineering practice you do care more about the total number of "valid" digits in ENG, rather than number of digits after the decimal point..

robtillaart


Quote
What about "constant significant digits ENG"? For example:


makes sense, would mean that the parameter "digits" get another meaning when using the ENG option.
It is not complex to code, so when time permits I'll give it a try.
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Go Up