3.8 Math

Numerical operations fall into two categories:

  • Arithmetic operations: addition, subtraction, multiplication, quotient, and remainder

  • Bitwise operations: shifts and boolean operations (OR, XOR, NOT, AND, etc.)

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.

Table 3.6. Arithmetic operations
Mnemonic Stack Result
iadd int i1, int i2 i1+i2
ladd long l1, long l2 l1+l2
fadd float f1, float f2 f1+f2
dadd double d1, double d2 d1+d2
isub int i1, int i2 i1 i2
lsub long l1, long l2 l1 l2
fsub float f1, float f2 f1 f2
dsub double d1, double d2 d1 d2
imul int i1, int i2 i1xi2
lmul long l1, long l2 l1xl2
fmul float f1, float f2 f1xf2
dmul double d1, double d2 d1xd2
idiv int i1, int i2 i1÷i2
ldiv long l1, long l2 l1÷l2
fdiv float f1, float f2 f1÷f2
ddiv double d1, double d2 d1÷d2
irem int i1, int i2 remainder of i1÷i2
lrem long l1, long l2 remainder of l1÷l2
frem float f1, float f2 remainder of f1÷f2
drem double d1, double d2 remainder of d1÷d2
ineg int i1 i1
lneg long l1 l1
fneg float f1 f1
dneg double d1 d1
In the Stack column, the bottom of the stack is to the left.

Exercise 3.3

Write Oolong code to calculate ax2+bx+c, where a, b, c, and x are int arguments to the method. Try it out with a=1, b= 2, c= 35, and x=7. What is the maximum stack height?

Exercise 3.4

Rewrite your answer to exercise 3.3 to use doubles instead of ints. How does the maximum stack height change?

Exercise 3.5

Rewrite your answer to exercise 3.3 using the formula ((ax+b)x)+c. Compare your answers. Is there a reason to do it this way?

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.

Table 3.7. Bitwise instructions
Mnemonic Stack Result
ishl int i1, int s i1 << s
lshl long l1, int s l1 << s
ishr int i1, int s i1 >> s
lshr long l1, int s l1 >> s
iushr int i1, int s i1 >>> s
lushr long l1, int s l1 >>> s
iand int i1, int i2 i1 & i2
land long l1, long l2 l1 & l2
ior int i1, int i2 i1 | i2
lor long l1, long l2 l1 | l2
ixor int i1, int i2 i1 ^ i2
lxor long l1, long l2 l1 ^ l2

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 

Exercise 3.6

Write a method that computes the number of 1 bits in the binary representation of int. For example, the result should be 1 for 1, 2, 4, 8, 16, . . . It should be 2 for 3, 5, 9, 17, . . . For the number 99, the result should be 4. Hint: This can be done without a loop by repeating the same code 32 times.

Exercise 3.7

There's another kind of shift called a barrel shift. When you barrel shift to the right, you take the rightmost bit and move it all the way to the left. For example, the barrel shift right of

 0000 0001 0010 0011 0100 0101 0110 0111 

is

 1000 0000 1001 0001 1010 0010 1011 0011 

Barrel shift is not explicitly supported by the JVM. Implement a method that does a barrel shift right by 1. Implement another method that does a barrel shift left by 1.

Exercise 3.8

Bitwise operations are often used to represent sets. Build a collection of methods that uses an int as a set. Implement the following operations:

 boolean test(int set, int x)         // Return 1 if bit x is set int set(int set, int x, boolean v)   // Set the x-th bit to v .  

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.



Programming for the Java Virtual Machine
Programming for the Javaв„ў Virtual Machine
ISBN: 0201309726
EAN: 2147483647
Year: 1998
Pages: 158
Authors: Joshua Engel

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net