Abs() function arguments understanding, but other too

I am learning coding. it took me unfortunatelly long to discover (in longer program - that below example ilustrates problem) that it works differently than I expected.
please consider:

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

void loop() {

unsigned int iG = millis();
  
int iGn = abs(((iG) % 100)) - 100);

//iGn = abs(iGn);
Serial.print((iGn));
Serial.println();

}

what suprised me that printed values are also negative. I expected that it will be never negative.
if commented above line will be uncommented - will work as I expected also without it.

I understand that there are limitations for arguments, and seems that I exceeded with this "-100"
Why compiler does not warn that it is too much?
how do you know what is possible to put as arguments inside a function? I suppose reading docs :slight_smile:
After painfull time looking for bug i know now that for abs() we have in documentation warning:" Notes and Warnings

Because of the way the abs() function is implemented, avoid using other functions inside the brackets, it may lead to incorrect results."

This is the only way ? or I am missing some warning mechanism?

Because the compiler can not predict what the code will do when it is run. It only checks syntax.

Also you declared the variable as unsigned and therefore asking for the absolute value of an unsigned number makes no sense.

You are taking the abs() of this

and then subtracting 100.

I must say this is weird...
...and you should be proud you dug in deep to find the root cause...

No, op does not do that.

The difference seems to be 100 between original and without "-100"

abs(((iG) % 100)) - 100 --> -67  (diff:100)  33 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -65  (diff:100)  35 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -64  (diff:100)  36 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -64  (diff:100)  36 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -63  (diff:100)  37 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -62  (diff:100)  38 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -61  (diff:100)  39 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -60  (diff:100)  40 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -59  (diff:100)  41 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -59  (diff:100)  41 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -58  (diff:100)  42 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -57  (diff:100)  43 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -55  (diff:100)  45 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -55  (diff:100)  45 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -54  (diff:100)  46 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -53  (diff:100)  47 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -53  (diff:100)  47 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -51  (diff:100)  49 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -50  (diff:100)  50 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -49  (diff:100)  51 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -49  (diff:100)  51 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -48  (diff:100)  52 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -46  (diff:100)  54 <-- abs(((iG) % 100))
abs(((iG) % 100)) - 100 --> -46  (diff:100)  54 <-- abs(((iG) % 100))

You might be right. But how can the compiler accept more ) than (?

1 Like

Ah... thank you, and sorry, I thought I added that ")" My compiler did not like/allow it.

Question to OP, is your code exactly equal to the code in post#1??? And does it compile without warnings?

If op sees very large numbers and his code is:
iGn = abs(((iG) % 100) -100)

My guess is that it puts iG in a register.
It takes the modulo and puts that in the same register (which is still regarded as unsigned int). Now it subtracts 100.
The unsigned int underflows to a large positive number. The absolute value of a positive numver is a positive number...
What happens if you do:
iGn= abs((int(iG) % 100) - 100);

Otherwise see explanation of xfpd...

Whatever you are doing in your longer program is likely to fail shortly after the first minute of execution. You also need to read the documentation on the millis() function

Why would this fail? Op will just loose the first two bytes returned by millis.
(Implicit cast from uint32_t to uint16_t).

We cannot tell until we know more about what the longer program does with that variable.

Thank you for all input.

Code I pasted contains not intentional mistake with missing bracket, I am really sorry for confusing, but it was result of many iterations to understand the topic.

in first post this line which suprised me should be from begining like below.

int iGn = abs((((iG) % 100)) - 100);

so substraction "-100" was also inside "abs()" function, but returned value is also below zero. That was my problem. It took long to discover that. No warning from compiler that "potentially to much inside abs()"

As I said, I understand that these tiny micros have limits - related to limited memory/registers/stack/whatever resources. My primary question is how do you know what is limit of nesting, if there is a mechanism to warn of possible trouble like my example?

Yes, basically exactly what you did:
Read the documentation.
If it says "do not put a function here", than it is best not to do so...
What values did you get? Just curious...

Must admit, it would have taken me a lot of time as well...

you asked what values it produced.
I dont know now exaclty, it was part of longer problem story...
presented short sketch is only ilustrating the problem, and honestly, I am really not good at programming so there is nothing to be proud to share... but thanks to community - I am learning... I believe... :slight_smile:
seems that RTFM will never die.

Values close to 65535?

Could be. I just don’t really know exact values.
You probably think about messing with signed and unsigned int. I can’t meaningfully say. Although before I found this abs() issue, that was also on my mind. I suspected this kind of mistake is somewhere in my code.

Here is the deal:

To begin with, abs() is not a function. It is a macro that uses the conditional (ternary) operator:

  #define abs(x) ((x)>0?(x):-(x))

The instruction under analysis then becomes

iGn = (iG%100-100) ? (iG%100-100) : -(iG%100-100);

Now, iG is declared as an unsigned int. The expression whose absolute value we are calculating, iG%100-100, is evaluated using integer arithmetic and its result is interpreted as an unsigned int.

In the signed int realm, this expression can take values from -100 to -1. In the unsigned int world, it is always positive (65437 to 65535, if you care to know).

A positive value is then used in the conditional operation to select either the first result or the second result. Always.

The conditional operation returns the first result. Always.

This value is then copied to iGn, which is a regular 16-bit integer that can have values from -32768 to +32767. That is when the negative sign appears.


Suppose iG gets from millis()* a value of 12345. The expression iG % 100 - 100 yields 65481 as an unsigned integer, (-55 using signed integer arithmetic).

* A variable getting its value from millis() should be declared as unsigned long. Unless you are ready for more fun.

Our expression becomes

iGn = 65481 > 0 ? 65481 : -65481.

Understandably, the result is the first possibility, 65481. When you assign this number to a 16-bit integer, you get a negative number! ( the bits are the same, its interpretation is not).

If in doubt about this try the following:


void setup() {
  Serial.begin(115200);
  Serial.println((unsigned int)(65481));
  Serial.println((int)(65481));
}

void loop() {
}

I must say I learned a lot trying to figure this one out, and I was able to keep some of my hair in place.

5 Likes

Exactly. Note that I pointed this out in post#2.

But you went into much greater detail about this, thanks.

2 Likes