1

I got an MCU that sends to a LCD a series of numbers to display. I need to send their ASCII coresponding value for it to work right. Those numbers are float types, no more than 2 digits precision for the fraction part.

The thing is that I need a function that will parse the float, get its digits without reversing them, convert them to ASCII, and put the ASCII code for comma at its location in the number.

Example:

number = 123.45

result = 49 50 51 44 52 53 //Where 44 is the ASCII code of comma

Any suggestions for writing a function that can do this? Would be awesome if the result variable would retain a single ASCII code at a time since I need to call the WriteToLCD function for each symbol at a time.

Also I dont really have access to library functions like sprintf, itoa, ftoa etc.

Some code sample:

void LCD_print(int number)
{
        char aux;
        int naux=0,maux=0;
        while(number!=0)
        {
            maux=number%10;
            naux=naux*10+maux;
            number=number/10;
        }
        while(naux)
        {
        aux=naux%10+'0';
        WriteToLCD(aux);
        naux=naux/10;
        }
}

I got this function from somewhere off the internet, it does a good job at converting integers to ASCII, but I need the argument to become a float and add the dot's ASCII code to the result.

Main will look something like this:

void main()
{
//other stuff
float number = ReadValueFromSensor();
LCD_print(number);
}
DragonBase
  • 17
  • 7
  • "Those numbers are float types, no more than 2 digits precision for the fraction part." -- Is that floating point number stored in one of the [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) formats or some other format? – Andreas Wenzel Dec 06 '20 at 20:50
  • A minor point ... 46 (decimal) is the ASCII code for `.` [_not_ 44 for `,`]. – Craig Estey Dec 06 '20 at 20:54
  • When I said that no more than 2 digits precision, I meant that I wont need to send more than 2 digits to the LCD, the rest can be ignored. Thought maybe it could help you guys in your algorithms Edit: Thanks Craig, I'll edit the post – DragonBase Dec 06 '20 at 20:57
  • 1
    Could you show _some_ code? At least function declaration? How it will be used _in code_? `number = 123.45` How is the number _represented_ in C? Is it a `char[]` array or a `float` variable (or a `_Accum` variable :) ? What MCU are you using? Do you have hardware float support? `I dont really have access to library functions like sprintf` why? As a general advise, consider not using floats if your MCU doesn't have hardware float support - instead stick to integers. `Any suggestions for writing a function that can do this?` is too broad. What exactly are you having problem with? – KamilCuk Dec 06 '20 at 21:01
  • If you only need two digits of precision after the decimal point, you could multiply the floating point value by 100 and then cast it to an `int` in C. That way, you will have an integer that is 100 times higher than the desired value. All you must do afterwards is print the individual digits and insert the decimal point at the correct location. However, this will cause rounding towards zero, which may not be desired. – Andreas Wenzel Dec 06 '20 at 21:08
  • It would be way simpler if you would specify what exactly environment are you using, what MCU are using, what standard library are you programming with, what sensor and what value are you reading and where are you doing to display it. So what research did you do? Is the following: https://www.geeksforgeeks.org/convert-floating-point-number-string/ not enough? It's a ready algorithm right there. `get its digits without reversing them` why? Do you know that [floating points are not accurate](https://stackoverflow.com/questions/21895756/why-are-floating-point-numbers-inaccurate)? – KamilCuk Dec 06 '20 at 21:12
  • I dont know how all of this is relevant, but I will go ahead and answer. I have a temperature sensor, I use a STM32Discovery MCU, using Eclipse. Periodically I need to read from the sensor the temperature it registers. AD converter will get me a value which will be stored in a float variable. With that float variable, I call the DisplayOnLCD function. I need to prepare the data and send each digit's ASCII value for it to work. ITOA and all mainstream functions dont work, so that's why I ask here for an alternative. WriteToLCD function requires a char type as argument. – DragonBase Dec 06 '20 at 21:16
  • Honestly it does not matter if it's losing precision, it's a temperature sensor, no one will die if it has a marginal error of 0.01. What I need is an algorithm that does the conversion I described above – DragonBase Dec 06 '20 at 21:24
  • @KamilCuk Dont get mad now, but you should try being more like the fellers below that provided an answer without asking redundant questions. – DragonBase Dec 06 '20 at 21:41

2 Answers2

1

need a function that will parse the float, get its digits without reversing them, convert them to ASCII, and put the ASCII code for comma at its location in the number.

Scale the value by 100.0 to only work with whole number values and afford the extra precision of double in making the product.

Use integer like code, where the least significant digit is first found using fmod(), then the next most, etc. saving the result in a worst case sized buffer.

Lastly print in reverse order.

Extra code needed for NaN and infinity.

#include <assert.h>
#include <float.h>
#include <stdio.h>

//                    -     up to 38 digits      .  00  \0
#define PRINT_FLT_SZ (1 + (FLT_MAX_10_EXP + 1) + 1 + 2 + 1)

void print_flt(float f) {
  assert(isfinite(f));
  double x = fabs(round(f * 100.0));
  char buf[PRINT_FLT_SZ];
  char *p = buf + sizeof buf - 1;
  *p = '\0';
  int digit_count = 0;

  do {
    double digit = fmod(x, 10.0);
    x = (x - digit)/10;
    *(--p) = (char)(digit + '0');
    digit_count++;
    if (digit_count == 2) {
      *(--p) = '.';
    }
  } while (x || digit_count <= 2);

  if (signbit(f)) {
    *(--p) = ',';  // or '.'
  }

  while (*p) {
    // WriteToLCD(*p);
    putchar(*p);
    p++;
  }
  putchar('\n');
}

Usage

int main() {
  print_flt(123.45f);     // OP's test
  print_flt(123.459f);    // round test
  print_flt(-FLT_MAX);    // range test
  print_flt(0.00500001f); // round test
  print_flt(-0.0);        // signed zero test
  //print_flt(-0.0/0.0); // TBD code for NaN and INF
}

Output

123,45
123.46
-340282346638528886604286022844204804240,00
0,01
-0,00
  

For values more than 1014 there may be some additional concerns with x = (x - digit)/10; than warrant more analysis, but something to get OP started.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • I have tested your code and, while it looks a little bit complicated (for me), it works very well. I have modified a bit to return ASCII values and it does exactly what I need it to do. I also appreciate that you took precautions for individual cases. – DragonBase Dec 06 '20 at 21:59
  • @DragonBase For the larger problem, I would have used a integer only based solution without any `float`. To me, using `float` complicates things. To each his own. – chux - Reinstate Monica Dec 06 '20 at 22:05
0

Simplest solution without using any standard functions or libraries, neither arrays to print in reverse order. Just using simple maths and loops, and ignoring precision errors:

void WriteFloatToLCD(float x) {
    // Prints minus sign if number is negative
    // Then converts it to positive for normal processing below
    if (x < 0) {
        x *= -1;
        WriteToLCD('-');
    }

    // Truncate float to int (gets the whole part)
    int whole = (int)x;

    // Difference between whole part and x is the decimal part, truncated
    int decimal = (int)(x * 100 - whole * 100);

    // "div" will be 10^(number of digits from the whole part) / 10
    int div = 1;
    while (div <= whole / 10) {
        div *= 10;
    }

    // Convert and print each digit from the whole part
    while (div > 0) {
        WriteToLCD((whole / div) + '0');
        whole %= div;
        div /= 10;
    }

    // Print the decimal separator
    WriteToLCD('.');

    // Convert and print each digit from the decimal part
    for (div = 10; div > 0; div /= 10) {
        WriteToLCD((decimal / div) + '0');
        decimal %= div;
    }
}

void main(void) {
    float number = ReadValueFromSensor();
    WriteFloatToLCD(number);
}

You can implement support to NaN by comparing x != x and printing 'NaN' if necessary. Further modifications should be done if you want to support better floating precision or if x > INT_MAX/100 as noted in the comments.

Toribio
  • 3,963
  • 3
  • 34
  • 48