Rounding and Code Skipping

Greetings!

  1. Why does it appear that my code skips lines when it shouldn’t and
  2. Why do I get numbers that don’t make sense given the type they’ve been cast as?

I am using a Cerberus to read a voltage in but I don’t want or need voltages out to 17 digits so I’m trying to round it.

I found some similar posts about rounding and tried to implement it.

I also set up some debug output so I could see what’s happening during the code.

What I’ve found makes it seem like it’s skipping lines of code. It randomly succeeds at rounding the numbers as I’d like. Other times it reports voltages that don’t make any sense as to how it could be doing that.

This is an example of how the code [em]should[/em] work:
A. It reads the volts in at, say, 0.2156549873254 and stores it into a double
B. Multiplies that value by 1000 and stores it into another double as 215.6549873254
C. Casts the double into an integer therefore truncating the number to 215
D. Casts the integer to a double and then divides it by 1000. It should read 0.215.

Doesn’t always do that, sometimes it’s 0.214999999999 or 0.21500000001

Here’s some of the debug output

Program Started
The thread ‘’ (0x3) has exited with code 0 (0x0).
Button 1 Pressed
VCheck Started
0.85421245421245429 analogin is
854.2124542124543 trunk
854 convert
854 convert which is an integer cast as a double into truncated
0.85399999999999998 integer volts (x1000) / 1000
End VCheck Timer
VCheck Started
0.22886446886446887 analogin is
End VCheck Timer
VCheck Started
0.21597069597069596 analogin is
215.97069597069597 trunk
215 convert
215 convert which is an integer cast as a double into truncated
0.215 integer volts (x1000) / 1000
End VCheck Timer

Here’s the code:


using System;
using System.Collections;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Presentation.Shapes;
using Microsoft.SPOT.Touch;

using Gadgeteer.Networking;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using Gadgeteer.Modules.GHIElectronics;

namespace AnalogInTester
{
    public partial class Program
    {

        GT.Timer checkVoltage = new GT.Timer(1000);

        int count = 0;

        double truncated;

        int convert;

        double trunk;

        void ProgramStarted()
        {
            Debug.Print("Program Started");
            button.ButtonPressed += new Button.ButtonEventHandler(button_ButtonPressed);
            button2.ButtonPressed += new Button.ButtonEventHandler(button2_ButtonPressed);
            checkVoltage.Tick += new GT.Timer.TickEventHandler(checkVoltage_Tick);

            char_Display.Clear();
            char_Display.PrintString("Volts in checker");
            char_Display.SetCursor(1, 0);
            char_Display.PrintString("> 3.3Vdc = BREAK");

        }

        void button_ButtonPressed(Button sender, Button.ButtonState state)
        {
            Debug.Print("Button 1 Pressed");
            checkVoltage.Start();
        }


        void button2_ButtonPressed(Button sender, Button.ButtonState state)
        {
            Debug.Print("Button 2 Pressed");
            checkVoltage.Stop();
        }

        void checkVoltage_Tick(GT.Timer timer)
        {
            Debug.Print("VCheck Started");
            Gadgeteer.Interfaces.AnalogInput analogvolts = breakout.SetupAnalogInput(GT.Socket.Pin.Three);
            double analogin = analogvolts.ReadVoltage();

            Debug.Print(analogin + " analogin is");

            analogvolts.Dispose();

            trunk = analogin * 1000;
            Debug.Print(trunk + " trunk");

            convert = (int)trunk;
            Debug.Print(convert + " convert");

            truncated = (double)convert;
            Debug.Print(truncated + " convert which is an integer cast as a double into truncated");

            Debug.Print((truncated/1000) + " integer volts (x1000) / 1000");
            char_Display.Clear();
            char_Display.PrintString("Volts Read");
            char_Display.SetCursor(1, 0);
            char_Display.PrintString((truncated/1000) + " volts");

            if (analogin > 1.5)
            {
                relay_X1.TurnOn();
                Thread.Sleep(5000);
                relay_X1.TurnOff();
            }
            
            count++;
            if (count == 10)
            { count = 0; }
            Debug.Print("End VCheck Timer");
        }
    }
}

it’s called “floating point maths”. It is always only “approximate”. The rounding error on those values you show are still within the ballpark of what I would expect.

What actual issue does this pose to you? What are you trying to achieve?

Floating point maths?

That’s weird. I would have thought for sure that converting it to an integer and then back to a double would eliminate the 0.0000000001.

It doesn’t really pose me a serious issue. I’m trying to achieve an output where I can tack the word “volts” on the end or “feet” which is my end goal. Since I’m printing to a character display which is limited to 16 characters, those words get pushed out.

What really concerns me though is how my debug output seems to skip lines of code. Is it really skipping lines of code?

@ Keith_ - Floating-point arithmetic - Wikipedia

have you tried this:


var rdDouble = System.Math.Round(myDouble);

Thanks for helping out!

I found Math.Round in a couple of other posts so I read about it in at Math.Round Method (System) | Microsoft Learn and tried to use it like this:

System.Math.Round(3.44, 1);

But it just gets underlined in red.

When I pasted @ Jay Jay 's code it didn’t highlight in red. However, when I put a comma to add how many digits to round it, it highlighted in red.

Thanks @ Mike! That article is a bit thick but I appreciate it.

Hi Keith,

If you hover your mouse over the line it will show you the error. On mine it shows that there is no function that takes 2 arguments.

It is likely that you have found help for the full .NET framework which does take 2 arguments. The version for .NET MF is only 1.

Another way is to use the ToString function on the double value. Eg.

String myvalueStr = myvalue.ToString(“F1”);

This will give you a string with 1 decimal place. I think this will be ideal for your LCD use.

I came up with my own solution.

Since the ADC only accepts voltages up to 3.3 volts and the resolution of the ADC starts to be erratic at the thousandths place, I decided that I want voltage readings to the thousandths place.

In short, I turned the values I wanted past the decimal place into integers and then skipped converting them back to “double” type and just used the sorting of the values to determine what to put before the decimal place.

Also, I was trying to smooth out the erratic values of the thousandths place so I started averaging the values taken over the course of a second and them displaying them at the end of the second.

My code (a brief version):


{        
        {
        int average = 0;
        int count = 0;
        double A;
        double B;
        int C;
        int D;
        int adder;
        int E = 0;

            {Gadgeteer.Interfaces.AnalogInput analogvolts = breakout.SetupAnalogInput(GT.Socket.Pin.Three);
            A = analogvolts.ReadVoltage();

            analogvolts.Dispose();

            B = A * 1000;
            Debug.Print(B + " is B, analog volts * 1000");

            C = (int)B;
            Debug.Print(C + " is C, B cast as an integer");

            if (C > 3000)
            {
                adder = 3;
                D = C - 3000;
            }

            if (C > 2000 & C <3000)
            {
                adder = 2;
                D = C - 2000;
            }
            
            if (C > 1000 & C < 2000)
            {
                adder = 1;
                D = C - 1000;
            }
            
            if (C < 1000)
            {
                adder = 0;
                D = C;
            }

            average = average + D;

            if (count == 9)
            {
                Debug.Print(D + " yo mami");

                E = (average)/10;
                displayvolts();
                average = 0;
            }

            count++;
            if (count == 10)
            { count = 0; }
            Debug.Print("End VCheck Timer");
        }
        public void displayvolts()
        {
            char_Display.Clear();
            char_Display.PrintString("Volts Read");
            char_Display.SetCursor(1, 0);
            char_Display.PrintString(adder + "." + E);
        }

        public void welcome()
        {
            char_Display.Clear();
            char_Display.PrintString("Volts in checker");
            char_Display.SetCursor(1, 0);
            char_Display.PrintString("> 3.3Vdc = BREAK");
        }
    }
}

@ Keith_ -
Congratulations. You’ve just invented fixed point scaled arithmetic. :>

Take your floating point input, multiply it by a fixed scale, then convert to integer to truncate the noise bits. Perform all subsequent math operations as integer operations with constants that have the same scale factor built in. This will avoid most of the error accumulation seen with floating point arithmetic and let you work with speedy integer operations.

Neat!

Every base number system has numbers that can’t be represented exactly. 1/3, cannot be represented exactly in base 10, for example (0.33333333…). But in base 3, it’s exactly 0.1. If you convert base 3 (0.1) to base 10 (0.33333…) and back to base 3, and you won’t get 0.1 because information was lost in the base 10 step because base 10 can only approximate the target value.

Floating point numbers are implemented in base 2. To render them on screen they are converted to base 10. There are numbers that are exact in base 10 that cannot be exactly represented in base 2, and vise-versa. When you do a round trip conversion of such numbers between bases, the output won’t match the input, even though algebraically we know it should. This is the nature of floating point math.

And then there are the irrational numbers, like Pi and “e”. They can’t be represented exactly in ANY base system. :> (and there are more irrational numbers on the number line than anything else! )

-Danny

2 Likes

That makes sense. Thank you for the explanation!