Arduino Forum

Development => Other Software Development => Topic started by: robtillaart on May 12, 2013, 07:54 pm

Title: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on May 12, 2013, 07:54 pm
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
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on May 12, 2013, 08:06 pm
also posted as a issue on github - https://github.com/arduino/Arduino/issues/1412 -
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on May 12, 2013, 08:55 pm
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?
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on May 12, 2013, 09:13 pm
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
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on May 12, 2013, 10:11 pm
small update to support negative infinity

Code: [Select]
 
  if (isnan(number)) return print("nan");
  if (isinf(number))
  {
    if (number < 0) print("-");
    return print("inf");
  }
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on May 12, 2013, 10:26 pm
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
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on May 12, 2013, 10:30 pm
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
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on May 14, 2013, 10:36 pm
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).
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on May 15, 2013, 07:38 pm
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.
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on May 15, 2013, 08:14 pm
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.
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on May 15, 2013, 08:49 pm
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() {}
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: pito on May 15, 2013, 10:33 pm
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
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on May 16, 2013, 06:52 pm
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?

Title: Re: Proposed update for the printFloat code of print.cpp
Post by: pito on May 17, 2013, 09:35 am
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..
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on May 17, 2013, 04:37 pm

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.
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: dariosalvi on Jan 25, 2017, 12:11 pm
Hi,

how do I use this library?

if I just import it, I can't instantiate any Print object because it's virutal...

Any suggestion?
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: aweatherguy on Jan 25, 2017, 09:46 pm
You don't directly instantiate it. This is a replacement for the Print.cpp file in the hardware/arduino/avr/cores/arduino directory. You overwrite the files in that directory with the new ones. This re-defines the print class which propagates to Serial, HardwareSerial and so on.

If you do this, then you must do it again everytime you update the Arduino IDE installation because that will re-install a default Print.cpp file.
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on Oct 09, 2017, 01:23 pm

Included updated code for print.cpp and print.h work with IDE 1.8.1
This is not the latest version IDE and I will check 1.8.5 compatibility a.s.a.p.

This code also prints 64 bit integers, signed and unsigned.
(so you can get that prime sieve beyond 32 bit)

Unfortunatelly I did not have time to test all integers and floats ;)
so remarks and comments are welcome.

Title: Re: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on Oct 09, 2017, 01:45 pm
Checked compatibility with IDE 1.8.5 ==> OK
Title: Re: Proposed update for the printFloat code of print.cpp
Post by: seradu on Oct 15, 2017, 08:08 pm
aweatherguy.... I follow your instruccions and replace both files print.h and print.cpp into my directory

C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino... but it is still not working.


My example code is:


double Wo = 500;
double L5 ;   
double resultado;     

void setup()
        {
            Serial.begin(9600);
        }

void loop()

        {
            L5 = pow(Wo,5);
            Serial.println(L5);
            resultado = L5 - (L5-2);//31249999999999;
            Serial.println(resultado);
            while(1);
        }


And the serial monitor is showing

ovf
2.00



I even try removing the files (print.h, print.cpp) from the directory, expecting to see a error message. But de IDE still showing


ovf
2.00

Title: Re: Proposed update for the printFloat code of print.cpp
Post by: robtillaart on Dec 09, 2017, 08:45 pm
@seradu
You might need to stop all instances of the IDE before the new version is used.