Multiplication errors

Hey everyone.

I'm programming a bike computer.. most of the snags i'm hitting i'm able to figure out, but this one has me particularly stumped.

In calculating the distance traveled, I divide the total number of mm counted by the number of mm in a mile, then need to output the floating point calculation to a serial display.

I'm trying to use the suggestion i saw somewhere else to change the whole number from the float to a long, then change the decimal to a long and multiply by 100 to get the precision, then print the text. However, I'm running into an issue.

When I dump the output from the multiplication, instead of multiplying dist2 times 100, it's squaring dist2. This happens when dist2 is a long/unsigned long. If it's an int, the calculation works fine. If dist2 is defined prior to the calculation with a single number, it works fine. It doesn't work when calculated out after setting it with the prior equation.

I've talked to some people on IRC about this, but we haven't been able to come up with a reason why it's doing this. They've compiled the offending code into C on a computer and it works fine, but the Arduino doesn't seem to be doing the work properly.

Here's the code, if anyone would like to actively discuss it I'm on AIM most of the time as Chorca1, a bit faster to test code and such that way. The offending calculation is " dist2 = (unsigned long)((unsigned long)dist2 * (unsigned long)(100));" in the display() function.

//Tire circumference in millimeters
const unsigned long circ = 2095;


const byte SensorPin = 2;   //Input pin for speed sensor
unsigned long time;         //last time sensor was tripped
unsigned int milsecs;       //this will overflow after 64 seconds or so between hits, but that doesn't matter, only need it for sub-second timings
unsigned long time2;        //time before last sensor was tripped
unsigned int count;         //number of revs
unsigned long total;        //total mm
float mph = 0.0;            //calculated mph
unsigned int avg[4];        //array for averaging speed
unsigned int outspeed;      //speed output to screen
volatile boolean state = false;  //check for if sensor has been tripped
float miles = 0;
unsigned long dist1;
unsigned long dist2;

void setup()
{
  pinMode (SensorPin,INPUT);       //enable input sensor
  digitalWrite (SensorPin, HIGH);  //turn on pull up resistor
/*
  Serial.begin(9600);              //Code for setting LCD to 38400 baud. LCD stores speed in EEPROM, so reconfig shouldn't be necessary.
  Serial.print(124, BYTE);
  Serial.print(16, BYTE);
  delay(500);
*/
  Serial.begin(38400);
  Serial.print(0xFE, BYTE);        //Command byte
  Serial.print(0x01, BYTE);        //Clear screen
  attachInterrupt(0,sense,LOW);
}


void loop()
{
  if(state == true && (millis() - time) > 40)    //check if the thing's been tripped, and also debounce the input (40ms = approx 250mph).
  {
    time = millis();          //set the time the interrupt occured
    count = count + 1;        //update the count number
    display();                //run the display routine to throw stuff up onto the screen
  }

  if(state == true)           //if it's still true...
  {
    state = false;            //falsify state
  }
} 

//----------------------------------------------------------------------------

void display() 
{
  milsecs = time - time2;          //calculate the difference between the current time and the last time it was tripped
  time2 = time;                    //reset the tripped time
  total = (total + circ);          //add the circumference to the current total..
  mph = (circ / milsecs) * 2.2369; //calculate the speed
  //miles = (total / 1609344);
  dist1 = (unsigned long)(total / 16093);
  dist2 = (unsigned long)(total / 16093);
  dist2 = (unsigned long)((unsigned long)dist2 * (unsigned long)(100));
  avg[0] = avg[1];                 //shift storage array
  avg[1] = avg[2];                 //    "
  avg[2] = avg[3];                 //    "
  avg[3] = int(mph);               //insert current calculated mph
  outspeed = ((avg[0] + avg[1] + avg[2] + avg[3]) / 4);    //average the speed over the last four readings (mean)
  Serial.print(0xFE, BYTE);        //Command byte
  Serial.print(0x01, BYTE);        //Clear screen
  Serial.print("Dist: ");          //Show time
  //Serial.print(total);           //   "
  Serial.print(dist1);
  Serial.print(".");
  Serial.print(dist2);
  Serial.print(0xFE, BYTE);        //Command byte
  Serial.print(192, BYTE);         //Move to 2nd line
  //Serial.print("MPH: ");           //Show speed
  //Serial.print(outspeed);          //   "
  Serial.print(total);
}

//----------------------------------------------------------------------------

void sense()
{
  state = true;
}

What happens if you remove all those casts and just write:

dist2 = dist2 * 100UL;

It should work without the casts, but that is not the problem

Try this in your display routine:

unsigned long dist1 = total / 16093UL; // declare these variables in display if they are only used in this routine
unsigned long distFrac = ((total * 100UL) / 16093) - (dist1 * 100);

Serial.print("Dist: ");
Serial.print(dist1);
Serial.print(".");
Serial.print(distFrac);

You don't need the casts. I have added the letters UL after the constants to clarify that these constants are treated as unsigned longs. This isn't really necessary as the compiler will promote all numeric values in an expression to the largest type in the expression, but may be helpful to people reading the code.

That seems to calculate out properly. Was it declaring the variables within the function?

The only thing is that distFrac displays numbers only while dist1 = 0. If dist1 => 1, then distFrac stays at 0.

Declaring the variables in the function does not affect the calculations, it just helps the compile to make sure that noting that its not supposed to use those variables wont access them by mistake. It also helps people reading code to understand the scope of the variables.

distFrac displays numbers only while dist1 = 0. If dist1 => 1, then distFrac stays at 0.

Strange, I just tested it with the following values:
total= 120000;
unsigned long dist1 = total / 16093UL;
unsigned long distFrac = ((total * 100UL) / 16093) - dist1 * 100;

Serial.print("Dist: ");
Serial.print(dist1);
Serial.print(".");
Serial.print(distFrac);

And got the following output:
Dist: 7.45

Well, having it uploaded to the arduino and displaying on the LCD here, I see

Total = 121510
output on display= 7.0

and with different values..

Total = 10475
output on display=0.6

[EDIT]
If i manually set total to 120000, it displays 7.45 on the LCD.

If you can post your latest version with just serial output, I will try it here and see if I can find out why we are getting different results.

this code:

void display()
{
  total=  121510;
  unsigned long dist1 = total / 16093UL;
  unsigned long distFrac = ((total * 100UL) / 16093) - dist1 * 100;
  
  Serial.print("Dist: ");          //Show time
  Serial.print(dist1);
  Serial.print(".");
  Serial.print(distFrac);
}

printed:
Dist: 7.55

Here's the latest version. The line to manually set total to 120000 is commented out, I just tested it and if I uncomment that line, the display shows 7.45.

[EDIT]
Just tried it with manually specifying the 121510, and that is calculated properly and displays Dist: 7.55

//Tire circumference in millimeters
const unsigned long circ = 2095;


const byte SensorPin = 2;   //Input pin for speed sensor
unsigned long time;         //last time sensor was tripped
unsigned int milsecs;       //this will overflow after 64 seconds or so between hits, but that doesn't matter, only need it for sub-second timings
unsigned long time2;        //time before last sensor was tripped
unsigned int count;         //number of revs
unsigned long total;        //total mm
float mph = 0.0;            //calculated mph
unsigned int avg[4];        //array for averaging speed
unsigned int outspeed;      //speed output to screen
volatile boolean state = false;  //check for if sensor has been tripped
float miles = 0;


void setup()
{
  pinMode (SensorPin,INPUT);       //enable input sensor
  digitalWrite (SensorPin, HIGH);  //turn on pull up resistor
/*
  Serial.begin(9600);              //Code for setting LCD to 38400 baud. LCD stores speed in EEPROM, so reconfig shouldn't be necessary.
  Serial.print(124, BYTE);
  Serial.print(16, BYTE);
  delay(500);
*/
  Serial.begin(38400);
  Serial.print(0xFE, BYTE);        //Command byte
  Serial.print(0x01, BYTE);        //Clear screen
  attachInterrupt(0,sense,LOW);
}


void loop()
{
  if(state == true && (millis() - time) > 40)    //check if the thing's been tripped, and also debounce the input (40ms = approx 250mph).
  {
    time = millis();          //set the time the interrupt occured
    count = count + 1;        //update the count number
    display();                //run the display routine to throw stuff up onto the screen
  }

  if(state == true)           //if it's still true...
  {
    state = false;            //falsify state
  }
} 

//----------------------------------------------------------------------------

void display() 
{
  milsecs = time - time2;          //calculate the difference between the current time and the last time it was tripped
  time2 = time;                    //reset the tripped time
  total = (total + circ);          //add the circumference to the current total..
  mph = (circ / milsecs) * 2.2369; //calculate the speed
  //miles = (total / 1609344);
  //total = 120000;

  unsigned long dist1 = total / 16093UL;
  unsigned long distFrac = ((total * 100UL) / 16093) - dist1 * 100;

  avg[0] = avg[1];                 //shift storage array
  avg[1] = avg[2];                 //    "
  avg[2] = avg[3];                 //    "
  avg[3] = int(mph);               //insert current calculated mph
  outspeed = ((avg[0] + avg[1] + avg[2] + avg[3]) / 4);    //average the speed over the last four readings (mean)
  Serial.print(0xFE, BYTE);        //Command byte
  Serial.print(0x01, BYTE);        //Clear screen
  Serial.print("Dist: ");          //Show distance
  Serial.print(dist1);
  Serial.print(".");
  Serial.print(distFrac);
  Serial.print(0xFE, BYTE);        //Command byte
  Serial.print(192, BYTE);         //Move to 2nd line
  //Serial.print("MPH: ");           //Show speed
  //Serial.print(outspeed);          //   "
  Serial.print(total);
}

//----------------------------------------------------------------------------

void sense()
{
  state = true;
}

hmm, 7.45 is the correct result.

Comment out the 120000 and add in a line to print total and see what you get.

I'm printing total, and the 120000 line is already commented out. As i edited in above, it seems if i set the variable to a constant, the calculation works, but if it's set with the previous calculation, it does not.

I should add that i also had issues calculating Total * Count, so i had to go to adding the circumference to total for each display cycle.

So, is the total value you print correct?

edit: Shouldn't your count increment on each interrupt and the total be equal to count * circumference.

yes, the total value is correct. It increments by circ each time display() is called.

edit: Yes, however, when I multiply count * circ, I get random numbers. Each time i restart the board they start out at things like 583, 604, etc.
i.e. commenting out total = (total + circ); and adding total = count * circ;
will yield random numbers coming out that grow almost exponentially.

Looks like you have two issues that need to be looked at.

One is to verify that you are counting the revolutions correctly.

Then you can verify that the total distance is being calculated correctly.

I suggest you focus on just printing the count and getting that correct. Once that is working you can see if everything else falls into place.

what kind of sensor are you using to detect revolutions?

A simple reed switch. I'm pretty sure count is being updated properly. I swapped printing total for printing count, and it's counting normally. 1,2,3,4,5,6, and so on for each click. I'm not sure if it has to do with data types or something, but the calculations just go all wonky.

Chat about this instead of the forums? http://www.chatmaker.net/chatap/rooms/arduinoz/

Well, we haven't been able to get the issue resolved yet. I've resorted to writing a test program that only does calculations to see if I can figure out where the issue is. Apparently mem has been able to compile a test program and have it operate correctly, but mine still isn't outputting right, so I'm going to spend some more time on this and see what I can find. I'll keep the topic updated.

[EDIT]
Figured out why the multiplication of count * circ wasn't working, there was an issue with type casting. One was an int, the other a long, and they did not play nice. Changing them both to longs fixed that issue.
Still working on the other problems, cleaning up code in the process.

Alright, this is driving me insane. Here's a very small bit of code that exhibits the issue I'm having.

unsigned long tire = 2095;
unsigned long revs = 10;

void setup()
{
  Serial.begin(38400);
}


void loop()
{
  unsigned long totalmm = revs * tire;
  unsigned long distFrac = totalmm / 16093;
  distFrac = distFrac * 10;
    
  Serial.print(0xFE, BYTE);        //Command byte
  Serial.print(0x01, BYTE);        //Clear screen
  Serial.print(distFrac);
  Serial.print("...");
  Serial.print(totalmm);
  delay(1000);
}

This code does not work. distFrac * 9 = 9. distFrac * 10 = 65536.

I cannot for the life of me figure out why.

I was just about to log off when I saw your post.

I ran this code:

unsigned long tire = 2095;
unsigned long revs = 10;

void setup()
{
  Serial.begin(38400);
}


void loop()
{
  unsigned long totalmm = revs * tire;
  unsigned long distFrac = totalmm / 16093;
  Serial.print(distFrac);
  distFrac = distFrac * 10;
 Serial.print(",");
  Serial.print(distFrac);
  Serial.print("...");
  Serial.println(totalmm);


  totalmm = revs * tire;
  distFrac = totalmm / 16093;
  Serial.print(distFrac);
  distFrac = distFrac * 9;
  Serial.print(",");  
  Serial.print(distFrac);
  Serial.print("...");
  Serial.println(totalmm);
  delay(1000);
}

and got this result:

1,10...20950
1,9...20950

that looks right to me. Try that code and see what you get. If you don't get the correct answer I would consider (re)installing arduino version 0012

distFrac = totalmm / 16093;

20950 / 16093 = 1,3

I copied and pasted that code in to the code window and uploaded, and got

1,1...20950

1,9...20950

I'm running under linux.. does that make a difference?

Gonna reboot into windows and give that a shot. see if there's any change.

[EDIT]
WEEEEEL THAT CHANGED THINGS

Things seem to be calculating properly now. Looks like the linux version is broke :frowning:

[EDIT 2]
And after more testing, all other math-related issues seem to have disappeared when i upload with windows instead of linux. Guess there's an issue here.

I think
1,10...20950
1,9...20950
Is correct.

What if:
unsigned long distFrac = totalmm / 16093;
became:
unsigned long distFrac = totalmm / (unsigned long)16093;

the '65,536' looks like an unsigned int overflow + 1.

Hmmm.. Need too sleep! on my 31st hour ::slight_smile:

Good luck on this one!