Numerical operations fall into two categories:
3.8.1 Arithmetic Operations
Arithmetic operations are the four operations you learned in elementary school: addition, subtraction, multiplication, and division. Actually, division is really two different operations: quotient and remainder. There is also an additional operation, negation, which is really just multiplication by 1. This is provided because most computers have hardware that can perform negation more quickly than going through the steps of multiplication. Each of these six operations is defined on each of the four basic numerical types: int, long, float, and double. That's a total of 24 operations, summarized in Table 3.6.
Each of these instructions takes no arguments. The operands are found on the stack. All of them except the negation take two operands; negation takes only one. The operands are popped off the stack and the result is pushed on in their place. When order matters, as in subtraction, the top of the operand stack is subtracted from the next element; division is similar. For example, to calculate (100000 99*(22222/3+7) ):
; Stack: ldc 1000000 ; 100000 bipush 99 ; 100000 99 sipush 22222 ; 100000 99 22222 iconst_3 ; 100000 99 22222 3 idiv ; 100000 99 7407 bipush 7 ; 100000 99 7407 7 iadd ; 100000 99 7414 imul ; 100000 733986 isub ; 633986 ineg ; 633986
Notice that the type of the result is always the same as the type of the operands. If you want to subtract a float from a long or another such unnatural act, you will need to convert one or the other so that both are the same type. The operations to do this are discussed in section 3.9.
3.8.2 Nonnumbers and Infinity
The JVM double and float values can take on three values that aren't numbers in the usual sense: NaN (Not a Number), +x, and x (positive and negative infinity). Infinity is the result of dividing by zero. NaN is the result of dividing zero by zero or dividing infinity by infinity. Some examples:
fconst_0 fconst_0 fdiv ; Yields NaN ldc 3.14159 ; Push Pi fadd ; Yields NaN ldc 2.71828 ; Push e fconst_0 ; Push zero fdiv ; Yields infinity ldc 42.0 ; Push some other number fadd ; infinity + anything = infinity dup ; Put two infinities on the stack sub ; infinity infinity = NaN
The standard Java libraries keep NaN, positive infinity, and negative infinity values in Float.NaN, Float.POSITIVE_INFINITY and Float.NEGATIVE_INFINITY. The double equivalents are found in the class Double.
3.8.3 Bitwise Operations
You can think of ints and longs as strings of bits (32 bits for an int, and 64 for a long.) Sometimes you want to work directly with the bits. For example, if you have 50 booleans to keep track of, you can use the bits of a long to represent them with a single unit. This may be more efficient than keeping around 50 separate boolean values in an array.
Bitwise operations are used for performing boolean operations on the bits of ints and longs. They don't apply to floats or doubles, for which the internal representation is more complicated. There are six basic operations: and, or, xor, shift left, shift right, and arithmetic shift. The twelve bitwise instructions are summarized in Table 3.7. Most programmers are already familiar with bitwise operations, so we won't go into them in too much depth here. The only one that may require a bit of explanation is the arithmetic right shift.
The bits of a number are like the digits of your odometer. If 000001 is 1 and 000000 is 0, then 999999 can be treated like 1, 999998 as 2. Every number from 500000 to 999999 is treated as though you subtracted 1,000,000 from it. Every number from 000000 to 499999 is treated as itself. Using just zeroes and ones, 1 and 2 are represented in an int as
1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1110
That's 32 ones for 1, and 31 ones followed by a zero for 2. If the leftmost bit is 1, then the number is interpreted as negative. (Numbers are grouped into sets of four for easier reading.)
Suppose you want to shift this number to the right one step. What do you put in the leftmost place? Should it be 0, 1, or something else? The JVM answers this question two different ways. The standard right shift, ishr, repeats the leftmost bit. This preserves the sign of the number. So 1 >> 1 and 2 >> 1 are, respectively,
1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
That's 1 in both cases. The standard right shift by 1 is just like division by 2, rounding down. To shift by 2 is like dividing by 4; shifting by 3 is like dividing by 8. You get the idea.
The unsigned shift, iushr, always puts a 0 in the leftmost place instead of copying the leftmost bit. For example, 2 >> 1 is:
0111 1111 1111 1111 1111 1111 1111 1111
or 2,147,483,647. This is the largest positive number you can get in the JVM with ints.
A common use for iushr is to cycle through the bits of a number. You look at the rightmost bit, then do an unsigned shift to make the next-to-rightmost number the new rightmost number. Meanwhile, zeroes are shifted in at the left. When all the bits are 0, you're done.
Here's an example of some bit twiddling on ints. Bit twiddling on long values is similar, but there are twice as many bits involved.
; Stack iconst_m1 ; 0xFFFFFFFF ldc 0x12345678 ; 0xFFFFFFFF 0x12345678 ldc 0x87654321 ; 0xFFFFFFFF 0x12345678 0x87654321 iand ; 0xFFFFFFFF 0x02244220 ldc 0xFFFFCCCCC ; 0xFFFFFFFF 0x02244220 0xFFFFCCCC ior ; 0XFFFFFFFF 0xFFFFCEEC ixor ; 0x00003113 bipush 16 ; 0x00003113 16 ishl ; 0x31130000 iconst_2 ; 0x31130000 2 ishl ; 0xC44C0000 iconst_2 ; 0xC44C0000 4 ishr ; 0xF1130000
3.8.4 Floating-Point Arithmetic and strictfp
The biggest change for the Java 2 platform to the JVM itself is the introduction of the strictfp bit in methods and classes. Certain computer architectures use wider floating-point representations than the original JVM specification calls for. Technically, using them would be in error: the additional bits of precision mean that floating-point arithmetic would be slightly different on different JVM implementations. In practice, however, the differences are rarely noticeable, and the performance boost of using native floating-point instructions is far more important than extremely accurate reproducibility of results. The compromise introduced in the Java 2 platform is to introduce the strictfp keyword.
When a method is marked strictfp, all floating-point operations within it are done exactly as the original JVM specification required. If strictfp is not present, the JVM may use its discretion for implementing floating-point arithmetic. You may also mark the class strictfp, which is equivalent to marking each method. Strictness applies to both float and double computations; int and long operations are not affected.
You may not use strictfp to get extra floating-point precision. At various points, the JVM implementation is still required to round numbers off to the standard precision. The rules for when this happens are somewhat arcane and of interest mostly to JVM implementers. As a JVM programmer, these rules mean that you must not assume any more bits of precision than are guaranteed by the JVM specification.
Programs produced with compilers prior to the Java 2 platform do not have the strictfp bit set. All pre-Java 2 implementations always do strict arithmetic. However, a program compiled with a Java 1.0 or Java 1.1 compiler may not yield precisely the same results on a Java 2 platform JVM.
Some applications require that you get the same results every time you run the program. For example, scientific experiments are often sensitive to small differences between strict and nonstrict floating-point arithmetic. If you require the same results no matter what JVM you run on, you should use the strictfp keyword and you should not use any methods that are not marked strictfp. In particular, the java.lang.Math libraries are not written with strictfp, so you should be very careful.
Using strictfp may slow a program down somewhat, since the JVM implementation may have to emulate strict floating-point arithmetic, which is slower than using the native floating-point instructions. By defaulting to nonstrict arithmetic, the JVM favors performance over exactness, but the strictfp keyword allows you to regain exactness.