Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: Pseudo floating point numbers using integer data types #30

Open
hattesen opened this issue Nov 24, 2018 · 9 comments
Open

Comments

@hattesen
Copy link

Proposed feature

I propose, that the printf precision option be made to also work with pseudo floating point numbers, represented by an integer type with an implicit (fixed) decimal exponent.

Example:

The value 3.14 could be represented (with two decimals precision) by an integer value of 314 (assuming an implicit decimal exponent of -2): 314 * 10^(-2) = 3.14

Note, that precision = -exponent

Current printf formatting design.

Proposed behavior

Using the standard "f" specifier, with an implicit float conversion: int_value * 10^(-precision)

printf("Pi = %.2f", 314); // result: "pi = 3.14"
printf("Amount $%.2f", 23456); // result: "Amount $234.56

It may be a good idea, to even support negative precision specifiers (positive decimal exponents):

printf("Rounded %.-2f", (byte) 123); // result: "Rounded 12300"

For backwards compatibility, it might be better to use a new "integer with decimal exponent" specifier, rather than repurposing the "f" specifier with an integer argument.

Motivation

On memory and CPU resource restricted MCUs like the AVRs used on the Arduino, it is common practice to avoid using floats to represent floating point values, like temperature, but instead use an integer data type, like an int, representing tenth of degrees. So a temperature of 45.6 would be represented by an integer value of 456, with an implicit decimal exponent (10^-2). Similarly, a monetary amount, e.g. $234.56 can be represented by an integer value, representing the number of cents: 23456, with an implicit decimal exponent (10^-2)

Such an "external/fixed 10^n exponent" implementation have several advantages over using native floating point data types:

  • Low memory footprint (e.g. values -10.0 .. +10.0 with one decimal precision, or -1000 ..+1000 with -1 decimal precision, can be stored in a single byte).
  • Calculations are much faster (less CPU cycles).
  • No rounding issues when converting values to/from decimal (logging, displaying or inputting), as any number, with the chosen precision, can be accurately represented in the binary (integer) form.
@scottchiefbaker
Copy link

I understand the desire for this on AVRs, but modifying the f specifier is a fundamental change in printf() behavior and probably best avoided for consistency with other implementations.

I do like the idea however. If we were to create a new modifier specifically for this library that does not conflict with other implementations I would support it. What about W for "wide int"?

@hattesen
Copy link
Author

hattesen commented Feb 3, 2019

I agree that modifying the f specifier can be problematic.

Doesn't matter much what letter is chosen as the specifier for this "integer with decimal exponent" "decimal integer" or "pseudo float", but reusing the relevant modifiers from the f specifier would be ideal for consistency.

@jirkaptr
Copy link

jirkaptr commented Feb 8, 2019

The following solutions may be appropriate:

char*  ltostrqf(long int value, unsigned char prec, char *buff )
{
    char *fmt = "%ld.%Xld";
    fmt[5] = '0' + prec;
    
    long int divisor = 1; 
    while(prec--)
        divisor *= 10;

    sprintf(buff, fmt, value / divisor, abs(value % divisor));
    return buff;
}

Application examples:

 PrintEx serial = Serial;
 ltostrqf(-987654321, 5, qf_buf);
 serial.printf("***%16s***%5s***\n", qf_buf, qf_buf);
 serial.printf("###%16s###\n", ltostrqf(-1234567890, 3, qf_buf));
 Serial.println(ltostrqf(123456789, 5, qf_buf));

Comment:
• The name is chosen to characterize the content ("long -to-string-quasi-float"). The call also corresponds to the similar dtostrf () function with the exception the width parameter..
• Parameter width (or right alignment) is entered in the formating string of printf(), eg. %12s.
• Overflow and formal call rules (parameters) checks are not performed.
• If 0<precision< 5 is applied anytime then the divisor can be int type.
• The function is freely usable independently of the PrintEx library.

@scottchiefbaker
Copy link

I think I found a possible bug:

void loop() {
    char qf_buf[50] = "";

    ltostrqf(-987654321 , 5, qf_buf);
    
    serial.printf("Count up  : %s\n", ltostrqf(123456789, 3, qf_buf));
    serial.printf("Count down: %s\n", ltostrqf(987654321, 3, qf_buf));

    delay(5000);
}

Adding the initial ltostrqf() causes the output on the other two to be wrong. If I comment out the offending line it's correct again.

@scottchiefbaker
Copy link

Also, with warnings enabled I get:

warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
   char *fmt  = "%ld.%Xld";

Chaning it to char fmt[] = "%ld.%Xld"; fixes the warning

@scottchiefbaker
Copy link

The "bug" may be a simple integer overflow. Here is the full sketch. Compare the output after commenting out line #12.

ltostrqf-bug.txt

@jirkaptr
Copy link

jirkaptr commented Feb 8, 2019

Now I understand your problem! You probably did not fix the PrintExtension.h!
Apply a fix as stated in #26 .
I believe that then everything will be OK.

@scottchiefbaker
Copy link

Aha... that would make sense!

@jirkaptr
Copy link

jirkaptr commented Feb 9, 2019

Sorry for errors in ltostrqf(). The original version does not process correctly numbers in the target format range of -0.9999999 to +0.999999.
I attach a corrected version here:
ltostrqf_20190209.txt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants