Lesson 10. Mathematics

In this lesson you will learn about the mathematical capabilities of Java. Java is designed as a multipurpose language. In addition to standard, general-purpose arithmetic support, Java supports a small number of more-advanced math concepts such as infinity. Support is provided through a mixture of Java language features and library classes.

Java supplies a wide range of floating point and integral numerics in both fixed and arbitrary precision form. The representation of numerics and related functions in Java generally adheres to IEEE standards. In some cases, you can choose to use either algorithms designed for speed or algorithms whose results strictly adhere to published standards.

Topics:

• BigDecimal

• additional integral types and operations

• numeric casting

• expression evaluation order

NaN (Not a Number)

• infinity

• numeric overflow

• bit manipulation

• java.lang.Math

• static imports

BigDecimal

The class java.math.BigDecimal allows you to do arbitrary-precision decimal (base 10) floating-point arithmetic. This means that arithmetic with BigDecimal works like you learned in grade school. The BigDecimal class provides a comprehensive set of arithmetic methods and extensive control over rounding. A BigDecimal object represents a decimal number of arbitrary precision, meaning that you decide the number of significant digits it will contain. It is immutable—you cannot change the number that a BigDecimal stores.

The most frequent use of BigDecimal is in financial applications. Financial applications often require you to accurately account for money down to the last hundredth of a cent or even at more granular levels.

Floating-point numbers, as implemented by the Java primitive types float and double, do not allow every number to be represented exactly as specified. This is because it is not mathematically possible to represent certain decimal numbers (such as 0.1) using binary.1 BigDecimal can exactly represent every number.

1 See http://www.alphaworks.ibm.com/aw.nsf/FAQs/bigdecimal.

There are two major downsides to using BigDecimal. First, hardware normally optimizes binary floating-point operations, but BigDecimal's decimal floating point is implemented in software. You may experience poor performance when doing extremely heavy math calculations with BigDecimal.

Second, there is no mathematical operator support for BigDecimal. You must execute common operations such as addition and multiplication via method calls. The requisite code is wordy and thus tedious to write and read.

Using BigDecimal

image

The student information system must maintain an account of charges and credits for each student. The first test demonstrates the ability of an Account to track a balance based on applied charges and credits.

image

The preferred way to construct a new BigDecimal is to pass a String to its constructor. The String contains the value you want the BigDecimal to represent. You could also pass a double, but it may not precisely represent the number you expect, due to the nature of floating-point representation in Java.2 As a result, the BigDecimal may not precisely represent the number you expect.

2 See the Java Glossary entry at http://mindprod.com/jgloss/floatingpoint.html for a discussion of floating-point representation.

You may also construct BigDecimal objects from other BigDecimal objects. This allows you to directly use an expression returning a BigDecimal as the constructor parameter.

The Account implementation:

package sis.studentinfo;

import java.math.BigDecimal;

public class Account {
   private BigDecimal balance = new BigDecimal("0.00");

   public void credit(BigDecimal amount) {
      balance = balance.add(amount);
   }

   public BigDecimal getBalance() {
      return balance;
   }
}

BigDecimal objects are immutable. Sending an add message to a BigDecimal does not alter it. Instead, the add method takes the argument's value and adds it to the value the BigDecimal stores. The add method then creates and returns a new BigDecimal object with this sum. In the Account implementation, the result of sending the add message to balance is a new BigDecimal object. You must reassign this new object to balance in order to maintain the total.

BigDecimal provides a full complement of methods to represent arithmetic operations. They include abs, add, divide, max, min, multiply, negate, and subtract. Additional methods help you to manage scaling and to extract values as different types from a BigDecimal.

Scale

In testTransactions, the assertion expects the result to be a BigDecimal with value "11.10"—with two places after the decimal point. In contrast, you would expect the result of an equivalent Java float or double operation to be "11.1". Adding such an assertion:

assertEquals(new BigDecimal("11.1"), account.getBalance());

fails the test.

The number of digits in the fractional part represents the scale of the number. When one does arithmetic operations on BigDecimal numbers, the result is a new BigDecimal whose scale is the larger between the receiving BigDecimal and the parameter BigDecimal. For example:

assertEquals(new BigDecimal("5.300"),
   new BigDecimal("5.000").add(new BigDecimal("0.3")));

That is, the scale as the result of adding a 1-scale number to a 3-scale number is 3.

Division and Rounding

When dividing numbers, the result often contains a larger number of fractional digits than either the divisor or the dividend. By default, BigDecimal does not expand the scale of the result to this larger amount; instead, it restricts it to the larger of the dividend and divisor scales. You can also explicitly define a new scale for the result.

image

Constraining the scale means that Java may need to round the result of a division operation. Java provides eight different rounding modes. You are probably most familiar with the rounding mode that corresponds to BigDecimal.ROUND_HALF_UP. This mode tells BigDecimal to round the result up if the distance toward the nearest neighbor is greater than or equal to 0.5, and to round it down otherwise. For example, 5.935 would round up to the 2-scale number 5.94, and 5.934 would round down to 5.93.

For an account, you must be able to capture the average transaction amount. A test demonstrates this need:

public void testTransactionAverage() {
  Account account = new Account();
  account.credit(new BigDecimal("0.10"));
  account.credit(new BigDecimal("11.00"));
  account.credit(new BigDecimal("2.99"));
  assertEquals(new BigDecimal("4.70"), account.transactionAverage());
}

The modified Account class:

image

Review the Java API documentation for details on the other seven rounding modes.

More on Primitive Numerics

To this point, you have learned about the numeric primitive types int, double, and float. You have learned about the basic arithmetic operators, the increment/decrement operators for int, and compound assignment.

Other Integer Types

You have been using the int type for integer numerics. There are other integer types available, each representing different sizes available in which to store numbers of that type. Table 10.1 lists all integral types, their size, and the range of values that they support.

Table 10.1. Integer Types

image

The char type is also a numeric type. Refer to Lesson 3 for more information on char.

You usually express numeric literals as decimal, or base 10, numbers. Java also allows you to represent them in hexadecimal (base 16) or octal (base 8).

You prefix hexadecimal literals with 0x.

assertEquals(12, 0xC);

You prefix octal literals with 0:

assertEquals(10, 012);

Integral literals are by default of int type. You can force an integral literal to be a long by suffixing it with the letter L. The lowercase letter l can also be used, but prefer use of an uppercase L, since the lowercase letter is difficult to discern from the number 1.

Integer Math

Let's revisit the code in the class Performance (from Lesson 7) that calculates the average for a series of tests.

public double average() {
   double total = 0.0;
   for (int score: tests)
      total += score;
   return total / tests.length;
}

Suppose the total local variable had been declared as an int instead of a double, which is reasonable since each test score is an int. Adding an int to an int returns another int.

public double average() {
   int total = 0;
   for (int score: tests)
      total += score;
   return total / tests.length;
}

This seemingly innocuous change will break several tests in PerformanceTest. The problem is, dividing an int by an int also returns an int. Integer division always produces integer results; any remainders are discarded. The following two assertions demonstrate correct integer division:

assertEquals(2, 13 / 5);
assertEquals(0, 2 / 4);

If you need the remainder while doing integer division, use the modulus operator (%).

assertEquals(0, 40 % 8);
assertEquals(3, 13 % 5);

Numeric Casting

Java supports numeric types of different ranges in value. The float type uses 32 bits for its implementation and expresses a smaller range of numbers than does double, which uses 64 bits. The integral primitive types—char, byte, short, int, and long—each support a range that is different from the rest of the integral primitive types.

You can always assign a variable of a smaller primitive type to a larger primitive type. For example, you can always assign a float reference to a double reference:

float x = 3.1415f;
double y = x;

Behind the scenes, this assignment implicitly causes a type conversion from float to double.

Going the other way—from a larger primitive type to a smaller—implies that there might be some loss of information. If a number stored as a double is larger than the largest possible float, assigning it to a float would result in the truncation of information. In this situation, Java recognizes the possible loss of information and forces you to cast so that you explicitly recognize that there might be a problem.

If you attempt to compile this code:

double x = 3.1415d;
float y = x;

you will receive a compiler error:

possible loss of precision
found   : double
required: float
float y = x;
               ^

You must cast the double to a float:

double x = 3.1415d;
float y = (float)x;

You may want to go the other way and cast from integral primitives to floating-point numbers in order to force noninteger division. A solution to the above problem of calculating the average for a Performance is to cast the dividend to a double before applying the divisor:

public double average() {
   int total = 0;
   for (int score: tests)
      total += score;
   return (double)total / tests.length;
}

As an added note: When it builds an expression that uses a mix of integral and float values, Java converts the result of the expression to an appropriate float value:

assertEquals(600.0f, 20.0f * 30, 0.05);
assertEquals(0.5, 15.0 / 30, 0.05);
assertEquals(0.5, 15 / 30.0, 0.05);

Expression Evaluation Order

The order of evaluation in a complex expression is important. The basic rules:

• Java evaluates parenthesized expressions first, from innermost parentheses to outermost parentheses.

• Certain operators have higher precedence than others; for example, multiplication has a higher precedence than addition.

• Otherwise, expressions are evaluated from left to right.

You should use parentheses in particularly complex expressions, since the precedence rules can be difficult to remember. In fact, some of them are counterintuitive. Parenthesizing is worth the effort to ensure that there is no misunderstanding about how an expression will be evaluated. Just don't go overboard with the parentheses—most developers should be familiar with the basic precedence rules.

image

Use parentheses to simplify understanding of complex expressions.

Here are a few examples of how precedence affects the result of an expression:

assertEquals(7, 3 * 4 - 5);       // left to right
assertEquals(-11, 4 - 5 * 3);   // multiplication before subtraction
assertEquals(-3, 3 * (4 - 5));  // parentheses evaluate first

When in doubt, write a test!

NaN

image

If no test scores exist, the average for a Performance should be zero. You haven't yet written a test for this scenario. Add the following brief test to PerformanceTest.

public void testAverageForNoScores() {
   Performance performance = new Performance();
   assertEquals(0.0, performance.average());
}

If you run this, oops! You get a NullPointerException. That can be solved by initializing the integer array of tests defined in Performance to an empty array:

private int[] tests = {};

When rerunning the tests, you get a different exception:

junit.framework.AssertionFailedError: expected:<0.0> but was:<NaN>

NaN is a constant defined on both the java.lang.Float and java.lang.Double classes that means “Not a Number.”

If there are no scores, tests.length() returns 0, meaning you are dividing by zero in this line of code:

return total / tests.length;

When it works with integers, Java throws an ArithmeticException to indicate that you are dividing by zero. With floating-point numbers, you get NaN, which is a legitimate floating-point number.

Fix the problem by checking the number of test scores as the very first thing in average.

public double average() {
   if (tests.length == 0)
      return 0.0;
   int total = 0;
   for (int score: tests)
      total += score;
   return (double)total / tests.length;
}

NaN has some interesting characteristics. Any boolean comparisons against NaN always result in false, as these language tests demonstrate:

assertFalse(Double.NaN > 0.0);
assertFalse(Double.NaN < 1.0);
assertFalse(Double.NaN == 1.0);

You might want the average method to return NaN. But how would you write the test, since you cannot compare NaN to any other floating-point number? Java supplies the isNaN static method, defined on both Float and Double, for this purpose:

public void testAverageForNoScores() {
   Performance performance = new Performance();
   assertTrue(Double.isNaN(performance.average()));
}

Infinity

The java.lang.Float class provides two constants for infinity: Float.NEGATIVE_INFINITY and Float.POSITIVE_INFINITY. The class java.lang.Double provides corresponding constants.

While integral division by zero results in an error condition, double and float operations involving zero division result in the mathematically correct infinity value. The following assertions demonstrate use of the infinity constants.

image

image

Numeric Overflow

When working with integral variables, you must be careful to avoid overflow, which can create incorrect results.

For each numeric type, Java provides constants representing its range. For example, the constants Integer.MAX_VALUE and Integer.MIN_VALUE represent the largest (231 – 1) and smallest (–231) values an int can have, respectively.

Java will internally assign the results of an expression to an appropriately large primitive type. The example test shows how you can add one to a byte at its maximum value (127). Java stores the result of the expression as a larger primitive value.

byte b = Byte.MAX_VALUE;
assertEquals(Byte.MAX_VALUE + 1, b + 1);

But if you attempt to assign the result of an expression that is too large back to the byte, the results are probably not what you expected. The following test passes, showing that adding 1 to a byte at maximum value results in the smallest possible byte:

byte b = Byte.MAX_VALUE;
assertEquals(Byte.MAX_VALUE + 1, b + 1);
b += 1;
assertEquals(Byte.MIN_VALUE, b);

The result is due to the way Java stores the numbers in binary. Any additional significant bits are lost in an overflow condition.

Floating-point numbers overflow to infinity.

assertTrue(Double.isInfinite(Double.MAX_VALUE * Double.MAX_VALUE));

Floating-point numbers can also underflow—that is, have a value too close to zero to represent. Java makes these numbers zero.

Bit Manipulation

Java supports bit-level operations on integers. Among myriad other uses, you might use bit manipulation for mathematical calculations, for performance (some mathematical operations may be faster using bit shifting), in encryption, or for working with a compact collection of flags.

Binary Numbers

Your computer ultimately stores all numbers internally as a series of bits. A bit is represented by a binary, or base 2, number. A binary number is either 0 or 1. The following table on top of page 381 counts in binary from 0 through 15.

The table also shows the hexadecimal (“hex”) representation of each number. Java does not allow you to use binary literals, so the easiest way to understand bit operations is to represent numbers with hexadecimal (base 16) literals. Each hex digit in a hex literal represents four binary digits.

Java Binary Representation

Java represents an int as 32 binary digits. The leading bit indicates whether the int is positive (0) or negative (1). Java stores positive integers as pure binary—each binary digit adds into a positive sum. Negative integers are stored in two's complement form. To obtain the two's complement representation of a number, take its positive binary representation, invert all binary digits, and add 1.

image

For example, the number 17 is represented in two's complement as:

0000_0000_0000_0000_0000_0000_0001_00013

3 I have added underscores for readability.

The number -17 is represented in two's complement as:

1111_1111_1111_1111_1111_1111_1110_1111

Logical Bit Operations

Java contains four logical operators that allow you to manipulate bits: bit-and, bit-or, negation, and bit-xor (exclusive-or). A truth table defines how each logical bit operator works, by specifying the results for all possible bit pairings.

The bit-and, bit-or, and bit-xor logical operators operate on two integers; they are therefore referred to as binary operators. The negation operator operates on a single integer and is thus a unary operator.

To execute a binary logical operation, Java compares all corresponding bits from each of the two numbers involved. When doing a bitwise-and operation on two 32-bit integers, then, this means that 32 individual bit-ands take place. To execute a unary logical operation against an integer, Java negates each bit in the integer individually.

You can perform logical bit operations against two integers of type int or smaller. However, Java internally translates integers declared as smaller than intbyte, short, and char—to type int before the bit operation takes place.

The bit-and operator (&) is also known as bitwise multiplication. If both bits are 1, bitwise multiplication returns a 1; otherwise it returns a 0. The following TDTT demonstrates bit-and.

assertEquals(0, 0 & 0);
assertEquals(0, 0 & 1);
assertEquals(0, 1 & 0);
assertEquals(1, 1 & 1);

The bit-or operator (|) is also known as bitwise addition. If both bits are 0, bitwise addition returns a 0; otherwise it returns a 1. The following TDTT demonstrates bit-or.

assertEquals(0, 0 | 0);
assertEquals(1, 0 | 1);
assertEquals(1, 1 | 0);
assertEquals(1, 1 | 1);

The xor (exclusive-or) operator (^) is also known as bitwise difference. If both bits are the same, bitwise difference returns 0; otherwise it returns a 1. The following TDTT demonstrates xor.

assertEquals(0, 0 ^ 0);
assertEquals(1, 0 ^ 1);
assertEquals(1, 1 ^ 0);
assertEquals(0, 1 ^ 1);

The logical negation operator (~) flips all bits in the integer so that 0s become 1s and 1s become 0s.

image

Java supports the use of compound assignment with logical bit operators. Thus, x &= 1 is equivalent to x = x & 1.

Using Bit-And, Bit-Or and Negation

If you have a series of related flags (boolean values) to represent, you can use an integer variable to compactly represent them. A less-efficient alternative would be to define each as a separate boolean variable or as an array of booleans. Each bit in the integer represents a different flag. Thus, you could represent eight flags in a single byte. For example, the binary value 00000001 would mean that the first flag is on and all other flags are off. The binary value 00000101 would mean that the first and third flags are on and all other flags are off.

To set the values of each flag, you first define a mask for each binary position. The mask for the first position is 00000001 (decimal 1), the mask for the second position is 00000010 (decimal 2), and so on. Setting a value is done using bit-or, and extracting a value is done using bit-and.

image

You need to be able to set four yes-no flags on each student: Does the student reside on campus, is the student tax-exempt, is the student a minor, and is the student a troublemaker? You have 200,000 students and memory is at a premium.

image

You should normally prefer explicit query methods for each flag (isOnCampus, isTroublemaker, etc.). The above approach may be preferable if you have a large number of flags and more dynamic needs.

The relevant code in Student follows.

image

Each flag is an enum constant, defined by the Flag enum located in Student. Each enum instantiation passes an integer representing a mask to the constructor of Flag. You store the flags in the int instance variable settings. You explicitly initialize this variable to all 0s to demonstrate intent.

The set method takes a variable number of Flag enum objects. It loops through the array and applies the Flag's mask to the settings variable by using a bit-or operator.

The unset method loops through Flag enum objects and bit-ands the negation of the Flag's mask to the settings variable.

A demonstration of how the bit-or operation sets the correct bit:

image

The isOn method bit-ands a Flag's mask with the settings variable.

image

Finally, a demonstration of the use of negation in the unset method:

image

Using bit operations to store multiple flags is a classic technique that comes from a need to squeeze the most possible information into memory. It is not normally recommended or needed for most Java development, since an array or other collection of boolean values provides a clearer, simpler representation.

Using Xor

The xor operator has the unique capability of being reversible.

int x = 5;                   // 101
int y = 7;                  // 111
int xPrime = x ^ y;   // 010
assertEquals(2, xPrime);
assertEquals(x, xPrime ^ y);

You can also use xor as the basis for parity checking. Transmitting data introduces the possibility of individual bits getting corrupted. A parity check involves transmitting additional information that acts like a checksum. You calculate this checksum by applying an xor against all data sent. The receiver executes the same algorithm against the data and checksum. If checksums do not match, the sender knows to retransmit the data.

The parity check is binary: A stream of data has either even parity or odd parity. If the number of 1s in the data is even, parity is even. If the number of 1s in the data is odd, the parity is odd. You can use xor to calculate this parity. Here is a simple stand-alone test:

public void testParity() {
   assertEquals(0, xorAll(0, 1, 0, 1));
   assertEquals(1, xorAll(0, 1, 1, 1));
}

private int xorAll(int first, int... rest) {
   int parity = first;
   for (int num: rest)
      parity ^= num;
   return parity;
}

Xoring an even number of 1s will always result in an even parity (0). Xoring an odd number of 1s will always result in an odd parity (1).

The reason this works: Xoring is the same as adding two numbers, then taking modulus of dividing by 2. Here is an extension of the truth table that demonstrates this:

image

Dividing mod 2 tells you whether a number is odd (1) or even (0). When you add a string of binary digits together, only the 1 digits will contribute to a sum. Thus, taking this sum modulus 2 will tell you whether the number of 1 digits is odd or even.

Take this one level further to calculate a checksum for any integer. The job of the ParityChecker class is to calculate a checksum for a byte array of data. The test shows how corrupting a single byte within a datum results in a different checksum.

image

ParityChecker loops through all bytes of data, xoring each byte with a cumulative checksum. In the test, I lined up the binary translation of each decimal number (source1, source2, and source3). This allows you to see how the checksum of 5 is a result of xoring the bits in each column.

image

A simple xor parity check will not catch all possible errors. More-complex schemes involve adding a parity bit to each byte transmitted in addition to the a parity byte at the end of all bytes transmitted. This provides a matrix scheme that can also pinpoint the bytes in error.

Bit Shifting

Java provides three operators for shifting bits either left or right.

To shift bits left or right, and maintain the sign bit, use the bit shift left (<<) or bit shift right (>>) operators.

A bit shift left moves each bit one position to the left. Leftmost bits are lost. Rightmost bits are filled with zeroes.

For example, shifting the bit pattern 1011 one position to the left in a 4-bit unsigned number would result in 0110. Some annotated Java examples:

image

You'll note that bit shifting left is the equivalent of multiplying by powers of 2. Bit shifting right is the equivalent of dividing by powers of 2.

The unsigned bit shift right shifts all bits irrespective of the sign bit. Rightmost bits are lost, and the leftmost bits (including the sign bit) are filled with 0.

image

Note that an unsigned bit shift right always results in a positive number.

Bit shifting has uses in cryptography and in graphical image manipulation. You can also use bit shifting for some mathematical operations, such as dividing or multiplying by powers of 2. Only do so if you need extremely fast performance, and then only if your performance tests show you that the math is the performance problem.

BitSet

The Java API library includes the class java.util.BitSet. It encapsulates a vector of bits and grows as needed. BitSet objects are mutable—its individual bits may be set or unset. You can bit-and, bit-or, or bit-xor one BitSet object to another. You can also negate a BitSet. One of its few benefits is that it supports bit operations for numbers larger than the capacity of an int.

java.lang.Math

The class java.lang.Math provides a utility library of mathematical functions. It also provides constant definitions Math.E, for the base of the natural log, and Math.PI, to represent pi. These constants are double quantities that provide fifteen decimal places.

The Java API library also contains a class named java.lang.StrictMath, which provides bit-for-bit adherence to accepted standard results for the functions. For most purposes, Math is acceptable and also results in better performance.

I summarize the functions provided by the Math class in the following table. Refer to the Java API documentation for complete details on each function.

image

A method to calculate the hypotenuse of a right triangle provides a simple example. (The hypotenuse is the side opposite the right angle.)

image

The pow function is used to square the sides a and b. The hypotenuse method then sums these squares and returns the square root of the sum using sqrt.

Pervasive use of the Math class gives you a justifiable reason for using the static import facility introduced in Lesson 4. Code with a lot of Math method calls otherwise becomes a nuisance to code and read.

I've named the utility class Math, like the java.lang.Math class. While this is a questionable practice (it may introduce unnecessary confusion), it demonstrates that Java can disambiguate the two.

Numeric Wrapper Classes

As you saw in Lesson 7, Java supplies a corresponding wrapper class for each primitive type: Integer, Double, Float, Byte, Boolean, and so on. The primary use of the wrapper class is to convert primitives to object form so that they can be stored in collections.

The wrapper classes for the numeric types have additional uses. You learned earlier in this lesson that each numeric wrapper class provides a MIN_VALUE and MAX_VALUE constant. You also saw how the Float and Double classes provide constants for Not a Number (NaN) and infinity.

Printable Representations

The class Integer provides static utility methods to produce printable representations of an int in hex, octal, and binary form.

assertEquals("101", Integer.toBinaryString(5));
assertEquals("32", Integer.toHexString(50));
assertEquals("21", Integer.toOctalString(17));

The Java library includes general-purpose methods to produce a proper String for any radix (base). The following assertion shows how to get the trinary (base 3) String for an int.

assertEquals("1022", Integer.toString(35, 3));

Converting Strings to Numbers

In Lesson 8, you learned that the method Integer.parseInt takes a String and parses it to produce the corresponding int value. You typically use the parseInt method to convert user interface input into numeric form. All characters in the input String must be decimal digits, except for the first character, which may optionally be a negative sign. If the String does not contain a parseable integer, parseInt throws a NumberFormatException.

An additional form of parseInt takes a radix, representing the base of the input string, as the second parameter. The method valueOf operates the same as parseInt, except that it returns a new Integer object instead of an int.

Each of the corresponding numeric wrapper classes has a parsing method. For example, you may convert a String to a double using Double.parseDouble, or to a float using Float.parseFloat.

The parseInt method can accept only decimal input. The decode method can parse an input string that might be in hex or octal, as these assertions show:

assertEquals(253, Integer.decode("0xFD"));
assertEquals(253, Integer.decode("0XFD"));
assertEquals(253, Integer.decode("#FD"));
assertEquals(15, Integer.decode("017"));
assertEquals(10, Integer.decode("10"));
assertEquals(-253, Integer.decode("-0xFD"));
assertEquals(-253, Integer.decode("-0XFD"));
assertEquals(-253, Integer.decode("-#FD"));
assertEquals(-15, Integer.decode("-017"));
assertEquals(-10, Integer.decode("-10"));

Random Numbers

The class Math provides the method random to return a pseudorandom double between 0.0 and 1.0. A number in this range may be all you need. The generated number is not truly random—the elements of a sequence of pseudorandom numbers are only approximately independent from each other.4 For most applications, this is sufficient.

4 http://en.wikipedia.org/wiki/Pseudo-random_number_generator.

The class java.util.Random is a more comprehensive solution for generating pseudorandom numbers. It produces pseudorandom sequences of booleans, bytes, ints, longs, floats, Gaussians, or doubles. A pseudorandom sequence generator is not purely random; it is instead based upon an arithmetical algorithm.

You can create a Random instance with or without a seed. A seed is essentially a unique identifier for a random sequence. Two Random objects created with the same seed will produce the same sequence of values. If you create a Random object without explicitly specifying a seed, the class uses the system clock as the basis for the seed.5

5 Which means that if you construct two Random objects and the construction happens to execute in the same nanosecond, you get the same sequence in both. In earlier versions of Java, the Random constructor used the current system time in milliseconds. This increased granularity significantly increased the likelihood of two Random objects having the same sequence.

You could create a simulation of coin flips through repeated use of the nextBoolean method on Random. A true value would indicate heads and a false value would indicate tails. The below test shows use of the seed value to produce two identical pseudorandom coin-flip sequences.

image

The methods nextInt, nextDouble, nextLong, and nextFloat work similarly. An additional version of nextInt takes as a parameter a maximum value, such that the method always returns a number from 0 through the maximum value.

Testing Random Code

There are a few ways to write unit tests against code that must use random sequences. One way is to provide a subclass implementation of the Random class and substitute that for the Random class used in the code. The substitute Random class will provide a known sequence of values. This technique is known as mocking the Random class. I will cover mocking in considerable depth later.

image

In Lesson 8, you provided your own logging Handler class solely for purposes of testing. This was also a form of mocking.

You must assign passwords to students so they can access their accounts online. Passwords are eight characters long. Each character of the password must fall in a certain character range.

image

The test method testGeneratePassword sets the random variable into the PasswordGenerator class to point to an instance of MockRandom. The MockRandom class extends from the Random class. The Java API documentation explains how to properly extend the Random class by overriding the method next(int bits) to return a random number based on a bit sequence. All other methods in the Random class are based on this method.

The mock implementation takes an int representing a starting character value as a parameter to its constructor. It uses this to calculate an initial value for the random sequence and stores it as i. The initial value is a number relative to the lowest valid character in the password. Its next(int bits) method simply returns and then increments the value of i.

image

MockRandom is defined completely within PasswordGeneratorTest. It is a nested class of PasswordGenerator. You can directly instantiate Mock-Random from within PasswordGeneratorTest, but not from any other class. There are additional kinds of nested classes, each kind with its own nuances. In Lesson 11, you will learn about nested classes in depth. Until then, use them with caution.

The implementation of PasswordGenerator follows.

image

Note that if the package-level setRandom method is not called, the random instance variable keeps its initialized value of a legitimate java.util.Random instance. Since MockRandom meets the contract of java.util.Random, you can substitute an instance of MockRandom for Random for purposes of testing.

The goal of testGeneratePassword is to prove that the PasswordGenerator class can generate and return a random password. The test does not need to reprove the functionality inherent in the Random class, but the test does need to prove that a PasswordGenerator object interacts with a Random instance (or subclass instance) through its published interface, nextInt(int max). The test must also prove that PasswordGenerator uses the nextInt return value properly. In this example, PasswordGenerator uses the nextInt return value to help construct an 8-character String of random values.

Java supplies an additional random class, java.util.SecureRandom, as a standards-based, cryptographically strong pseudorandom number generator.

Figure 10.1. Mocking the Random Class

image

Exercises

  1. Create a test demonstrating the immutability of a BigDecimal. Add a second BigDecimal to the first and show that the first still has the original value.
  2. Create one BigDecimal using the value "10.00" and another using the value "1". Show they are not equal. Use multiplication and scale to create a new BigDecimal from the second so that it equals the first. Now reverse the transformation, starting from 10.00, and get back to 1.
  3. Show that 0.9 and 0.005 * 2.0 are not equal with floats. To what precision are they equal? With doubles?
  4. Why won't the following compile? What are two ways it can be fixed?

    public class CompilerError {
       float x = 0.01;        
    }

  5. Write a brief spike program to discover the decimal value of 0xDEAD. How could you recode the literal in octal?
  6. Find as many interesting variations of NaN and Infinity calculations as you can.
  7. Challenge: Can you find ways in which the wrappers such as Float act differently than the corresponding primitive?
  8. Create a method (test first, of course) that takes a variable number of int values. Code it to return only numbers in the list that are divisible by 3. Code it twice: The first time, use only the modulus (%) operator. The second time, use the division (/) operator and the multiplication operator if necessary, but not the % operator. Test against the sequence of integers from 1 through 10.
  9. Change the chess code to use casting where appropriate. Eliminate the need for the toChar method on the Board class.
  10. Which of the following lines will compile correctly? Why?

    float x = 1;
    float x = 1.0;
    float x = (int)1.0;

  11. What is the value of (int)1.9?
  12. What is the value of Math.rint(1.9)?
  13. When using Math.rint, how is rounding performed? Is 1.5 equal to 1 or 0? How about 2.5?
  14. What are the final values of the following expressions, assuming x is 5 and y is 10 and x and y are both ints? Flag the lines that wouldn't compile, as well. What are the values of x and y afterward? (Consider each expression to be discrete—i.e., each expression starts with the values of x and y at 5 and 10.)

    image

  15. Using only the << operator, convert 17 into 34.
  16. What is the decimal value of ~1?
  17. Demonstrate the difference between >> and >>>. Is there a difference between the two for positive numbers? For negative numbers?
  18. Create a function which uses Math.random to generate a random integer from 1 through 50. How will you go about testing your function? Can you test it perfectly? How confident can you get that your code works?
  19. Create a list of numbers from 1 to 100. Use a Random generator to randomly swap elements of the list around 100 times. Write a test that does a reasonable verification: Ensure that the list size remains the same and that two numbers were swapped with each call to the swapper.
  20. Show that the next double is not the same between a Random seeded with 1 and a Random with no seed. Can you make a test that absolutely proves that this is true?
  21. Challenge: Swap two numbers without a temporary variable by using the xor operator.
  22. Challenge: Programmatically demonstrate the number of bits required to store each integral type (char, byte, short, int, long). Hint: Use shift operators. Remember that signed types require an extra bit to store the sign.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.186.219