A little DATA types help - negative numbers give unexpected results

Dear learned crew,

I am developing a project which will prevent a 40A breaker from tripping by measuring the current being drawn, and thereby calculating how much capacity is available to use on an electric heating system. I am developing it in small parts, adding functionality to it at each stage. For reasons of speed I have tried to use integer maths rather than floating point maths, as recommended in the Arduino.cc reference section.

Today I appear to be having problems around the result of this sum:

int maxCurrent = ((tripCurrent - rmsCurrent)/1000);  //use this value to CONSTRAIN the finalOutput below

tripCurrent and rmsCurrent are both unsigned integers, because each can be greater than 32,768, but never negative.
maxCurrent is a plain old integer, because its value will never exceed 40, but may have a brief negative value in reality (see serial monitor output below).

  1. Please will someone indicate where I am going wrong, and/or suggest a correction, preferrably sticking to integer maths.

  2. On a different subject, refering to the function at the end of my full code below, I tried to make a local variable int SP = 60; but that would not compile. This is strange, because a similar variable int MV = 61; did compile.
    What could be the reason for this apparent anomaly?

The results when rmsCurrent > tripCurrent are incorrect, as shown in the clip from the serial monitor, shown below:

14:03:26.201 -> Irms = 42.27
14:03:26.201 -> rmsCurrent = 42265
14:03:26.201 -> 40000
14:03:26.201 -> 42265
14:03:26.201 -> Output = 4
14:03:26.201 -> maxCurrent = 63 but (40000-42265)/1000 = -2 as an integer
14:03:26.255 -> Final Output = 4
14:03:26.255 ->
14:03:31.467 -> Irms = 16.60
14:03:31.467 -> rmsCurrent = 16604
14:03:31.514 -> 40000
14:03:31.514 -> 16604
14:03:31.514 -> Output = 4
14:03:31.514 -> maxCurrent = 23
14:03:31.514 -> Final Output = 4

For those who wish to view it, the full code is below. Some is commented out and some variables are given arbitrary values for testing purposes - they will be replaced with sensors indue course. Nothing is connected to analogue input 0, so the value of rmsCurrent floats around - also useful for proving the maths

#include "EmonLib.h"            //external libraries have " ", internal have < >
EnergyMonitor emon1;

int output;



void setup() 
{
Serial.begin (9600);
pinMode (A0,INPUT);
emon1.current(0,50);
}

void loop()
{
double Irms = emon1.calcIrms (1480);
unsigned int rmsCurrent = Irms * 1000;
Serial.print ("Irms = ");
Serial.println (Irms);
Serial.print ("rmsCurrent = ");
Serial.println (rmsCurrent);

const unsigned int tripCurrent = 40000;                                //40A trip value, *1000 to use integer maths
int maxCurrent = ((tripCurrent - rmsCurrent)/1000);  //use this value to CONSTRAIN the finalOutput below
calcOutput ();                                                //calls function to calc heating output before CONSTRAINing it
int finalOutput = constrain (output, 0 , maxCurrent);
Serial.println (tripCurrent);
Serial.println (rmsCurrent);
Serial.print ("Output = ");
Serial.println (output);
Serial.print ("maxCurrent = ");
Serial.println (maxCurrent);
Serial.print ("Final Output = ");
Serial.println (finalOutput);
Serial.println();
delay (5000);
}

void calcOutput ()
{
  int setPoint = 60;                  //will be modulated by outsideTemp in future version
  int MV = 61;                  //will be measured by DS18B20 sensor in future version
  int deviation = (setPoint - MV);    //could also be a negative number
  int span = 50;                //equals maxTempSetting (75C)minus minTempSetting (25C)
  const int offset = 20;        //equals 20% output when MV = setPoint
  const int gain = 8;           //equivalent to 12.5% proportional band
  
  output = (gain * deviation *100 / span) + offset;
}

In 16-bit unsigned math, 40000-42265 = 63271.
Integer divide that by 1000 and you get 63. Only then is it converted to a signed integer and assigned to maxCurrent.

Why not make your life easier? Just define all your variables as type 'int32_t'. That way, you can cover the (approximate) range -2,000,000,000 to +2,000,000,000.

gfvalvo:
In 16-bit unsigned math, 40000-42265 = 63271.
Integer divide that by 1000 and you get 63...

Why not make your life easier? Just define all your variables as type 'int32_t'.

And that, my friends, is why I love coming to this forum. Thank you and (until the next stupid question) goodnight. I have some reading up to do, on this int32_t thingumybob.

As an aside, any thoughts on the 'int SP = 60;' problem?

GM

Glorymill:
I have some reading up to do, on this int32_t thingumybob.

On an Arduino, it's the same thing as a "long" datatype. But, by explicitly specifying it's size (32 bits), it shows that you're actually thinking about the range it provides. See: Fixed width integer types (since C++11) - cppreference.com

As an aside, any thoughts on the 'int SP = 60;' problem?

It would be helpful to know the compiler error.

long, -2^31 to 2^31 -1

Or go unsigned long, 0 to 2^32 -1

I'll guess that SP is already defined and the compiler is complaining about your attempt to change it. Try a different name.

wildbill:
I'll guess that SP is already defined and the compiler is complaining about your attempt to change it. Try a different name.

Hence my request for the compiler error.

gfvalvo:
Hence my request for the compiler error.

I did try a different name 'setPoint', and that worked without the complaint. I have changed it back to 'SP', and the compiler complaint reads:

In function 'void calcOutput()':

Cizek_heating_development_no1_27May2020:42:12: error: lvalue required as left operand of assignment

int SP = 60; //will be modulated by outsideTemp in future version

^~

exit status 1
lvalue required as left operand of assignment

I hope that helps. The code for the function now reads:

void calcOutput ()
{
  int SP = 60;                  //will be modulated by outsideTemp in future version
  int MV = 61;                  //will be measured by DS18B20 sensor in future version
  int deviation = (SP - MV);    //could also be a negative number
  int span = 50;                //equals maxTempSetting (75C) minus minTempSetting (25C)
  const int offset = 20;        //equals 50% output when MV = setPoint
  const int gain = 8;           //equivalent to 12.5% proportional band
  
  output = (gain * deviation *100 / span) + offset;

with the compiler error as above.

Assuming you're using an UNO or similar AVR-based board, it looks like 'SP' is a macro defined to allow access to the processor's Stack Pointer. Don't mess with it.

Thanks gfvalvo and other contributors; I promise I won't twiddle with that scary Stack Pointer.

It is noteworthy to mention that the change to int32_t data types has solved the maths anomaly, though maxCurrent can become negative if rmsCurrent is greater than tripCurrent. As the output will be on a heating application I can use some code to recognise a non-positive number and set it to zero, so I foresee no problems there.

It is interesting to see that CONSTRAIN between 0 and a negative number (ie constrain (output, 0 , -19) returns -19 as the minimum value,and not 0. I'll have to remember that one.

GM