News:

;) This forum is the property of Proton software developers

Main Menu

Simple floating point problem.

Started by david, Aug 03, 2021, 12:57 AM

Previous topic - Next topic

david

Hi All,
I've not used floating point a lot so it's not an area I'm confident in but recently I blundered into a problem that seemed trivial but I couldn't make sense of what was going on.
The attached code is part of a one button menu on an OLED display that prompts you to enter a value for a low voltage cut-off value that cycles from 3.0 to 3.8 in 0.2 steps - round and round.  Provided you keep pressing the button it keeps changing and when you stop it confirms the value saved. The code as shown works fine but when I changed the increment to 0.1 it went very strange.
I then changed the OLED display to show 2 decimal points and this revealed that the value was incrementing as follows-
3.00, 3.10, 3.20, 3.29, 3.39, 3.49, 3.59, 3.69, 3.79, 3.89, 3.99, 4.09, 4.19, 4.29, 4.39.......9.00, 9.10, 9.20, 9.30......  No further error noticed up to 30.00

This explains why the code with a single decimal point display (and 0.1 increment) appeared to ignore the third button press (3.29 displayed as 3.2) and also why it ignored the line   If LVC=4.0 Then LVC=3.0    as the value skipped from 3.99 to 4.09 and hence kept on going.
I've tried both the default Float display and the Fast Float display but did not see any difference so what am I doing wrong?
Device is a 16F1827 and compiler version is 3.7.5.5

Regards to all,
David


'********* Set LVC ************* 
       GoSub printaddress     
       mystring="LVC?    "      'print input 8 chars
       GoSub printing
       DelayMS 750
       While sw1=1 :Wend
       
       LVC=3.0
LVCselect:
       If LVC=4.0 Then LVC=3.0
       GoSub printaddress
       mystring=Str$ (Dec1 LVC)+"V    "
       GoSub printing
       While sw1=0:Wend
       DelayMS 100
       For n=0 To 90
         If sw1=0 Then LVC=LVC+0.2 :GoTo LVCselect
         DelayMS 20
       Next n 
       DelayMS 100
       GoSub printaddress
       mystring=Str$ (Dec1 LVC)+"V Saved" 
       GoSub printing
       DelayMS 1000           


John Drew

Greetings David,
What you are seeing is the inability of a 32 bit float to represent 0.1 accurately whereas it can accurately represent 0.2.
There are a couple of workarounds either using integers or adding a small number in a float and rounding.
John

david

Hello John,
That was faster service than McDonald's.
I was expecting to see the error reoccur but instead it came right at 9.00 and didn't appear again - at least out to 30.00. 
I shall do some more playing but my other issue is that I'm almost out of code space so I can't get clever.
Thanks for the reply.

Cheers,
David

trastikata

David,

what I do is to add an increment to an integer variable and then make the Float variable equal to the division of the integer by 100 (10,1000 or so) - works always. Short example:

Dim TempSDword As SDword
Dim TempFloat As Float
       
Main:
    TempSDword = 0
   
    While 1 = 1
        TempFloat = TempSDword / 100
        Print Dec2 TempFloat
        TempSDword = TempSDword + 10
        DelayMS 200     
    Wend

Stephen Moss

Quote from: david on Aug 03, 2021, 12:57 AMThe code as shown works fine but when I changed the increment to 0.1 it went very strange.
I then changed the OLED display to show 2 decimal points and this revealed that the value was incrementing as follows-
3.00, 3.10, 3.20, 3.29, 3.39, 3.49, 3.59, 3.69, 3.79, 3.89, 3.99, 4.09, 4.19, 4.29, 4.39.......9.00, 9.10, 9.20, 9.30......  No further error noticed up to 30.00
I noticed the appearance of extra digits on PC's 30 years ago when doing a similar increment as I was to lazy to work out the correct equation to the provide a quicker result. Expensive 64bit processor = issue, £5 calculator = no issue, not sure why calculator gets it right.

I have not tried it but I was going to suggest the same solution as see trastikata, only I was going to suggest using a factor of 10 so your loop values run from 30 to 38 in increments of 1, then divide the result by 10 to get your 3.0 to 3.8 float.

david

Quote from: trastikata on Aug 03, 2021, 07:14 AMDavid,

what I do is to add an increment to an integer variable and then make the Float variable equal to the division of the integer by 100 (10,1000 or so) - works always. Short example:

Dim TempSDword As SDword
Dim TempFloat As Float
       
Main:
    TempSDword = 0
   
    While 1 = 1
        TempFloat = TempSDword / 100
        Print Dec2 TempFloat
        TempSDword = TempSDword + 10
        DelayMS 200     
    Wend

Many thanks for your example code.   That looks like the way to go.  In the past I would have used integer maths before ever considering floats - obviously I'm getting lazy in my old age.
Thanks again.

Best regards,
David

david

Quote from: Stephen Moss on Aug 03, 2021, 07:47 AMI noticed the appearance of extra digits on PC's 30 years ago when doing a similar increment as I was to lazy to work out the correct equation to the provide a quicker result. Expensive 64bit processor = issue, £5 calculator = no issue, not sure why calculator gets it right.

I have not tried it but I was going to suggest the same solution as see trastikata, only I was going to suggest using a factor of 10 so your loop values run from 30 to 38 in increments of 1, then divide the result by 10 to get your 3.0 to 3.8 float.

Ha!   Didn't Windows 7 default calculator initially have a problem that gave 2+2=3.99999 ?  I believe it was patched a couple of weeks later.
Working with 30-38, increment 1 then dividing by 10 is probably what I would have done in the past.  I can see I need to be a bit more wary of using floats.

Cheers,
David

david

Well that was a trivial change (using 30-38, increment 1 and diving by 10) and it's all working correctly. 
Thanks for the help guys.

Cheers,
David

top204

Within your program, add the "Declare Float_Display_Type = Fast". I created the original floating point to ASCII display routine many, many years ago, but later I re-wrote a better routine, but to keep backward compatability, the compiler uses the original routine because the newer routine uses more code memory, so there would be differences in users code.

The newer "Fast" routine is also more accurate when viewing the values on an LCD or serial terminal, but 32-bit floating point does have its downfalls, especially with the value 0.1. That's why I added 64-bit floating point to the PIC24 and dsPIC33 compiler, so that John could perform his moon calculations with a lot more accuracy. However, 64-bit floating point on an 8-bit device would be silly. i.e. Huge and slow. :-)

david

Hello Les,
Many thanks for your reply.  I did try the Fast Float display but because I'm almost out of code space I had to delete some of my working code to try it and it didn't appear to help with the menu entry of the 0.1 step increments.
That's been resolved but I'm now uncertain as to the accuracy of some of the products of the calculations for Watt.Hours and mA.Hours.   With a bigger chip I would certainly use the larger Float display declare but I'm sure I don't need John's precision and would be happy with 2 decimal points.
The project is a lithium polymer battery discharger and requires the user to set the number of cells, the low voltage cut-off value and the discharge current.  It measures each cell, the total battery voltage, generates the PWM (integrated) for the current sink, calculates the WH and mAH and outputs to the display and via a serial port - all at one second intervals. I should use bigger chips to make life easier but often it starts out as a small project and grows.

Best regards,
David