Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

[PIC] PIC 16F887 > LM335 > Can not display value of more than 1 decimal

Eric_O

Advanced Member level 4
Joined
May 31, 2020
Messages
104
Helped
0
Reputation
0
Reaction score
0
Trophy points
16
Activity points
993
Bonjour,

Would like to display 2 or 3 decimals on LCD.
For example, display 22,5; or 22,50; or 22,500.
But would like to display 22,57; or 22,524 for example.

Code:
// MCU : PIC 16F887
// External clock : Quartz 4 MHz
// LED YELLOW is connected to pin 17 (portc.b2).
// LED GREEN is connected to pin 18 (portc.b3).
// Switch is connected to pin 21 (portd.b2).
// Switch is connected to pin 22 (portd.b3).
// LM35 temperature sensor : pin GND to GND,
//                           pin DQ to pin 5 (AN3),
//                           pin VCC to VCC.

/******************************************************************************/

//#include <math.h>                     // A re tester en décochant dans View > Library Manager > C_Math.
                                        // Fonction floor() dans float_to_ASCII_with_2_decimals_v4

/******************************************************************************/

// LCD module connections :
                                        // VSS (pin  1) -> GND
                                        // VDD (pin  2) -> VCC
                                        // VEE (pin  3) -> middle pin contrast potentiometer
sbit LCD_RS at RB4_bit;                 // RS  (pin  4) -> portB.b4 (pin 37)
// RW not used.                         // RW  (pin  5) -> GND
sbit LCD_EN at RB5_bit;                 // EN  (pin  6) -> portB.b5 (pin 38)
                                        // D0  (pin  7) -> GND
                                        // D1  (pin  8) -> GND
                                        // D2  (pin  9) -> GND
                                        // D3  (pin 10) -> GND
sbit LCD_D4 at RB0_bit;                 // D4  (pin 11) -> portB.b0 (pin 33)
sbit LCD_D5 at RB1_bit;                 // D5  (pin 12) -> portB.b1 (pin 34)
sbit LCD_D6 at RB2_bit;                 // D6  (pin 13) -> portB.b2 (pin 35)
sbit LCD_D7 at RB3_bit;                 // D7  (pin 14) -> portB.b3 (pin 36)
                                        //     (pin 15) -> GND
                                        //     (pin 16) -> GND

sbit LCD_RS_Direction at TRISB4_bit;
sbit LCD_EN_Direction at TRISB5_bit;
sbit LCD_D4_Direction at TRISB0_bit;
sbit LCD_D5_Direction at TRISB1_bit;
sbit LCD_D6_Direction at TRISB2_bit;
sbit LCD_D7_Direction at TRISB3_bit;

/******************************************************************************/

#define CMD 0
#define DATA 1
#define LCD_PORT PORTB
#define LCD_COLUMNS 16

/******************************************************************************/

// Declaration of variables :

char texte[64];                         // 0 to 255
                                        // Variables ci-dessous sorties du main() pour les déclarer en global.
                                        // Le PIC 16F a moins de mémoire RAM et ROM que le PIC 18F.
unsigned char *pointeur_de_char;        // Déclaration d'un pointeur (*) de char "pointeur_de_char".

unsigned char i;

char ROW[] = {0x80, 0xC0};

/******************************************************************************/

// Routines :

void ADC_initialization()
{
 ADCON0 = 0b01000001;              // b0 = 1 (ADON) : le convertisseur A/N interne est activé.
                                   // b1 (GO/DONE)  : A/D Conversion Status bit.
                                   // b2, b3, b4, b5 (CHS0, CHS1, CHS2, CHS3) : Analog Channel Select bits.
                                   // b6 = 1 (ADSC0), b7 = 0 (ADSC1)          : fosc / 8 = 4 MHz / 8 = 0,5 MHz = 500 KHz
                                   //                                           tosc = 1 / fosc = 1 / 500 KHz = 0,002 mS = 2 uS.
                                   // 2 uS est le temps de conversion A/D d'un bit, TAD.
                                   // Pour une conversion totale sur 10 bits il faut 12 TAD.
                                   // Pour une conversion correcte il faut que TAD = 1,6 uS au minimum.
                                   // TAD = 2 uS / bit. 12 TAD = 24 uS pour 10 bits.

 ADCON1 = 0b10000000;              // b0 = b1 = b2 = b3 = 0 : Unimplemented. Read as '0'.
                                   // b4 = 0 (VCFG0)        : Voltage reference VDD.
                                   // b5 = 0 (VCFG1)        : Voltage reference VSS.
                                   // b6 = 0                : Unimplemented. Read as '0'.
                                   // b7 = 1                : ADFM (A/D result ForMat) = 1 (format justifié à droite).
                                   // Explication :
                                   //          ADRESH        |         ADRESL
                                   // b7|b6|b5|b4|b3|b2|b1|b0|b7|b6|b5|b4|b3|b2|b1|b0
                                   //  0| 0| 0| 0| 0| 0| r| r| r| r| r| r| r| r| r| r
                                   //    les 6 MSB = 0 |   résultat r sur 10 bits
}

unsigned int ADC_read(unsigned char channel)
// Si message d'erreur "'ADC_read' Identifier redefined" pendant compilation, dans Library Manager décocher ADC_Read.
// ADC_read est ma propre routine écrite pour apprentissage. Je n'utilise pas ADC_Read de la librairie Mikroelektronika.
{
 static unsigned int k;            // Variable locale (static).
 ADCON0 = ADCON0 & 0b11000011;     // ADCON0 = 01000001
                                   //        & 11000011 (masque)
                                   // ADCON0 = 01000001
                                   // masque :
                                   // b0 = 1 (ADON)                               : pour prendre en compte l'état du convertisseur A/D, en service (0) ou à l'arrêt (1).
                                   // b1 = 1 (GO/DONE)                            : pour prendre en compte l'état de la conversion A/D, en cours (1) ou terminée (0).
                                   // b2 = b3 = b4 = b5 = 0 (CHS0 CHS1 CHS2 CHS3) : pour re initialiser au premier canal de conversion, le canal 0 (CHS0 = CHS1 = CHS2 = CHS3 = 0).
                                   // b6 = b7 = 1 (ADSC0 ADSC1)                   : pour prendre en compte la valeur du diviseur, de la vitesse de conversion, choisi.
 channel = channel << 2;           // Décalage channel de 2 bits à gauche pour placer la valeur de channel dans bits b2 b3 b4 b5 (CHS0 CHS1 CHS2 CHS3).
 ADCON0 = ADCON0 | channel;        // OU logique bit à bit entre ADCON0 et channel.
 // ou ADCON0 |= channel;
 Delay_ms(2);                      // Délais de 2 mS minimum. >>> A ajuster si besoin. <<<
 ADCON0.GO_DONE = 1;               // Déclenchement de la conversion A/N.
                                   // ou ADCON0.b2 = 1;
 _asm NOP;                         // Recommandé par Microchip.
 while (ADCON0.GO_DONE == 1);      // Attendre que le bit GO.DONE passe à 0.
 {
  k = ADRESL + (ADRESH * 256);     // XXX A REVOIR XXX
  //ou k = ADRESL + (ADRESH << 8);
 }
 return(k);
}

void LCD_E_Pulse(void)
{
 LCD_EN = 1;
 Delay_us(8);                                                     // Delay between 5 uS and 10 uS.
 LCD_EN = 0;
 Delay_us(500);                                                   // Delay between 5 uS and 1000 uS (1 mS).
}

void LCD_Write(unsigned char cmd_or_data, unsigned char byte)
{
 unsigned char low, high;
 high = (byte >> 4) & 0x0F;                                       // b7 b6 b5 b4 of byte are shifted to b3 b2 b1 b0 of byte and saved in high.
 low = byte & 0x0F;                                               // b3 b2 b1 b0 of byte are saved in low.
 LCD_PORT = high;                                                 // Send higher nibble of byte to the LCD.
 LCD_RS = cmd_or_data;
 LCD_E_Pulse();
 LCD_PORT = low;                                                  // Send lower nibble of byte to the LCD.
 LCD_RS = cmd_or_data;
 LCD_E_Pulse();
}

void StrConstRamCpy(unsigned char *dest, const code char *source) // Copie le texte de la FLASH ROM vers la RAM.
{
 while (*source)*dest ++ = *source ++;
 *dest = 0;                                                       // Terminateur "0" fin de chaine de caractère.
}

void LCD_Write_String(char *msg)                                  // Variante avec pointeur msg non modifié.
{
 int k;
 k = 0;
 while(*(msg + k) > 0)
 {
  LCD_Write(DATA, (*(msg + k)));                                  // Data pointée par (msg + k).
  k++;
  if (k == LCD_COLUMNS) break;                                    // Si k = 16 sortie de la boucle while ...
 }
}

void LCD_Write_String_At(char line, char column, char *msg)
{
 LCD_Write(CMD, ROW[line]|(column & 0x0F));                       // Print message on desired line and desired column.
 LCD_Write_String(msg);                                           // OK.
}

void print_float_v11(char *flt, long number, char decimals)
{
 if (number < 0)
    {
     number = - number;
     *(flt) = '-';
    }
    else
       {
        *(flt) = ' ';
       }

 if (decimals == 0)
    {
     *(flt + 1) = ' ';
     *(flt + 2) = number / 10000 + '0';
     *(flt + 3) = ((number % 10000) / 1000) + '0';
     *(flt + 4) = ((number % 1000) / 100) + '0';
     *(flt + 5) = ((number % 100) / 10) + '0';
     *(flt + 6) = (number % 10) + '0';
     *(flt + 7) = ' ';
     *(flt + 8) = ' ';
     *(flt + 9) = ' ';
     *(flt + 10) = ' ';
     *(flt + 11) = ' ';
     *(flt + 12) = ' ';
    }
 if (decimals == 1)
    {
     *(flt + 1) = ' ';
     *(flt + 2) = number / 10000 + '0';
     *(flt + 3) = ((number % 10000) / 1000) + '0';
     *(flt + 4) = ((number % 1000) / 100) + '0';
     *(flt + 5) = ((number % 100) / 10) + '0';
     *(flt + 6) = ',';
     *(flt + 7) = (number % 10) + '0';
     *(flt + 8) = ' ';
     *(flt + 9) = ' ';
     *(flt + 10) = ' ';
     *(flt + 11) = ' ';
     *(flt + 12) = ' ';
    }
 if (decimals == 2)
    {
     *(flt + 1) = ' ';
     *(flt + 2) = number / 10000 + '0';
     *(flt + 3) = ((number % 10000) / 1000) + '0';
     *(flt + 4) = ((number % 1000) / 100) + '0';
     *(flt + 5) = ((number % 100) / 10) + '0';;
     *(flt + 6) = ',';
     *(flt + 7) = (number % 10) + '0';
     *(flt + 8) = '0';
     *(flt + 9) = ' ';
     *(flt + 10) = ' ';
     *(flt + 11) = ' ';
     *(flt + 12) = ' ';
    }
 if (decimals > 2)
    {
     *(flt + 1) = ' ';
     *(flt + 2) = number / 10000 + '0';
     *(flt + 3) = ((number % 10000) / 1000) + '0';
     *(flt + 4) = ((number % 1000) / 100) + '0';
     *(flt + 5) = ((number % 100) / 10) + '0';;
     *(flt + 6) = ',';
     *(flt + 7) = (number % 10) + '0';
     *(flt + 8) = '0';
     *(flt + 9) = '0';
     *(flt + 10) = ' ';
     *(flt + 11) = ' ';
     *(flt + 12) = ' ';
    }

 //i = 0;                                                         // OK.
 i = 2;                                                           // OK.

 while(flt[i] != ',')
 {
  if (flt[i] == '0')
     {
      portc.b3 = 1;                                               // Green LED ON.
      flt[i] = ' ';
      if (flt[i - 2] == '-')
         {
          flt[i - 2] = ' ';
          flt[i - 1] = '-';
         }
      if (flt[i + 1] == ',')
         {
          portc.b2 = 1;                                           // Yellow LED ON.
          flt[i] = '0';
         }
     }
     else
        {
         break;
        }
  i++;
 }
}

void LCD_Init_v4 (void)
{
 Delay_ms(15);                                                    // LCD power ON initialization time >= 15 mS.
 LCD_Write(CMD, 0x30);                                            // 4 datas bits > Initialization of LCD with nibble method (4 datas bits).
 LCD_Write(CMD, 0x02);                                            // 4 datas bits > Initialization of LCD with nibble method (4 datas bits).
 LCD_Write(CMD, 0x28);                                            // 4 datas bits > 2 lines display, 5 × 8 dot character font.
 LCD_Write(CMD, 0x0C);                                            // 4 datas bits > Display ON. Cursor OFF.
 LCD_Write(CMD, 0x06);                                            // 4 datas bits > Auto increment cursor.
 LCD_Write(CMD, 0x01);                                            // 4 datas bits > Clear display.
 Delay_ms(1);                                                     // Ajustable ... (indispensable, sinon affichage erratique à la mise sous tension)
}

/******************************************************************************/

void main()
{
 PORTB = 0;                                                 // Initialisation du PORT B à 0.
 PORTC = 0;                                                 // Initialisation du PORT C à 0.
 PORTD = 0;                                                 // Initialisation du PORT D à 0.

 ANSEL = 0b00001000;                                        // b3 = 1 (ANS3) : sets pin 5 (AN3) as analog input.
 ANSELH = 0b00000000;

 TRISB = 0b00000000;                                        // PORT B : b0 à b7 configurés en sortie.
 TRISC = 0b00000000;                                        // PORT C : b0 à b7 configurés en sortie.
 TRISD = 0b00000000;                                        // PORT D : b0 à b7 configurés en sortie.

 C1ON_bit = 0;                                              // CMC1CON register > b7 > C1ON bit = 0 > Disable comparator 1.
 C2ON_bit = 0;                                              // CMC2CON register > b7 > C1ON bit = 0 > Disable comparator 2.

 SCS_bit = 0;                                               // OSCCON register > b0 > SCS bit = 0 > External oscillator (quartz) is used as a clock source.

 LCD_Init_v4 (void);

 pointeur_de_char = &texte[0];                              // pointeur_de_char pointe sur le premier élément du tableau "texte", soit texte[0].
                                                            // Autrement dit, pointeur_de_char contient l'adresse (&) de texte[0].
 do
  {
   float adc;
   float volt, temp;

   char txt[13];

   ADC_initialization();

   TRISD = 0x00;

    while(1)
    {
     adc = (ADC_read(3));                                   // Reads analog values.
     volt = adc * 4.88281;                                  // Converts it into the voltage.
     temp = volt / 10.0;                                    // Gets the temperature values.
     temp = temp - 273;                                     // Converts Farenheit to Celcius.

     StrConstRamCpy(pointeur_de_char, "Temperature ...  "); // OK.
     LCD_Write_String_At(0, 0, pointeur_de_char);           // OK. Voir dans void LCD_Write_String(char *msg), le while(*(msg + k) > 0).

     //print_float_v11(&txt[0], temp, 0);
     //print_float_v11(&txt[0], temp, 1);
     print_float_v11(&txt[0], temp, 2);
     //print_float_v11(&txt[0], temp, 3);

     LCD_Write_String_At(1, 0, &txt[0]);                    // Write txt in 2nd row, starting at 1st digit.
     LCD_Write_String_At(1, 12, " °C");                     // Write " °C" in 2nd row, starting at 13th digit.

     delay_ms(3000);
    }
  }
 while(1);
}
 
If your application leaves enough memory to use, try the 'sprintf' instruction to convert floating point numbers to strings. It can also add fixed text to the display in the process.
For example:
Code:
sprintf(TextToLCD,"Temperature is %fC", temp);
if 'temp' is 1.234 the character string 'TextToLCD' will become 'Temperature is 1.234C'.

Note that you can apply formats to the %f substitution, for example changing it to %02.3f will display up to two leading zeroes and 3 decimal places.

Brian.
 
You are calculating 'temp' as a floating point value but passing it to 'print_float_v11' which expects a 'long' (aka integer) value. You might be getting an automatic conversion (or garbage value) but assuming that your program is working as expected, then you will need to scale the 'temp' by a factor of 10 or 100 if you want those additional decimal values.
For example, if you have temp = 1.234 and want 2 decimal places, then the integer that the function needs to deal with must be 123 - i.e. the value multiplied by 100. Of course this may need to be adjusted to allow for the rounding for the dropped decimal place - i.e. if you want 1.237 to print as "1.24" then you need to pass 124 as the integer to your function.
Also your function needs to be altered to calculate the various values correctly, rather than (as you have it now) assuming a specific scaling factor and using literal '0' characters.
Following on from @betwixt's suggestion (to use sprintf), that function will probably call 'ftoa' to do the actual work. There are several source code versions of this function on the internet that you can use instead of yours, that will be a lot more flexible and handle being passed a floating point number, rather than a scaled integer.
Susan
 
If your application leaves enough memory to use, try the 'sprintf' instruction to convert floating point numbers to strings. It can also add fixed text to the display in the process.
For example:
Code:
sprintf(TextToLCD,"Temperature is %fC", temp);
if 'temp' is 1.234 the character string 'TextToLCD' will become 'Temperature is 1.234C'.

Note that you can apply formats to the %f substitution, for example changing it to %02.3f will display up to two leading zeroes and 3 decimal places.

Brian.
Merci Brian.
With 16F887, sprintf doesn’t run. I remember this instruction when I used C many years ago as a student. I just want to use my own conversion in open source. Finally I found a routine that Paulfjugo, as a member, left on your site, that fit. But just a little be more difficult to understand for me. Have to study it.
 
You are calculating 'temp' as a floating point value but passing it to 'print_float_v11' which expects a 'long' (aka integer) value. You might be getting an automatic conversion (or garbage value) but assuming that your program is working as expected, then you will need to scale the 'temp' by a factor of 10 or 100 if you want those additional decimal values.
For example, if you have temp = 1.234 and want 2 decimal places, then the integer that the function needs to deal with must be 123 - i.e. the value multiplied by 100. Of course this may need to be adjusted to allow for the rounding for the dropped decimal place - i.e. if you want 1.237 to print as "1.24" then you need to pass 124 as the integer to your function.
Also your function needs to be altered to calculate the various values correctly, rather than (as you have it now) assuming a specific scaling factor and using literal '0' characters.
Following on from @betwixt's suggestion (to use sprintf), that function will probably call 'ftoa' to do the actual work. There are several source code versions of this function on the internet that you can use instead of yours, that will be a lot more flexible and handle being passed a floating point number, rather than a scaled integer.
Susan
Merci Susan for tour clear
You are calculating 'temp' as a floating point value but passing it to 'print_float_v11' which expects a 'long' (aka integer) value. You might be getting an automatic conversion (or garbage value) but assuming that your program is working as expected, then you will need to scale the 'temp' by a factor of 10 or 100 if you want those additional decimal values.
For example, if you have temp = 1.234 and want 2 decimal places, then the integer that the function needs to deal with must be 123 - i.e. the value multiplied by 100. Of course this may need to be adjusted to allow for the rounding for the dropped decimal place - i.e. if you want 1.237 to print as "1.24" then you need to pass 124 as the integer to your function.
Also your function needs to be altered to calculate the various values correctly, rather than (as you have it now) assuming a specific scaling factor and using literal '0' characters.
Following on from @betwixt's suggestion (to use sprintf), that function will probably call 'ftoa' to do the actual work. There are several source code versions of this function on the internet that you can use instead of yours, that will be a lot more flexible and handle being passed a floating point number, rather than a scaled integer.
Susan
Thank you Susan for your clear explanation. It's very complete. I'm going to use a paulfjugo routine dropped in another thread I had opened. Fully adapted. Just a little harder to understand in detail.
 
To be honest, I founded a routine that paulfjugo dropped in a former discussion. But I do not understand almost nothing … 😏

Code:
void float2aascii (float x, unsigned char *str, char precision)
{
 // Converts a floating point number to an ASCII string.
 // Version limited at 5 decimals maximum.
 // x is stored into str, which should be at least 30 chars long.

 int ie, i, k, ndig;
 double y;

 if (precision >= 5)
    {
     precision = 5;
    }
    else
          {
           precision++;
          }

 ndig = precision;

 ie = 0;

 // If x is negative, write minus and reverse.
 
if (x < 0.00000)
    {
     *str++ = '-';
     x = -x;
    }
 
// Put x in range 1 <= x < 10.

 if (x > 0.000000)
    {
     while (x < 1.000000)
     {
      x *= 10.000; // 🤔 A la place de =*
      ie--;
     }
    }

 while (x >= 10.0000)
 {
  x = x / 10.0000;
  ie++;
 }

 // In f format, number of digits is related to size.

 ndig += ie; // 🤔 A la place de =+
 
// round x is between 1 and 10 and ndig will be printed to right of decimal point …
 
 for (y = i = 1; i < ndig; i ++)
       {
        y = y / 10.0000;
       }

 x += y / 2.0000; // 🤔

 if (x >= 10.0000)
    {
     x = 1.0000;
     ie++;
    }

 if (ie < 0)
    {
     *str++ = '0';
     *str++ = '.';
     if (ndig < 0)
        {
         ie = ie - ndig;
        }
     for (i = -1; i > ie; i --)
           {
            *str++ = '0';
           }
    }

 for (i = 0; i < ndig; i ++)
        {
         k = x;
         *str++ = k + '0';
         if (i == ie)
            {
             *str++ = '.';
            }
         x -= (y = k); 🤔
         x *= 10.0000;
        }
      
 *str = '\0'; 🤔
}
 
Good on you for not just using the code blindly but wanting to understand how it works.
I would start by thinking how you would go about this with a pencil and paper.
I think you have a specific problem with understanding the syntax such as 'ndig += ie;'. This is standard 'C' and is equivalent to (in this case) 'ndig = ndig + ie'. Of course the '+=' can be any of the normal numeric and bit manipulation operators ('*", '/', '&' etc.).
"*str = '\0'; " puts a null character at the end of the string which, by convention, indicates the end of the string.
The "x -= (y = k);" is doing two things at once. The 'y = k' part id a standard assignment. Also remember that the result of an assignment is the value itself (which means that code such as 'a = b = c = 0;' will set 'a', 'b' and 'c' all t 0). Once the assignment has been done this becomes equivalent to 'x -= k' which is the structure described above.
Susan
 
Good on you for not just using the code blindly but wanting to understand how it works.
I would start by thinking how you would go about this with a pencil and paper.
I think you have a specific problem with understanding the syntax such as 'ndig += ie;'. This is standard 'C' and is equivalent to (in this case) 'ndig = ndig + ie'. Of course the '+=' can be any of the normal numeric and bit manipulation operators ('*", '/', '&' etc.).
"*str = '\0'; " puts a null character at the end of the string which, by convention, indicates the end of the string.
The "x -= (y = k);" is doing two things at once. The 'y = k' part id a standard assignment. Also remember that the result of an assignment is the value itself (which means that code such as 'a = b = c = 0;' will set 'a', 'b' and 'c' all t 0). Once the assignment has been done this becomes equivalent to 'x -= k' which is the structure described above.
Susan
Thank you Susan for your encouragement. It would be more reasonable for me to take a pencil, an eraser and a sheet of paper and try to write the algorithm of this code myself. But honestly as I'm not very strong and I'm missing a little time, I'm going to spend several sleepless nights without sleeping to try to find. Programmers like you, more ade, will probably take less time. And it would be a beautiful Christmas 🎅 gift 🎁 for me under the tree 🎄!
--- Updated ---

Good on you for not just using the code blindly but wanting to understand how it works.
I would start by thinking how you would go about this with a pencil and paper.
I think you have a specific problem with understanding the syntax such as 'ndig += ie;'. This is standard 'C' and is equivalent to (in this case) 'ndig = ndig + ie'. Of course the '+=' can be any of the normal numeric and bit manipulation operators ('*", '/', '&' etc.).
"*str = '\0'; " puts a null character at the end of the string which, by convention, indicates the end of the string.
The "x -= (y = k);" is doing two things at once. The 'y = k' part id a standard assignment. Also remember that the result of an assignment is the value itself (which means that code such as 'a = b = c = 0;' will set 'a', 'b' and 'c' all t 0). Once the assignment has been done this becomes equivalent to 'x -= k' which is the structure described above.
Susan
Even this strange for loop, do not understand clearly … 🤔
for (y = i = 1; i < ndig; i ++)
😱
 
Code:
for (y = i = 1; i < ndig; i ++)
       {
        y = y / 10.0000;
       }
Break it down. The 'y = i = 1' part we dealt with earlier - this is just an assignment to both the 'y' and the 'i' variables with the numeric value of 1.
The rest is a fairly standard 'for' loop that initialises 'i', check that it is less than the value of 'ndig', performs the loop and then increments 'i' by 1.
So really the only 'strange' part is the assignment of 'y' to an initial value or 1 along with the loop variable.
The loop part is probably written with more trailing 0's of the floating point value (it could be '10.0' and be just the same) than necessary but that is not 'wrong as such.
This is all basic 'C' - I suggest that you get a good beginner's book or tutorial on 'C" and read through that.
Susan
 
Code:
for (y = i = 1; i < ndig; i ++)
       {
        y = y / 10.0000;
       }
Break it down. The 'y = i = 1' part we dealt with earlier - this is just an assignment to both the 'y' and the 'i' variables with the numeric value of 1.
The rest is a fairly standard 'for' loop that initialises 'i', check that it is less than the value of 'ndig', performs the loop and then increments 'i' by 1.
So really the only 'strange' part is the assignment of 'y' to an initial value or 1 along with the loop variable.
The loop part is probably written with more trailing 0's of the floating point value (it could be '10.0' and be just the same) than necessary but that is not 'wrong as such.
This is all basic 'C' - I suggest that you get a good beginner's book or tutorial on 'C" and read through that.
Susan
I understand mostly all C instruction, and otherwise I use tutorial on line or help menu of MikroC compiler.
The problem with this routine is more the principe used for conversion, how the conversion is managed, the différents steps / cases, with the several for loops one after one.
--- Updated ---

Good on you for not just using the code blindly but wanting to understand how it works.
I would start by thinking how you would go about this with a pencil and paper.
I think you have a specific problem with understanding the syntax such as 'ndig += ie;'. This is standard 'C' and is equivalent to (in this case) 'ndig = ndig + ie'. Of course the '+=' can be any of the normal numeric and bit manipulation operators ('*", '/', '&' etc.).
"*str = '\0'; " puts a null character at the end of the string which, by convention, indicates the end of the string.
The "x -= (y = k);" is doing two things at once. The 'y = k' part id a standard assignment. Also remember that the result of an assignment is the value itself (which means that code such as 'a = b = c = 0;' will set 'a', 'b' and 'c' all t 0). Once the assignment has been done this becomes equivalent to 'x -= k' which is the structure described above.
Susan
Thanks for explanations.
 
I understand mostly all C instruction, and otherwise I use tutorial on line or help menu of MikroC compiler.
The problem with this routine is more the principe used for conversion, how the conversion is managed, the différents steps / cases, with the several for loops one after one.
--- Updated ---


Thanks for explanations.
I understand mostly all C instruction, and otherwise I use tutorial on line or help menu of MikroC compiler.
The problem with this routine is more the principe used for conversion, how the conversion is managed, the différents steps / cases, with the several for loops one after one.
--- Updated ---


Thanks for explanations.
Good on you for not just using the code blindly but wanting to understand how it works.
I would start by thinking how you would go about this with a pencil and paper.
I think you have a specific problem with understanding the syntax such as 'ndig += ie;'. This is standard 'C' and is equivalent to (in this case) 'ndig = ndig + ie'. Of course the '+=' can be any of the normal numeric and bit manipulation operators ('*", '/', '&' etc.).
"*str = '\0'; " puts a null character at the end of the string which, by convention, indicates the end of the string.
The "x -= (y = k);" is doing two things at once. The 'y = k' part id a standard assignment. Also remember that the result of an assignment is the value itself (which means that code such as 'a = b = c = 0;' will set 'a', 'b' and 'c' all t 0). Once the assignment has been done this becomes equivalent to 'x -= k' which is the structure described above.
Susan
Bonjour Susan,
Have been busy these last weeks. Back to MikroC, debugging, understanding routines …
Concerning my routine I took time to understand almost all conditions before for boucles, and for boucles themselves in order to run it. I made simulation in an Excel sheet. The routines is based on power 10 with ie as exposant, when ie < 0 (values 0,…) and when ie > 0 (values 10 and upper …).
But, I do not really understand this for boucle :
for (y = i = 1; i < ndig; i ++)
{
y = y / 10.0000;
}
Inside the for, while for runs, is y also increased as + 1 like i ?
I wouldn't understand why.
Merci
 
Firstly, look carefully at where the 'y' variable is referenced in the loop - you will note that it is NOT incremented by 1 but rather divided by 10 on each time through the loop. Therefore, starting at 1, it will be 0.1 the first time through, 0.01 the 2nd and so un for 'ndig' times through the loop.
Secondly, you need to know how to read the 'for' statement. In effect you can write the 3 parts of it (initialization test and (what is often) increment/decrement) as an equivalent 'while' loop. Therefore, the 'for' loop can be written as:
C:
i = 1;
y = i;  // Thse two lines are the first part of the 'for' loop and initialise variables
while(i < ndig)  // This applies the 2nd part - the test - to see if we go through the loop (again)
{
    y = y / 10.00;  // This is the code that is inside the loop
    i++;      // The 3rd part of the 'for' statement - this typically updates the loop variable
}
Again, you can see that the 'y' variable is initialized to 1 but the only place it is altered is to divide it by 10 inside the loop.
Therefore your statement that
y also increased as + 1 like i ?
is incorrect.
Susan
 

    Eric_O

    Points: 2
    Helpful Answer Positive Rating
Firstly, look carefully at where the 'y' variable is referenced in the loop - you will note that it is NOT incremented by 1 but rather divided by 10 on each time through the loop. Therefore, starting at 1, it will be 0.1 the first time through, 0.01 the 2nd and so un for 'ndig' times through the loop. Secondly, you need to know how to read the 'for' statement. In effect you can write the 3 parts of it (initialization test and (what is often) increment/decrement) as an equivalent 'while' loop. Therefore, the 'for' loop can be written as:
C:
i = 1; y = i; // Thse two lines are the first part of the 'for' loop and initialise variables while(i < ndig) // This applies the 2nd part - the test - to see if we go through the loop (again) { y = y / 10.00; // This is the code that is inside the loop i++; // The 3rd part of the 'for' statement - this typically updates the loop variable }
Again, you can see that the 'y' variable is initialized to 1 but the only place it is altered is to divide it by 10 inside the loop. Therefore your statement that is incorrect. Susan

Thank you for your lighting Susan. Here I am reassured. Indeed, I didn't really like writing this loop with the for instruction. Yesterday I thought that a while loop was better, and simpler. But I preferred to wait for your answer. Usually I prefer the while over the for. And the initialization of variables i and y is clearer as well.
 
There is nothing wrong with using a 'for' loop, and it is probably 'neater' than using the equivalent 'while' loop as all of the key parts are all in the one place. However the choice is your as the coder - but remember that you or someone else may need to update your code in the years to come so making it understandable is a good thing.
The only reason I used the equivalent 'while' loop was to show you where the 'y' variable was actually used and why your comment about it being incremented by 1 was wrong.
Susan
 
There is nothing wrong with using a 'for' loop, and it is probably 'neater' than using the equivalent 'while' loop as all of the key parts are all in the one place. However the choice is your as the coder - but remember that you or someone else may need to update your code in the years to come so making it understandable is a good thing.
The only reason I used the equivalent 'while' loop was to show you where the 'y' variable was actually used and why your comment about it being incremented by 1 was wrong.
Susan
Merci Susan,

Step after step, I have other questions :

1) In the following part of the code …

// Put x in range 1 >= x < 10 :
// If x is > 0, and while x < 1 :
// x = x * 10,
// ie = ie - 1.
if (x > 0.000000)
{
while (x < 1.000000)
{
x = x * 10.0000;
ie--;
}
}
// While x >= 10 :
while (x >= 10.0000)
{
x = x / 10.0000;
ie++;
}


… these lines here under …

while (x >= 10.0000)
{
x = x / 10.0000;
ie++;
}

… could be inside the …

if (x > 0.000000)
{

}

… after the first while loop …

while (x < 1.000000)
{

}

2) While this line x = x + y / 2.0000; ?
3) I do not understand this condition :

if (x >= 10.0000)
{
x = 1.0000;
ie++;
}
 
Point #1: you could do that but that would also extend the size of the 'then' block (i.e. the part that is executed if the boolean in the 'if' statement is true). One thing that makes code more understandable is to keep blocks short and to keep then to one process.

Point #2: You need to understand what the 'x' and 'y' variables contain. The comment immediately above that code section indicates what is happening. The variable 'x' contains the absolute original value normalised to be greater than or equal to 1 and less than 10. (By the way, your first comment above has mis-copied the original comment. In programming, even in comments, accuracy is vital.) The variable 'y' indicates where the decimal point should be given the normalised 'x'.
For example, if the original number is 123.45 and the precision is 1 (i.e you want 1 digit displayed after the decimal point which would make 'ndig' - the total number of digits - 2), then 'x' would be 1.2345 and 'ie' would be 2 at the end of your loop in point 1. That means you have effectively shifted the decimal point 2 places right and this needs to be taken into account. That is why 'ndig' (which is the original precision [ignoring the +1 that accounts for the decimal point]) needs to be increased by 2 to make it 3. That means the decimal point needs to be printed after the 3rd numeric digit.
Now we get to why the 'divide by 2'. In the loop you were asking about before, 'y' starts as 1 and is divided by 10 'ndig - ' times so it becomes 0.001 in the example above. Look what happens when you divide that by 2 - it becomes 0.0005. How when you add that back to the 'x' value you get '1.2345 + 0.0005' which is 1.2350.
If the original number was 123.44, then it would now be 1.2349, and if the original umber was 123.46, it would become 1.2351.
Why is tis important - remember that we only want 1 digit after the printed decimal place which means that we will be (in this example) printing 4 digits (plus the decimal point) That makes all of the example values rounded to the last displayed digit. If the last digit of the 'x' value was between 0 and 4 then it will round the number down; if it was between 5 and 9 then the number is rounded up.

Point #3: after all the rounding, what if the original number was 99.95 and you wanted 1 decimal place. We have already made 'x' to be between 1 (inclusive) and 10 (exclusive) so it would be '9.995'. We now round that (as described before) and we now have 'x' become '10.00'. That is now equal to the upper bound for 'x' (remember that the upper bound is exclusive) so we bring it back to '1.000' and increment 'ie' to show that we have made another left shift.

What I strongly recommend what you do is to get a pencil and paper, start with some values for the function parameters - in my example above I started with the number 123.45 and a precision of 1 - and then create some columns for each of the variables involved putting in the numbers with each step. at step through the code update the numbers according to the statement and you will see how the code is working. (By the way, that is exactly what I did in working through my example above to make sure I had the right numbers.)
Do this with several initial numbers so that you go through all of the code paths.

Susan
 

    Eric_O

    Points: 2
    Helpful Answer Positive Rating
Point #1: you could do that but that would also extend the size of the 'then' block (i.e. the part that is executed if the boolean in the 'if' statement is true). One thing that makes code more understandable is to keep blocks short and to keep then to one process.

Point #2: You need to understand what the 'x' and 'y' variables contain. The comment immediately above that code section indicates what is happening. The variable 'x' contains the absolute original value normalised to be greater than or equal to 1 and less than 10. (By the way, your first comment above has mis-copied the original comment. In programming, even in comments, accuracy is vital.) The variable 'y' indicates where the decimal point should be given the normalised 'x'.
For example, if the original number is 123.45 and the precision is 1 (i.e you want 1 digit displayed after the decimal point which would make 'ndig' - the total number of digits - 2), then 'x' would be 1.2345 and 'ie' would be 2 at the end of your loop in point 1. That means you have effectively shifted the decimal point 2 places right and this needs to be taken into account. That is why 'ndig' (which is the original precision [ignoring the +1 that accounts for the decimal point]) needs to be increased by 2 to make it 3. That means the decimal point needs to be printed after the 3rd numeric digit.
Now we get to why the 'divide by 2'. In the loop you were asking about before, 'y' starts as 1 and is divided by 10 'ndig - ' times so it becomes 0.001 in the example above. Look what happens when you divide that by 2 - it becomes 0.0005. How when you add that back to the 'x' value you get '1.2345 + 0.0005' which is 1.2350.
If the original number was 123.44, then it would now be 1.2349, and if the original umber was 123.46, it would become 1.2351.
Why is tis important - remember that we only want 1 digit after the printed decimal place which means that we will be (in this example) printing 4 digits (plus the decimal point) That makes all of the example values rounded to the last displayed digit. If the last digit of the 'x' value was between 0 and 4 then it will round the number down; if it was between 5 and 9 then the number is rounded up.

Point #3: after all the rounding, what if the original number was 99.95 and you wanted 1 decimal place. We have already made 'x' to be between 1 (inclusive) and 10 (exclusive) so it would be '9.995'. We now round that (as described before) and we now have 'x' become '10.00'. That is now equal to the upper bound for 'x' (remember that the upper bound is exclusive) so we bring it back to '1.000' and increment 'ie' to show that we have made another left shift.

What I strongly recommend what you do is to get a pencil and paper, start with some values for the function parameters - in my example above I started with the number 123.45 and a precision of 1 - and then create some columns for each of the variables involved putting in the numbers with each step. at step through the code update the numbers according to the statement and you will see how the code is working. (By the way, that is exactly what I did in working through my example above to make sure I had the right numbers.)
Do this with several initial numbers so that you go through all of the code paths.

Susan
Bonjour Susan,

Thank you very much for your clear and complete answer to all my questions. I understand much better now. Not being in a hurry and wanting to understand every line of this routine, throughout our conversation and your explanations, I completed the simulation in an attached Excel file (see the sheet "float to ASCII").
This still I would need an explanation from you on the last loop for (i = 0; i < ndig; i++). I understand that this loop allows you to add "0s" at the end of the number ? For example, if we have x = 3.38 and the precision is 4 (with the comma included in the 4 as you explained to me above), we display the character string "3.380" instead of "3.38"?
Then I think everything will be clear for me for this routine.

Have a nice and safe week.

Eric
 

Attachments

  • Conversion - float to ascii.rar
    59.6 KB · Views: 78
By the last loop I assume you mean:
Code:
for (i = 0; i < ndig; i ++)
        {
         k = x;
         *str++ = k + '0';
         if (i == ie)
            {
             *str++ = '.';
            }
         x -= (y = k); 🤔
         x *= 10.0000;
        }
As before, using a pencil and paper and stepping through the code yourself wil show you what is happening.

Lets recall what some of the variables mean:
- ndig is the number of digits to be displayed
- x is the normalised value (i.e. the initial value that is now 1 <= x < 10
- str is a pointer to the output string

The 'for' loop should be understandable now.

The 'k = x' line may need as bit of explanation. Recall that 'x' is a floating point number, and looking back you can see that 'k' has been declared as an integer. When you assign a real to an integer, it will truncate the value - in other words it will keep any integer part and simply ignore any fractional part. For example. 3.65 will become 3.

The '*str++' for a pointer does two things. Firstly the '*' will dereference the pointer so that (in this case) the assignment will be to the memory location where 'str' is pointing. The is the output character string location. The '++' means that, after (in this case) the assignment, the pointer wil be incremented to the next address. AS this is a pointer to a character, the pointer will move to the next character position.
Now, what is assigned at the location? That is the "k + '0'" part. K is the binary integer we want to display but we need to convert it to as human readable ASCII value. If you look at an ASCII table, you will see that the digits are in their normal ascending sequence, tarting with '0'. Therefore adding '0' to the binary value will make it the ASCII equivalent: binary 4 will become "4" and so on. Therefore we are converting the binary digit to its ASCII equivalent and storing it in the output string.

The next 'if' statement looks to see if we are at the point in the output string where we need to put in the decimal point. (They are assuming the "." character is used whereas I know the Europeans often use ",".)

The 'x -= (y = k);' line needs a bit of explanation. As always you start within the brackets so we assign 'y' the value of 'k'. 'k' is the digit we have just "printed" but is an integer. It is quite acceptable to subtract a integer from a floating point number but in this case they have elected to convert the integer to the equivalent double precision value 'y' via the assignment. So it is the double precision value that is now subtracted from 'x'.

If 'x' was '3.65', then 'k' would have been '3' and "3" is what would have been added to the string. 'y' would be 3.0000 and this is what is subtracted from 'x' - so 'x' now becomes 0.65.

As the loop assumes we are using a normalised value, we multiply 'x' by 10 - in our example 0.65 will now be 6.5 - and we start the loop all over again.

The statement that is just outside the loop "*str='\0';" will write a null character at the end of the printed string. In C that is the convention to terminate a string.

Susan
 
Dear Susan,
Sorry for my late reply.
Thank you very much again for your clear explanations. They were a great help to me for the full understanding of this routine. I will not fail to ask you again if necessary. But I will try to consider your advice, trying next time by myself with the eraser and pencil.
Eric

By the last loop I assume you mean:
Code:
for (i = 0; i < ndig; i ++)
        {
         k = x;
         *str++ = k + '0';
         if (i == ie)
            {
             *str++ = '.';
            }
         x -= (y = k); 🤔
         x *= 10.0000;
        }
As before, using a pencil and paper and stepping through the code yourself wil show you what is happening.

Lets recall what some of the variables mean:
- ndig is the number of digits to be displayed
- x is the normalised value (i.e. the initial value that is now 1 <= x < 10
- str is a pointer to the output string

The 'for' loop should be understandable now.

The 'k = x' line may need as bit of explanation. Recall that 'x' is a floating point number, and looking back you can see that 'k' has been declared as an integer. When you assign a real to an integer, it will truncate the value - in other words it will keep any integer part and simply ignore any fractional part. For example. 3.65 will become 3.

The '*str++' for a pointer does two things. Firstly the '*' will dereference the pointer so that (in this case) the assignment will be to the memory location where 'str' is pointing. The is the output character string location. The '++' means that, after (in this case) the assignment, the pointer wil be incremented to the next address. AS this is a pointer to a character, the pointer will move to the next character position.
Now, what is assigned at the location? That is the "k + '0'" part. K is the binary integer we want to display but we need to convert it to as human readable ASCII value. If you look at an ASCII table, you will see that the digits are in their normal ascending sequence, tarting with '0'. Therefore adding '0' to the binary value will make it the ASCII equivalent: binary 4 will become "4" and so on. Therefore we are converting the binary digit to its ASCII equivalent and storing it in the output string.

The next 'if' statement looks to see if we are at the point in the output string where we need to put in the decimal point. (They are assuming the "." character is used whereas I know the Europeans often use ",".)

The 'x -= (y = k);' line needs a bit of explanation. As always you start within the brackets so we assign 'y' the value of 'k'. 'k' is the digit we have just "printed" but is an integer. It is quite acceptable to subtract a integer from a floating point number but in this case they have elected to convert the integer to the equivalent double precision value 'y' via the assignment. So it is the double precision value that is now subtracted from 'x'.

If 'x' was '3.65', then 'k' would have been '3' and "3" is what would have been added to the string. 'y' would be 3.0000 and this is what is subtracted from 'x' - so 'x' now becomes 0.65.

As the loop assumes we are using a normalised value, we multiply 'x' by 10 - in our example 0.65 will now be 6.5 - and we start the loop all over again.

The statement that is just outside the loop "*str='\0';" will write a null character at the end of the printed string. In C that is the convention to terminate a string.

Susan
 

LaTeX Commands Quick-Menu:

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top