What is the right way to find the average of two values?

后端 未结 8 1170
北荒
北荒 2021-02-19 00:58

I recently learned that integer overflow is an undefined behavior in C (side question - is it also UB in C++?)

Often in C programming you need to find the average of two

相关标签:
8条回答
  • 2021-02-19 01:04

    Simplest answer if there is only 2 elements to avoid overflow would be:

    (a/2) + (b/2) = average
    

    For more elements, you could use:

    (a/x) + (b/x) + (c/x) + (d/x) ..... = average //x = amount of elements
    

    From a mathematical point of view, this will never reach an overflow if none of the original values has done so previously, as you do not really add them all together, but rather dividing them before adding them together. Thus no result of any operation performed during the calculation, including the result, will ever be larger (to either side of 0) than the largest initial element (assuming you only work with Real Numbers).

    So do the following:

    1. Determine the amount of elements in 'C', let's call it total.
    2. Declare a value to store the average, let's call it average.
    3. Declare a value to store the remainders, let's call it remainder.
    4. Iterate through them and:
      • Divide the current element by the total amount, total.
      • Add the result to the average, average.
      • Add the remainder of the divided values together, remainder.
    5. Divide the remainders as well and add it to the average, average.
    6. Do with the average what you need/intend to.

    This will give you an answer off by a maximum of 1 (Decimal numeric system [base 10]). I don't know C++ yet, so I can only give you an example in C#.

    Pseudo code in C# (just to provide an idea):

    int[] C = new int[20];            //The array of elements.
    int total = C.Length;             //The total amount of elements.
    int average = 0;                  //The variable to contain the result.
    int remainder = 0;                //The variable to contain all the smaller bits.
    foreach (int element in C)        //Iteration
    {
        int temp = (element / total); //Divide the current element by the total.
        average = average + temp;     //Add the result to the average.
        temp = (temp % total);        //Get the remainder (number that was not divided)
        remainder = remainder + temp; //Add remainder to the other remainders.
    }
    average = average + (remainder / total); // Adds the divided remainders to the average.
    

    Compressed C# example:

    int[] C = new int[20];               //The array of elements.
    int total = C.Length;                //The total amount of elements.
    int average = 0;                     //The variable to contain the result.
    int remainder = 0;                   //The variable to contain all the smaller bits.
    foreach (int element in C)           //Iteration
    {
        average += (element / total);    //Add the current element divided by the total to the average.
        remainder += ( element % total); //Add the remainders together.
    }
    average += (remainder / total); //Adds the divided remainders to the total.
    
    0 讨论(0)
  • 2021-02-19 01:06

    If you are concerned about overflow, you could cast the values to a larger type to perform the math, and then do the bounds checking.

    0 讨论(0)
  • 2021-02-19 01:07

    This is from Calculating the average of two integer numbers rounded towards zero in a single instruction cycle:

    (a >> 1) + (b >> 1) + (a & b & 0x1)
    

    You must consider that:

    • it's implementation defined whether right shifting a negative integer shifts zeros or ones into the high order bits. Many CPUs often have two different instructions: an arithmetic shift right (preserves the sign bit) and a logical shift right (doesn't preserve the sign bit). The compiler is allowed to choose either (most compilers choose an arithmetic shift instruction).

      ISO/IEC 9899:2011 §6.5.7 Bitwise shift operators

      ¶5 The result of E1 >> E2is E1 right-shifted E2 bit positions. [CUT] If E1 has a signed type and a negative value, the resulting value is implementation-defined.

      Changing the expression to:

      a / 2 + b / 2 + (a & b & 0x1)
      

      isn't a solution since logical right shifts are equivalent to division by a power of 2 only for positive or unsigned numbers.

    • also (a & b & 0x1) isn't well defined. This term should be non-zero when both a and b are odd. But it fails with one's complement representation and ISO C, section 6.2.6.2/2, states that an implementation can choose one of three different representations for integral data types:

      • two's complement
      • one's complement
      • sign/magnitude

      (usually the two's complement far outweigh the others).

    0 讨论(0)
  • 2021-02-19 01:11

    If you only have to deal with unsigned integer types (and can think in binary), you can decompose your addition into digit and carry. We can write a+b (in unlimited precision) as (a^b) + ((a&b)<<1)), so (a+b)/2 is simply ((a^b)>>1) + (a&b). This last expression fits within the common type of a and b, so you can use it in your code:

    unsigned semisum(unsigned a, unsigned b)
    {
        return ((a^b)>>1) + (a&b);
    }
    
    0 讨论(0)
  • 2021-02-19 01:13

    The simplest (and usually fastest) way to average two int over the entire range [INT_MIN...INT_MAX] is to resort to a wider integer type. (Suggested by @user3100381.) Let us call that int2x.

    int average_int(int a, int b) {
      return ((int2x) a + b)/2;
    }
    

    Of course this obliges a wider type to exist - so let's look at a solution that does not require a wider type.

    Challenges:

    Q: When one int is odd and the other is even, which way should rounding occur?
    A: Follow average_int() above and round toward 0. (truncate).

    Q: Can code use %?
    A: With pre-C99 code, the result of a % 2 allows for different results when a < 0. So let us not use %.

    Q: Does int need to have about symmetric range of positive and negative numbers?
    A: Since C99 the number of negative numbers is the the same (or 1 more) than the number of positive numbers. Let us try not to require this.

    SOLUTION:

    Perform tests to determine is if overflow may occur. If not, simple use (a + b) / 2. Otherwise, add half the difference (signed same as answer) to the smaller value.

    The following gives the same answer as average_int() without resorting to a wider integer type. It protects against int overflow and does not require INT_MIN + INT_MAX to be 0 or -1. It does not depends on encoding to be 2's complement, 1's complement or sign-magnitude.

    int avgC2(int a, int b) {
      if (a >= 0) {
        if (b > (INT_MAX - a)) {
          // (a+b) > INT_MAX
          if (a >= b) {
            return (a - b) / 2 + b;
          } else {
            return (b - a) / 2 + a;
          }
        }
      } else {
        if (b < (INT_MIN - a)) {
          // (a+b) < INT_MIN
          if (a <= b) {
            return (a - b) / 2 + b;
          } else {
            return (b - a) / 2 + a;
          }
        }
      }
      return (a + b) / 2;
    }
    

    At most, 3 if()s occur with any pair of int.

    0 讨论(0)
  • 2021-02-19 01:20

    With help from Secure Coding

    if (((si_b > 0) && (si_a > (INT_MAX - si_b))) ||
        ((si_b < 0) && (si_a < (INT_MIN - si_b))))
    {
      /* will overflow, so use difference method */
      return si_b + (si_a - si_b) / 2;
    } 
    else
    {
     /* the addition will not overflow */
      return (si_a + si_b) / 2;
    }
    

    ADDENDUM

    Thanks to @chux for pointing out the rounding problem. Here's a version that's tested for correct rounding...

    int avgnoov (int si_a, int si_b)
    {
        if ((si_b > 0) && (si_a > (INT_MAX - si_b)))
        {
          /* will overflow, so use difference method */
          /* both si_a and si_b > 0; 
              we want difference also > 0
              so rounding works correctly */
          if (si_a >= si_b)
            return si_b + (si_a - si_b) / 2;
          else
            return si_a + (si_b - si_a) / 2;
        } 
        else if ((si_b < 0) && (si_a < (INT_MIN - si_b)))
        {
          /* will overflow, so use difference method */
          /* both si_a and si_b < 0; 
              we want difference also < 0
              so rounding works correctly */
          if (si_a <= si_b)
            return si_b + (si_a - si_b) / 2;
          else
            return si_a + (si_b - si_a) / 2;
        }
        else
        {
         /* the addition will not overflow */
          return (si_a + si_b) / 2;
        }
    }
    
    0 讨论(0)
提交回复
热议问题