diff --git a/CHANGELOG.md b/CHANGELOG.md index 0617f3f..fd15bf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.4.5] - 2024-11-20 +- add experimental **fraction()** +- add **print_fractions.ino** example +- add **print_fractions_denum.ino** example +- update **print_sci_experimental.ino** example +- added define PRINTHELPERS_LIB_VERSION. +- update readme.md + ## [0.4.4] - 2024-01-05 -- FIx URL in examples +- fix URL in examples - minor edits - ## [0.4.3] - 2023-11-15 - added **csi()** comma separated integers for readability e.g. 123,458,654 - update readme.md. @@ -41,7 +48,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ---- -## [0.3.1] - 2023-01-27 +## [0.3.1] - 2023-01-27 - fix **scieng()** itoa() => sprintf() + conditional ESP32 - add leading 0 for exponents smaller than 10, to better align columns. - update GitHub actions diff --git a/README.md b/README.md index d00f4af..19e7f1b 100644 --- a/README.md +++ b/README.md @@ -16,35 +16,42 @@ Arduino library to help formatting data for printing. ## Description -The printHelpers library contains a number of functions that help to print -data in a way not possible in the standard print library of the Arduino. - -- **print64()** print **uint64_t** and **int64_t** -- **sci()** generates in scientific format - exponent has step 1. -- **eng()** generates in engineering format - exponent has step 3. -- **scieng()** generates in exponential format - exponent has step 1 to 9. -- **toBytes()** generates KB MB GB etc. -- **hex()** generates hexadecimal output with leading zeros up to **uint64_t**. -- **bin()** generates binary output with leading zeros up to **uint64_t**. -- **toRoman()** generates a ROMAN representation of a (positive) number. -- **printInch(float inch, uint16_t step)** experimental. -- **printFeet(float feet)** experimental. -- **csi()** generates a comma separated integer for readability. +The printHelpers library contains a number of functions that help to print +data in a way not supported in the standard print library of the Arduino. + +- **char \* print64()** returns a string for **uint64_t** and **int64_t**. +- **char \* sci()** returns a string in scientific format - exponent has step 1. +- **char \* eng()** returns a string in engineering format - exponent has step 3. +- **char \* scieng()** returns a string in exponential format - exponent has step 1 to 9. +- **char \* toBytes()** returns a string in KB MB GB etc. +- **char \* hex()** returns hexadecimal output with **leading zeros** up to **uint64_t**. +- **char \* bin()** returns binary output with **leading zeros** up to **uint64_t**. +- **char \* toRoman()** returns a ROMAN representation of a (positive) number. +- **char \* printInch(float inch, uint16_t step)** returns a string e.g. 5 7/8". +- **char \* printFeet(float feet)** returns a string e.g. 7"4' +- **char \* csi()** returns a comma separated integer for readability e.g. 3,254,152. +- **char \* fraction()** returns a fraction representation of a double/float e.g. 355/113. For the details, see sections below. If a (generic) print format is missing, please open an issue. -#### Thread safety +### Thread safety -Note the functions of this library all share an internal buffer, so the library is -definitely **not** thread safe. +Note the functions of this library all share an internal buffer, so the library is +definitely **not** thread safe. Therefore one should copy / print the data (returned pointer) as fast as possible. Thread-safe versions of these print functions might be made in the future. +### Related + +- https://github.com/RobTillaart/Fraction +- https://github.com/RobTillaart/lineFormatter (for tabular formatting) + + ## Interface ```cpp @@ -54,50 +61,50 @@ Thread-safe versions of these print functions might be made in the future. The following functions are implemented: -#### print64() +### print64() -- **char \* print64(int64_t value, uint8_t base = 10)** converts a 64 bit integer -number to a char array. +- **char \* print64(int64_t value, uint8_t base = 10)** converts a 64 bit integer +number to a char array. The plus sign is not printed, neither are leading zero's. Base 10 (DEC) and 16 (HEX) are supported and other bases up to 36 can be used. Default base == 10 == decimal. Note that negative numbers will always get a minus sign for any base. Cast the number to uint64_t to suppress the sign. -- **char \* print64(uint64_t value, uint8_t base = 10)** converts a unsigned 64 bit -int number to a char array. -No sign is printed, neither are leading zero's. +- **char \* print64(uint64_t value, uint8_t base = 10)** converts a unsigned 64 bit +int number to a char array. +No sign is printed, neither are leading zero's. Base 10 (DEC) and 16 (HEX) are supported and bases up to 36 can be used. Default base == 10 == decimal. -#### sci() eng() +### sci() eng() -- **char \* sci(double value, uint8_t decimals)** converts a float or double to a -char array. +- **char \* sci(double value, uint8_t decimals)** converts a float or double to a +char array. E.g. **print(sci(f, 4))** ==> results in **"6.7407E+21"**. The existing Arduino print library only supports printing of floats and -doubles up to about 4E9 while the range of floats goes up to ~1E38. -The smallest float values will often be printed as 0.00 while floats +doubles up to about 4E9 while the range of floats goes up to ~1E38. +The smallest float values will often be printed as 0.00 while floats support down to about 1E-38 (subnormal even to 1E-45). -Existing (AVR) library functions **dtostrf()** has no scientific notation +The existing (AVR) library function **dtostrf()** has no scientific notation and **dtostre()** is limited to 7 decimals. These latter two are faster. Values printed with **sci()** do look often pretty in column output. - **size_t sci(Stream &str, double value, uint8_t decimals)** as above. Prints directly to a stream, returning bytes printed. -- **char \* eng(double value, uint8_t decimals)** converts a float or double to a -char array. +- **char \* eng(double value, uint8_t decimals)** converts a float or double to a +char array. E.g. print(eng(f, 4)) ==> results in "6.7407E+21". Note the exponent created by **eng()** is always a multiple of 3. Values printed with **eng()** do not always look pretty in column output. This is due to the exponent power of 3. However its output translates easy to -thousands, millions, billions, and millis, micros, nano etc which are powers of 3. +thousands, millions, billions, and millis, micros, nano etc. which are powers of 3. -- **char \* scieng(double value, uint8_t decimals, uint8_t exponentMultiple)** converts a -float or double to a char array. -**sci()** and **eng()** use the same underlying function called **scieng()** +- **char \* scieng(double value, uint8_t decimals, uint8_t exponentMultiple)** converts a +float or double to a char array. +**sci()** and **eng()** use the same underlying function called **scieng()** as the initial code for converting was almost identical. Although not intended to be used directly, feel free to use it. The last parameter **exponentMultiple** defines where the exponent is a multiple of. @@ -108,15 +115,15 @@ Personally I like the multiple of 2 as I get 2 orders of magnitude in the mantissa. This is e.g. useful for temperature Celsius or percentages. -#### toBytes() +### toBytes() -- **char \* toBytes(double value, uint8_t decimals = 2)** converts a big number +- **char \* toBytes(double value, uint8_t decimals = 2)** converts a big number representing an amount of bytes to a shorter string usable for displaying. The string uses official extensions. -The number of decimals is max 3: - Example 3.292.528 ==> "3.140 MB" -Value ranges supported are in steps of powers of 1024. +The number of decimals is max 3, example: 3.292.528 ==> "3.140 MB" + +Value ranges supported are in steps of powers of 1024. These will all be shown in UPPERCASE so KB, MB etc. | Unit | abbrev. | size | Unit | abbrev. | size | @@ -130,28 +137,28 @@ These will all be shown in UPPERCASE so KB, MB etc. | exabytes | EB | 1024^6 | udabytes | UB | 1024^12 | -Treda Byte is shortened as "TDB" and uses 2 chars to indicate the magnitude. -That would take extra memory or slightly more complex code. -As it is very seldom used, "official" support stops with UDA. +Treda Byte is shortened as "TDB" and uses 2 chars to indicate the magnitude. +That would take extra memory or slightly more complex code. +As it is very seldom used, "official" support stops with UDA. Should be big enough for some time. -Note: max uint64_t 2^64 is in the order of exa or zetta bytes. +Note: max uint64_t == 2^64 is in the order of exa or zetta bytes. To have some support for the really big sizes the code uses lowercase for the next 8 levels: treda sorta rinta quexa pepta ocha nena minga luma (1024\^13 ~~ 1024\^21) To enable this patch the function in the **printHelpers.cpp** file. -#### hex() bin() +### hex() bin() -The default print() function of Arduino does not have leading zero's -for **HEX** and **BIN**. -This often causes a "broken" layout especially if one wants to print +The default print() function of Arduino does not have leading zero's +for **HEX** and **BIN**. +This often causes a "broken" layout especially if one wants to print the output in columns or so. -To solve this the following functions are added that will generate a -constant length char array. - +To solve this the following functions are added that will generate a +constant length char array. + - **char \* hex(uint64_t value, uint8_t digits = 16)** - **char \* hex(uint32_t value, uint8_t digits = 8)** - **char \* hex(uint16_t value, uint8_t digits = 4)** @@ -163,7 +170,7 @@ constant length char array. Note: Data types not supported, must be cast to an supported type. -Note: There is overlap between **hex(value)** and **print64(value, HEX)**. +Note: There is overlap between **hex(value)** and **print64(value, HEX)**. The latter does not produce the leading zero's or fixed length output. Note: hex() does not print hex indicator like "0x" or "H" in front. @@ -171,11 +178,11 @@ Note: hex() does not print hex indicator like "0x" or "H" in front. Note: bin() does not print bin indicator like "b" at the end. -#### toRoman() +### toRoman() https://en.wikipedia.org/wiki/Roman_numerals -A less used but well known print format are the Roman digits. +A less used but well known print format are the Roman digits. The library function **toRoman()** will convert any number from 0..100 million into a Roman number. The numbers 1..5000 ("official" range) are the well known UPPER case characters. @@ -202,7 +209,7 @@ Notes: - values between 5K-10K are extended with extra M chars. - values 10K-100M are represented with lower case characters. This is not a standard, but it sort of works well. -- values > 100M return OVF == overflow. +- values > 100M return OVF == overflow. - There is no special 'subtract code' for 9000 to have a clear distinction between "official" and extended numbers. - The number 4 is often written as IIII on clocks with Roman digits, @@ -210,7 +217,7 @@ Notes: The reason for IIII is that it is opposite of VIII giving a visual balance. -#### Distance feet inch +### Distance feet inch Experimental 0.4.1 @@ -221,12 +228,12 @@ The parameter step must be a power of 2 == 2, 4, 8, 16, 32, 64, 128. ``` a'b"``` e.g. 4.5 feet prints as ```4'6"``` -#### Comma Separated Integer +### Comma Separated Integer Experimental 0.4.3 When you are working with large numbers, more than lets say 6 digits. -With these numbers it is often difficult to see if it is 2 million something or 20 million something. +With these numbers it is often difficult to see if it is 2 million something or 20 million something. A proven way to solve this is to print those large numbers in groups of 3 digits separated by comma's. This improves the readability a lot and yes the price is more room needed on a display. The comma is chosen as it is default thousands separator in Excel. @@ -238,32 +245,55 @@ Like all printHelper functions it uses a shared print buffer to keep memory usag Example 192837465 becomes 192,837,465. signed -- **char \* csi(int64_t n)** -- **char \* csi(int32_t n)** -- **char \* csi(int16_t n)** +- **char \* csi(int64_t n)** +- **char \* csi(int32_t n)** +- **char \* csi(int16_t n)** - **char \* csi(int8_t n)** unsigned -- **char \* csi(uint64_t n)** -- **char \* csi(uint32_t n)** -- **char \* csi(uint16_t n)** +- **char \* csi(uint64_t n)** +- **char \* csi(uint32_t n)** +- **char \* csi(uint16_t n)** - **char \* csi(uint8_t n)** +### Fraction + +Experimental 0.4.5, based upon Fraction class. + +The precision is hard set to absolute 1e-6. +The fraction will have a numerator and denumerator in the range 1..99999. +Note that as floats only have 7 significant digits the precision varies +especially for numbers above 1 (as decimal part eats up significant digits). + +The algorithm is primary meant for values between 0 and 1 however any float +will be processed. The algorithm does not always come up with the best +fraction + +Time is not constant, e.g. **fraction(PI)** takes about 620 us on an Arduino UNO 16 MHz. + +- **char \* fraction(double value)** approach the value with a fraction like n / d. +- **char \* fraction(double value, uint16_t denom)** choose the denominator. +Note it will be reduced if possible e.g. 6/8 => 3/4 + +If you have a faster or more accurate algorithm or both please let me know +and open an issue. + + ## Shared print buffer -The implementation of the function all use a shared buffer to hold the -generated string. -This is done to reduce the memory overhead of embedding static buffers. +The implementation of the function all use a shared buffer to hold the +generated string. +This is done to reduce the memory overhead of embedding static buffers. **Note this is not thread safe!** -In a coming release the functions will be able to pass a buffer to them +In a coming release the functions will be able to pass a buffer to them to become more thread safe. -The size of this shared buffer is default 66 to be able to print a 64 bit -integer in base 2. -To save memory one can change this buffer size in the code or compile time +The size of this shared buffer is default 66 to be able to print a 64 bit +integer in base 2. +To save memory one can change this buffer size in the code or compile time by changing **PRINTBUFFERSIZE** in printHelpers.h. -Be aware that **sci()** and **eng()** use the same buffer. +Be aware that **sci()** and **eng()** use the same buffer. These functions need about 10 bytes plus one bytes for every decimal used. So for floats one need 15-20 bytes max, for doubles one need up to 30 bytes max. In practice a size of 22 will work for most applications. @@ -293,21 +323,21 @@ When functions are added, the recommended minimum size might increase. #### Could +- investigate **bin(float)** to dump floats? + - "sign, mantissa, exponent bits" + - like this "s0 m0111010 e100010" (right length) - investigate separators in **hex()** - space per 8, 4 or 2 - investigate thread safe version - pass char buffer as parameter (breaking) - could be the log10 pow version? - optimize **char \* hex(uint8_t / uint16_t ...)** -- make this library a .h file only? -- PRINTHELPERS_LIB_VERSION -- is there need for Scientific or Engineering integers - - 23.457e3 only positive powers - - could be without float math - - 4 versions (un)signed 32/64 ? +- negative ROMAN numbers (add a - sign) + #### Wont +- is there need for Scientific or Engineering integers (this just works) - add **oct()** along BIN, HEX - add **float()** as Arduino limits floats to "MAXLONG" by code. - use dtostrf() - is that portable? @@ -317,7 +347,7 @@ When functions are added, the recommended minimum size might increase. - investigate separators in **bin()** - point or space, per 8 or 4 or 2 - ==> printBuffer too small for bin(64) ==> need 75-100 bytes. -- Investigate performance and accuracy +- Investigate performance and accuracy - **sci()** and **eng()**. - investigate sci() version based upon use of log() - done => see examples. diff --git a/examples/print64/print64.ino b/examples/print64/print64.ino index 8e41a88..bebc153 100644 --- a/examples/print64/print64.ino +++ b/examples/print64/print64.ino @@ -19,8 +19,8 @@ void setup() { Serial.begin(115200); Serial.println(__FILE__); - Serial.print("PRINTHELPERS_VERSION: "); - Serial.println(PRINTHELPERS_VERSION); + Serial.print("PRINTHELPERS_LIB_VERSION: "); + Serial.println(PRINTHELPERS_LIB_VERSION); Serial.println(); delay(100); diff --git a/examples/print_comma_separated_integers/print_comma_separated_integers.ino b/examples/print_comma_separated_integers/print_comma_separated_integers.ino index a6c9b0d..0a561a8 100644 --- a/examples/print_comma_separated_integers/print_comma_separated_integers.ino +++ b/examples/print_comma_separated_integers/print_comma_separated_integers.ino @@ -13,8 +13,8 @@ void setup() Serial.begin(115200); while (!Serial); Serial.println(__FILE__); - Serial.print("PRINTHELPERS_VERSION: "); - Serial.println(PRINTHELPERS_VERSION); + Serial.print("PRINTHELPERS_LIB_VERSION: "); + Serial.println(PRINTHELPERS_LIB_VERSION); Serial.println((int32_t)123456789); Serial.println(csi((int32_t)123456789)); diff --git a/examples/print_fractions/print_fractions.ino b/examples/print_fractions/print_fractions.ino new file mode 100644 index 0000000..babf53a --- /dev/null +++ b/examples/print_fractions/print_fractions.ino @@ -0,0 +1,52 @@ +// +// FILE: print_fractions.ino +// AUTHOR: Rob Tillaart +// PURPOSE: demo +// URL: https://github.com/RobTillaart/printHelpers + + +#include "printHelpers.h" + +uint32_t start, stop, duration; + + +void setup() +{ + Serial.begin(115200); + Serial.println(__FILE__); + Serial.print("PRINTHELPERS_LIB_VERSION: "); + Serial.println(PRINTHELPERS_LIB_VERSION); + Serial.println(); + + delay(100); + start = micros(); + char *p = fraction(-PI); + stop = micros(); + Serial.print("TIME: \t"); + Serial.println(stop - start); + Serial.println(p); + delay(100); + + double n = 0.500; + for (int i = 0; i <= 1000; i++) + { + Serial.print(i); + Serial.print("\t"); + Serial.print(n, 3); + Serial.print("\t"); + Serial.print(fraction(n)); + Serial.println(); + n += 0.001; + } + Serial.println(); + + Serial.println("\ndone..."); +} + + +void loop() +{ +} + + +// -- END OF FILE -- diff --git a/examples/print_fractions_denum/print_fractions_denum.ino b/examples/print_fractions_denum/print_fractions_denum.ino new file mode 100644 index 0000000..ac6de00 --- /dev/null +++ b/examples/print_fractions_denum/print_fractions_denum.ino @@ -0,0 +1,63 @@ +// +// FILE: print_fractions_denum.ino +// AUTHOR: Rob Tillaart +// PURPOSE: demo +// URL: https://github.com/RobTillaart/printHelpers + + +#include "printHelpers.h" + +uint32_t start, stop, duration; + + +void setup() +{ + Serial.begin(115200); + Serial.println(__FILE__); + Serial.print("PRINTHELPERS_LIB_VERSION: "); + Serial.println(PRINTHELPERS_LIB_VERSION); + Serial.println(); + + delay(100); + start = micros(); + char *p = fraction(PI, 65536); // 2^16 + stop = micros(); + Serial.print("TIME: \t"); + Serial.println(stop - start); + Serial.println(p); + delay(100); + + start = micros(); + p = fraction(EULER, 65536); // 2^16 + stop = micros(); + Serial.print("TIME: \t"); + Serial.println(stop - start); + Serial.println(p); + Serial.println(); + delay(100); + + // notice most fractions will occur 3 or 4 times as denom + // is smaller than the step size of n. + double n = 0.500; + for (int i = 0; i <= 1000; i++) + { + Serial.print(i); + Serial.print("\t"); + Serial.print(n, 3); + Serial.print("\t"); + Serial.print(fraction(n, 256)); + Serial.println(); + n += 0.001; + } + Serial.println(); + + Serial.println("\ndone..."); +} + + +void loop() +{ +} + + +// -- END OF FILE -- diff --git a/examples/print_hex_bin/print_hex_bin.ino b/examples/print_hex_bin/print_hex_bin.ino index 2dd12c2..cc636d5 100644 --- a/examples/print_hex_bin/print_hex_bin.ino +++ b/examples/print_hex_bin/print_hex_bin.ino @@ -16,8 +16,8 @@ void setup() Serial.begin(115200); Serial.println(); Serial.println(__FILE__); - Serial.print("PRINTHELPERS_VERSION: "); - Serial.println(PRINTHELPERS_VERSION); + Serial.print("PRINTHELPERS_LIB_VERSION: "); + Serial.println(PRINTHELPERS_LIB_VERSION); Serial.println(); Serial.println(); diff --git a/examples/print_inch_feet/print_inch_feet.ino b/examples/print_inch_feet/print_inch_feet.ino index ccd624f..0764c27 100644 --- a/examples/print_inch_feet/print_inch_feet.ino +++ b/examples/print_inch_feet/print_inch_feet.ino @@ -12,8 +12,8 @@ void setup() { Serial.begin(115200); Serial.println(__FILE__); - Serial.print("PRINTHELPERS_VERSION: "); - Serial.println(PRINTHELPERS_VERSION); + Serial.print("PRINTHELPERS_LIB_VERSION: "); + Serial.println(PRINTHELPERS_LIB_VERSION); Serial.println(); // test some random values diff --git a/examples/print_performance/print_performance.ino b/examples/print_performance/print_performance.ino index 3df58cb..ebb895f 100644 --- a/examples/print_performance/print_performance.ino +++ b/examples/print_performance/print_performance.ino @@ -17,8 +17,8 @@ void setup() { Serial.begin(115200); Serial.println(__FILE__); - Serial.print("PRINTHELPERS_VERSION: "); - Serial.println(PRINTHELPERS_VERSION); + Serial.print("PRINTHELPERS_LIB_VERSION: "); + Serial.println(PRINTHELPERS_LIB_VERSION); Serial.println(); Serial.println(sizeof(float)); diff --git a/examples/print_sci/print_sci.ino b/examples/print_sci/print_sci.ino index cbd0fab..ffafd8f 100644 --- a/examples/print_sci/print_sci.ino +++ b/examples/print_sci/print_sci.ino @@ -12,8 +12,8 @@ void setup() { Serial.begin(115200); Serial.println(__FILE__); - Serial.print("PRINTHELPERS_VERSION: "); - Serial.println(PRINTHELPERS_VERSION); + Serial.print("PRINTHELPERS_LIB_VERSION: "); + Serial.println(PRINTHELPERS_LIB_VERSION); Serial.println(); Serial.println(sizeof(float)); diff --git a/examples/print_sci_experimental/print_sci_experimental.ino b/examples/print_sci_experimental/print_sci_experimental.ino index 05b8b06..3750247 100644 --- a/examples/print_sci_experimental/print_sci_experimental.ino +++ b/examples/print_sci_experimental/print_sci_experimental.ino @@ -14,22 +14,32 @@ void setup() { Serial.begin(115200); Serial.println(__FILE__); - Serial.print("PRINTHELPERS_VERSION: "); - Serial.println(PRINTHELPERS_VERSION); + Serial.print("PRINTHELPERS_LIB_VERSION: "); + Serial.println(PRINTHELPERS_LIB_VERSION); Serial.println(); + Serial.print("FLOAT: \t"); Serial.println(sizeof(float)); + Serial.print("DOUBLE: \t"); Serial.println(sizeof(double)); Serial.println(); double n = 6.072832E+37; + // reference + Serial.println("TEXT: \t6.072832E+37"); + Serial.print("PRINT: \t"); + Serial.println(n); + Serial.println(); + + delay(100); start = micros(); char * b = sci(n, 6); stop = micros(); - Serial.print("TIME: "); + Serial.print("TIME: \t"); Serial.println(stop - start); + Serial.print("SCI: \t"); Serial.println(b); delay(100); // duration = stop - start; @@ -37,9 +47,10 @@ void setup() start = micros(); b = newsci(n, 6); stop = micros(); - Serial.print("TIME: "); + Serial.print("TIME: \t"); Serial.println(stop - start); // Serial.println((stop - start) *1.0 / duration); + Serial.print("NEWSCI: \t"); Serial.println(b); delay(100); diff --git a/examples/print_toRoman/print_toRoman.ino b/examples/print_toRoman/print_toRoman.ino index ab437ac..e0ef821 100644 --- a/examples/print_toRoman/print_toRoman.ino +++ b/examples/print_toRoman/print_toRoman.ino @@ -14,8 +14,8 @@ void setup() { Serial.begin(115200); Serial.println(__FILE__); - Serial.print("PRINTHELPERS_VERSION: "); - Serial.println(PRINTHELPERS_VERSION); + Serial.print("PRINTHELPERS_LIB_VERSION: "); + Serial.println(PRINTHELPERS_LIB_VERSION); Serial.println(); Serial.println(sizeof(float)); diff --git a/examples/sci_test/sci_test.ino b/examples/sci_test/sci_test.ino index 3a8d008..118e586 100644 --- a/examples/sci_test/sci_test.ino +++ b/examples/sci_test/sci_test.ino @@ -14,8 +14,8 @@ void setup() { Serial.begin(115200); Serial.println(__FILE__); - Serial.print("PRINTHELPERS_VERSION: "); - Serial.println(PRINTHELPERS_VERSION); + Serial.print("PRINTHELPERS_LIB_VERSION: "); + Serial.println(PRINTHELPERS_LIB_VERSION); Serial.println(); test1(); diff --git a/examples/toBytes/toBytes.ino b/examples/toBytes/toBytes.ino index c04c564..7f76972 100644 --- a/examples/toBytes/toBytes.ino +++ b/examples/toBytes/toBytes.ino @@ -14,8 +14,8 @@ void setup() { Serial.begin(115200); Serial.println(__FILE__); - Serial.print("PRINTHELPERS_VERSION: "); - Serial.println(PRINTHELPERS_VERSION); + Serial.print("PRINTHELPERS_LIB_VERSION: "); + Serial.println(PRINTHELPERS_LIB_VERSION); Serial.println(); delay(100); diff --git a/keywords.txt b/keywords.txt index 27872f8..e846f64 100644 --- a/keywords.txt +++ b/keywords.txt @@ -22,9 +22,11 @@ printFeet KEYWORD2 csi KEYWORD2 +fraction KEYWORD2 + # Constants (LITERAL1) -PRINTHELPERS_VERSION LITERAL1 +PRINTHELPERS_LIB_VERSION LITERAL1 PRINTBUFFERSIZE LITERAL1 diff --git a/library.json b/library.json index f2c5a71..54cdd59 100644 --- a/library.json +++ b/library.json @@ -1,7 +1,7 @@ { "name": "printHelpers", "keywords": "Convert,int64,uint64,print,scientific,notation,toBytes,HEX,BIN,Roman", - "description": "Arduino library to help printing. int64 and uint64 support base 10 (DEC) and 16 (HEX). Scientific notation of floats. Feet and inch. Comma separated integers.", + "description": "Arduino library to help printing. int64 and uint64 support base 10 (DEC) and 16 (HEX). Scientific notation of floats. Feet and inch. Comma separated integers. Fractions.", "authors": [ { @@ -15,7 +15,7 @@ "type": "git", "url": "https://github.com/RobTillaart/printHelpers" }, - "version": "0.4.4", + "version": "0.4.5", "license": "MIT", "frameworks": "*", "platforms": "*", diff --git a/library.properties b/library.properties index 13bfb0a..b6ea908 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=printHelpers -version=0.4.4 +version=0.4.5 author=Rob Tillaart maintainer=Rob Tillaart sentence=Arduino library to help formatting data for printing. 64 bit integers (base 10 and 16). Engineering and scientific notation. -paragraph=Supports 64 bit integers (base 10 and 16). Engineering and scientific notation. toBytes() for KB MB, HEX and BIN, Roman numbers. Feet and inch. Comma separated integers. +paragraph=Supports 64 bit integers (base 10 and 16). Engineering and scientific notation. toBytes() for KB MB, HEX and BIN, Roman numbers. Feet and inch. Comma separated integers. Fractions. category=Other url=https://github.com/RobTillaart/printHelpers architectures=* diff --git a/printHelpers.cpp b/printHelpers.cpp index 7769128..beefbac 100644 --- a/printHelpers.cpp +++ b/printHelpers.cpp @@ -2,7 +2,7 @@ // FILE: printHelpers.cpp // AUTHOR: Rob Tillaart // DATE: 2018-01-21 -// VERSION: 0.4.4 +// VERSION: 0.4.5 // PURPOSE: Arduino library to help formatting for printing. // URL: https://github.com/RobTillaart/printHelpers @@ -134,14 +134,14 @@ char * print64(uint64_t value, uint8_t base) // typical buffer size for 8 byte double is 22 bytes // 15 bytes mantissa, sign dot E-xxx -// em = exponentMultiple. +// em = exponentMultiple == step size exponent. char * scieng(double value, uint8_t decimals, uint8_t em) { char * buffer = __printbuffer; int exponent = 0; uint8_t pos = 0; - double e1 = 10; - double e2 = 0.1; + double e1 = 10; // exponent step > 1 + double e2 = 0.1; // exponent step < 1 // scale to multiples of em. for (uint8_t i = 1; i < em; i++) @@ -535,7 +535,7 @@ char * printFeet(float feet) // Comma Separated Integers // Experimental // -// TODO +// TODO // - merge if possible 64-32 signed-unsigned // - performance (use divmod10?) // @@ -543,8 +543,8 @@ char * csi(int64_t value) { char * buffer = __printbuffer; int index = 0; - bool neg = (value < 0); - if (neg) + bool negative = (value < 0); + if (negative) { value = -value; } @@ -560,7 +560,7 @@ char * csi(int64_t value) buffer[index++] = ','; } } - if (neg) + if (negative) { buffer[index++] = '-'; } @@ -578,8 +578,8 @@ char * csi(int32_t value) { char * buffer = __printbuffer; int index = 0; - bool neg = (value < 0); - if (neg) + bool negative = (value < 0); + if (negative) { value = -value; } @@ -595,7 +595,7 @@ char * csi(int32_t value) buffer[index++] = ','; } } - if (neg) + if (negative) { buffer[index++] = '-'; } @@ -677,13 +677,186 @@ char * csi(uint16_t value) return csi((uint32_t)value); } - char * csi(uint8_t value) { return csi((uint32_t)value); } +//////////////////////////////////////////////////////////// +// +// Fraction +// Experimental +// Based upon Fraction library -> fractionize() +// +char * fraction(double value) +{ + static char buffer[20]; + if (isnan(value)) + { + strcpy(buffer, "nan"); + return buffer; + } + if (isinf(value)) + { + if (value < 0) strcpy(buffer, "-inf"); + strcpy(buffer, "inf"); + return buffer; + } + bool negative = false; + if (value < 0) + { + negative = true; + value = -value; + } + + float whole = 0; + if (value > 1) + { + whole = (uint32_t)value; + value -= whole; + } + + // find nearest fraction + float Precision = 0.000001; + + // low = (0,1), high = (1,1) + int32_t lowN = 0; + int32_t lowD = 1; + int32_t highN = 1; + int32_t highD = 1; + + // max 100 iterations + for (int i = 0; i < 100; ++i) + { + float testLow = lowD * value - lowN; + float testHigh = highN - highD * value; + if (testHigh < Precision * highD) + break; // high is answer + + if (testLow < Precision * lowD) + { // low is answer + highD = lowD; + highN = lowN; + break; + } + if (i & 1) + { // odd step: add multiple of low to high + float test = testHigh / testLow; + int32_t count = (int32_t)test; // "N" + int32_t n = (count + 1) * lowN + highN; + int32_t d = (count + 1) * lowD + highD; + if ((n > 0x8000) || (d > 0x10000)) // 0x8000 0x10000 + break; + highN = n - lowN; + highD = d - lowD; + lowN = n; + lowD = d; + } + else + { // even step: add multiple of high to low + float test = testLow / testHigh; + int32_t count = (int32_t)test; // "N" + int32_t n = lowN + (count + 1) * highN; + int32_t d = lowD + (count + 1) * highD; + if ((n > 0x10000) || (d > 0x10000)) // 0x10000 0x10000 + break; + lowN = n - highN; + lowD = d - highD; + highN = n; + highD = d; + } + } + + // produce the string + if (whole > 0) highN += whole * highD; + if (negative) + { + #if defined(ESP32) + // ESP32 does not support %ld or ltoa() + sprintf(buffer, "-%d/%d", highN, highD); + #else + sprintf(buffer, "%ld/%ld", highN, highD); + #endif + } + else + { + #if defined(ESP32) + // ESP32 does not support %ld or ltoa() + sprintf(buffer, "-%d/%d", highN, highD); + #else + sprintf(buffer, "-%ld/%ld", highN, highD); + #endif + } + return buffer; +} + + +char * fraction(double value, uint32_t denum) +{ + static char buffer[20]; + if (isnan(value)) + { + strcpy(buffer, "nan"); + return buffer; + } + if (isinf(value)) + { + if (value < 0) strcpy(buffer, "-inf"); + strcpy(buffer, "inf"); + return buffer; + } + bool negative = false; + if (value < 0) + { + negative = true; + value = -value; + } + + float whole = 0; + if (value > 1) + { + whole = (uint32_t)value; + value -= whole; + } + + uint32_t num = round(value * denum); + // find GCD + uint32_t a = num; + uint32_t b = denum; + while ( a != 0 ) + { + uint32_t c = a; + a = b % a; + b = c; + } + // simplify + denum /= b; + num /= b; + + // produce the string + if (whole > 0) num += whole * denum; + if (negative) + { + #if defined(ESP32) + // ESP32 does not support %ld or ltoa() + sprintf(buffer, "-%d/%d", num, denum); + #else + sprintf(buffer, "-%ld/%ld", num, denum); + #endif + } + else + { + #if defined(ESP32) + // ESP32 does not support %ld or ltoa() + sprintf(buffer, "%d/%d", num, denum); + #else + sprintf(buffer, "%ld/%ld", num, denum); + #endif + } + return buffer; +} + // -- END OF FILE -- diff --git a/printHelpers.h b/printHelpers.h index fb68d2c..0011838 100644 --- a/printHelpers.h +++ b/printHelpers.h @@ -3,7 +3,7 @@ // FILE: printHelpers.h // AUTHOR: Rob Tillaart // DATE: 2018-01-21 -// VERSION: 0.4.4 +// VERSION: 0.4.5 // PURPOSE: Arduino library to help formatting for printing. // URL: https://github.com/RobTillaart/printHelpers @@ -12,7 +12,9 @@ #include "stdlib.h" -#define PRINTHELPERS_VERSION (F("0.4.4")) +#define PRINTHELPERS_LIB_VERSION (F("0.4.5")) +// PRINTHELPERS_VERSION is obsolete in future +#define PRINTHELPERS_VERSION (F("0.4.5")) // global buffer used by all functions so no static buffer in every function @@ -132,6 +134,15 @@ char * csi(uint16_t n); char * csi(uint8_t n); +//////////////////////////////////////////////////////////// +// +// Fraction +// Experimental +// Based upon Fraction library -> fractionize() +// +char * fraction(double value); +char * fraction(double value, uint32_t denom); + // -- END OF FILE -- diff --git a/test/unit_test_001.cpp b/test/unit_test_001.cpp index fc7670c..48f1451 100644 --- a/test/unit_test_001.cpp +++ b/test/unit_test_001.cpp @@ -39,7 +39,7 @@ unittest_setup() { - fprintf(stderr, "PRINTHELPERS_VERSION: %s\n", (char *) PRINTHELPERS_VERSION); + fprintf(stderr, "PRINTHELPERS_LIB_VERSION: %s\n", (char *) PRINTHELPERS_LIB_VERSION); }