Absolute difference of millis() comparison

I want to detect if 2 keys are pressed within a certain time frame, thus detecting if they are pressed simultaneously or not.
For this, I store the millis() when a key is pressed in uint32_t values key1millis and key2millis.

Then I calculate the absolute difference of these 2 unsigned values.
It turned out a little tricky to get the correct results, and I am wondering if it could be done better.

Here is the function code I am using:

const uint32_t MAXDIFF     = 0xFFFFFFFF / 2 ;
const uint16_t MAXTIMEDIFF = 100 ;              // max 100ms delay between key1 and key2

uint32_t getABSdiff( uint32_t X, uint32_t Y ) 
{   uint32_t               Diff = X - Y ;
    if( Diff > MAXDIFF )   Diff = Y - X ;
    else
    { if( X < Y )          Diff = Y - X ;
      if( Diff > MAXDIFF ) Diff = X - Y ;
    }
    return( Diff ) ;
}

if( getABSdiff( key1millis, key2millis ) < MAXTIMEDIFF )
    Serial.println( "simultaneous" ) ;

Isn't that what the abs() function is for ?

Yes, abs() will fix you up.

What might come as a surprise is that the difference between two unsigned variables can be negative.

a7

abs() doesn't work on unsigned values.
The trick is that unsigned arithmetic needs to handle the value overflow correctly.
My code works, but I am wondering if it is the best performing solution to the problem.

abs( 20 - 80 ) on an uint32_t gives 4294967236 and not the correct result of 60.

Only because there are no negative unsigned numbers.

Try this. It converts the difference to a signed number to see if there was an underflow on subtraction. If there was, reverse the subtraction.

uint32_t getABSdiff( uint32_t X, uint32_t Y )
{
  int32_t diff = X - Y;  // Signed difference
  if (diff < 0)          // If the result is negative
    diff = Y - X;        // Positive difference
  return diff;           // Convert back to unsigned
}

Great, this does the trick and is definitely easier.

@artisian Could you please write the code in a normal way ? The way you have it does not show what the code is doing.
This is your function written in a more normal way.

uint32_t original( uint32_t X, uint32_t Y )
{   
  uint32_t Diff = X - Y;

  if( Diff > MAXDIFF )
  {
    Diff = Y - X;
  }
  else
  {
    if( X < Y )
      Diff = Y - X;
    if( Diff > MAXDIFF )
      Diff = X - Y;
  }
  return( Diff );
}

When writing the text in a more normal way, it becomes instantly clear that some lines are unnecessary. The code by @johnwasser is your function without those unnecessary lines.

You could test in code if the other button is already pressed.
The millis() rolls over every 50 days. What if someone pressed the button more than 50 days ago, then the calculation will certainly go wrong. What if the button is stuck and it takes 50 days before things go wrong ?

I think it is possible to calculate both subtractions and discard the one that is wrapped around.

unsigned long d1 = t2 - t1;
unsigned long d2 = t1 - t2;
unsigned long elapsedMillis = d1;
if( d2 < d1)
  elapsedMillis = d2;

All three calculations in Wokwi:

Sry, I did not mean to mislead…

This

# include <stdio.h>
# include <stdlib.h>

unsigned int a, b;

int main()
{
    printf("Hello World\n\n");
    
    a = 20; b = 80;
    
    printf("%ld \n", abs((long) a - b));

    return 0;
}

works but the below does not, I cannot explain that.

unsigned long a, b;

void setup()
{
	Serial.begin(115200);
    Serial.print("Hello World\n\n");
    
    a = 20; b = 80;
    
    Serial.print( abs((long) a - b));
}

void loop(){}

So I should run my test on the Arduino before I run my mouth.

a7

I can. Conversion is done 'length first' so when using unsigned int, the unsigned int result of (a-b): 0xFFC4 (65476) gets extended to 0x0000FFC4 (65476L) and then changed to 'signed long': (65476L). The absolute value is 65476. The unsigned long result of (a-b): 0xFFFFFFC4 (4294967236UL) doesn't need extending so it just changes to a 'signed long': -60L. The absolute value is 60.

If a and b were 'int': the int result of (a-b): 0xFFC4 (-60) gets sign-extended to 0xFFFFFFC4 (-60L) and then changed to 'signed long': (-60L). The absolute value is 60.

1 Like

@Koepel
Your code is also a good solution.
I did some examples and found that the dynamic space requirements do not differ between the solutions.
My code is inefficient though and requires 52 bytes more than @johnwasser's solution and 26 bytes more than your solution.
So John's solution is the most efficient.

@johnwasser THX.

In addition to meaning to use long in both programs, the other thing I failed, once again, to consider, is that the length of an int differs from machine to machine.

I was tots surprised to find one of my experiments yielding a negative number from abs(), no doubt a bit of scrutiny along the lines you have provided might clear that up, too, as strange as it seems.

So my takeaway is trust verify that this kind of cackulation is doing what and only that which you want.

No doubt in the world this will trip me up again.

Meanwhile, look what my friend google found for me: labs()

unsigned long a, b;

void setup()
{
	Serial.begin(115200);
    Serial.print("Hello World\n\n");
    
    a = 20; b = 80;
    
    Serial.print(labs((long) a - b));
}

void loop(){}

Add to the takeaway: use functions that are more like designed for the arguments you are passing to them.

a7

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.