The average of three byte values

exercise No. 217

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 programming companion 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.

In addition provide a solution by modifying the above implementation without changing the method's signature. You may present your code in the answer box below as well.

Alternatively correct the implementation of de.hdm_stuttgart.mi.sd1.task4_no_unit_test.Mathextend within your downloaded and imported exam.zip project using your IDEA IDE. In this case Do not forget to export and upload after finishing by using task 1 of this examination.

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 adding either Byte.MAX_VALUE or Byte.MIN_VALUE even three times in a row.

Dividing by 3 should be a floating point rather than an integer operation to avoid cutting off fractional values. On top we finally use the Math.round(double) method avoiding rounding errors like the one shown previously.

Finally we need a cast for converting back the rounded value's type double to the method's return type byte. The final result 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 our sum being within range [-3 * -128, 3 * 127] dividing by three guarantees avg to be within [-128, 127]. The (byte) cast will thus safe with respect to overflow problems of our returned value.