The average of three byte values

exercise No. 227

Q:

A newbie programmer codes the following snippet:

/**
 * The average of three values
 *
 * @param a First value
 * @param b Second value
 * @param c Third value
 *
 * @return Closest <code>byte</code> value to ⅓ (a + b + c)
 */
public static byte getAverage(byte a, final byte b, final byte c) {
   a += b;
   a += c;
   a /= 3;
   return a;
}

A senior programmer warns about two possible problems:

  1. The implementation is prone to overflow errors.

  2. The implementation will in many cases return unnecessarily inaccurate results.

Explain both points in the answer box below.

Then provide an improved implementation in the answer box. Do this without changing the method's signature .

Tip

Math.round(double) might be your friend.

A:

  1. Biggest problem here: The operator +='s way of cycling through a byte's range when exceeding Byte.MAX_VALUE or Byte.MIN_VALUE boundaries. Consider:

    Code Output
    byte b = 127;
    b += 1;
    System.out.println("Result: " + b);
    Result: -128
    byte b = -128;
    b += -1;
    System.out.println("Result: " + b);
    Result: +127
    // Expecting 129 / 3 == +43
    System.out.println("Result: " +
      getAverage((byte) 127, (byte) 1, (byte) 1));
    Result: -42
  2. Converting fractions to byte values is next on the list of troubles. The /= operator will simply cut off fractional values rather then rounding them properly.

    Switching to real values we have 1 3 ( 1 + 1 + 0 ) = 0.66... . Our byte valued method should thus return a value of 1 rather than 0. However:

    Code Output
    System.out.println("Result: " + 
      getAverage((byte) 1, (byte) 1, (byte) 0));
    Result: 0

Solving these flaws requires using a data type behaving free of overflow errors with respect to the given context of summing up three byte values. Choosing short is sufficient for this purpose.

Dividing by 3 should be a floating point rather than an integer operation to avoid cutting off fractional values. We subsequently use the Math.round(double) method thereby avoiding rounding errors.

Finally we need a cast for converting back the rounded value's type double to the method's return type byte. The final code reads:

public static byte getAverage(final byte a, final byte b, final byte c) {

   short sum = a;
   sum += b;
   sum += c;
   final double avg = sum / 3.;   // Floating point rather than integer division
   return (byte) Math.round(avg); 
}

// Note: final short sum = a + b + c does not even compile as being explained at
// https://freedocs.mi.hdm-stuttgart.de/sd1_sect_arithmeticOperators.html#sd1_explainNoByteByteOperator

Given that our variable sum will always stay within range [-3 * -128, 3 * 127] dividing by three guarantees avg to be within [-128, 127]. The byte-cast is thus safe with respect to overflow conditions.