3.1 Arithmetic Operators
Java supports the following arithmetic operators:
Operator
|
Description
|
Usage
|
Examples
|
*
|
Multiplication
|
expr1 * expr2
|
2 * 3 →
6
3.3 * 1.0 → 3.3 |
/
|
Division
|
expr1 / expr2
|
1 / 2 →
0
1.0 / 2.0 → 0.5 |
%
|
Remainder
(Modulus)
|
expr1 % expr2
|
5 % 2 →
1
-5 % 2 → -1 5.5 % 2.2 → 1.1 |
+
|
Addition
(or
unary positive)
|
expr1 + expr2
+expr |
1 + 2 →
3
1.1 + 2.2 → 3.3 |
-
|
Subtraction
(or
unary negate)
|
expr1 - expr2
-expr |
1 - 2 →
-1
1.1 - 2.2 → -1.1 |
All these operators are binary operators, i.e., they take two operands. However, '+' and '-' can also be interpreted as unary "positive" and "negative" operators. For example,
int number = -88; // negate int x = +5; // '+' optional
3.2 Arithmetic Expressions
In programming, the following arithmetic expression:
must be written as (1+2*a)/3 + (4*(b+c)*(5-d-e))/f - 6*(7/g+h)
. You cannot omit the multiplication symbol (*
), as in Mathematics.
(1+2*a)/3 + (4*(b+c)*(5-d-e))/f - 6*(7/g+h)
. You cannot omit the multiplication symbol (*
), as in Mathematics.Rules on Precedence
Like Mathematics:
- The multiplication (
*
), division (/
) and remainder (%
) take precedence over addition (+
) and subtraction (-
). For example, 1+2*3-4/2
is interpreted as 1+(2*3)-(4/2)
.
- Unary
'+'
(positive) and '-'
(negate) have higher precedence.
- Parentheses
()
have the highest precedence and can be used to change the order of evaluation.
- Within the same precedence level (e.g., addition and subtraction), the expression is evaluated from left to right (called left-associative). For example,
1+2-3+4
is evaluated as ((1+2)-3)+4
and 1*2%3/4
is ((1*2)%3)/4
.
*
), division (/
) and remainder (%
) take precedence over addition (+
) and subtraction (-
). For example, 1+2*3-4/2
is interpreted as 1+(2*3)-(4/2)
.'+'
(positive) and '-'
(negate) have higher precedence.()
have the highest precedence and can be used to change the order of evaluation.1+2-3+4
is evaluated as ((1+2)-3)+4
and 1*2%3/4
is ((1*2)%3)/4
.3.3 Mixed-Type Operations
The arithmetic operators are only applicable to primitive numeric types: byte
, short
, int
, long
, float
, double
, and char
.
These operators do not apply to boolean
.
If both operands are int
, long
, float
or double
, the arithmetic operations are carried in that type, and evaluated to a value of that type, i.e., int 5 + int 6 → int 11
; double 2.1 + double 1.2 → double 3.3
.
It is important to take note int
division produces an int
, i.e., int/int →
int
, with the result truncated, e.g., 1/2 → 0
, instead of 0.5
?!
If both operand are byte
, short
or char
, the operations are carried out in int
, and evaluated to a value of int
. A char
is treated as a 16-bit unsigned integer in the range of [0, 65535]
. For example, byte 127 + byte 1 → int 127 + int 1 → int 128
.
If the two operands belong to different types, the value of the smaller type is promoted automatically to the larger type (known as implicit type-casting). The operation is then carried out in the larger type, and evaluated to a value in the larger type.
byte
, short
or char
is first promoted to int
before comparing with the type of the other operand.
- The order of promotion is:
int → long → float → double
.
For examples,
int/double → double/double → double
. Hence, 1/2 → 0, 1.0/2.0 → 0.5, 1.0/2 → 0.5, 1/2.0 → 0.5
.
char + float → int + float → float + float → float
.
9 / 5 * 20.1 → (9 / 5) * 20.1 → 1 * 20.1 → 1.0 * 20.1 → 20.1
(You probably don't expect this answer!)
byte 1 + byte 2 → int 1 + int 2 → int 3
(The result is an int
, NOT byte
!)byte b1 = 1, b2 = 2;
byte b3 = b1 + b2; // Compilation Error: possible loss of precision
// b1+b2 returns an int, cannot be assigned to byte
The type-promotion rules for binary operations can be summarized as follows:
- If one of the operand is
double
, the other operand is promoted to double
;
- Else If one of the operand is
float
, the other operand is promoted to float
;
- Else If one of the operand is
long
, the other operand is promoted to long
;
- Else both operands are promoted to
int
.
The type-promotion rules for unary operations (e.g., negate '-'
) can be summarized as follows:
- If the operand is
double
, float
, long
or int
, there is no promotion.
- Else (the operand is
byte
, short
, char
), the operand is promoted to int
.
For example,
byte b1 = 1;
byte b2 = -b1; // Compilation Error: possible loss of precision
// -b1 returns an int, cannot be assigned to byte
byte
, short
, int
, long
, float
, double
, and char
.
These operators do not apply to boolean
.int
, long
, float
or double
, the arithmetic operations are carried in that type, and evaluated to a value of that type, i.e., int 5 + int 6 → int 11
; double 2.1 + double 1.2 → double 3.3
.int
division produces an int
, i.e., int/int →
int
, with the result truncated, e.g., 1/2 → 0
, instead of 0.5
?!byte
, short
or char
, the operations are carried out in int
, and evaluated to a value of int
. A char
is treated as a 16-bit unsigned integer in the range of [0, 65535]
. For example, byte 127 + byte 1 → int 127 + int 1 → int 128
.byte
, short
or char
is first promoted to int
before comparing with the type of the other operand.int → long → float → double
.int/double → double/double → double
. Hence, 1/2 → 0, 1.0/2.0 → 0.5, 1.0/2 → 0.5, 1/2.0 → 0.5
.char + float → int + float → float + float → float
.9 / 5 * 20.1 → (9 / 5) * 20.1 → 1 * 20.1 → 1.0 * 20.1 → 20.1
(You probably don't expect this answer!)byte 1 + byte 2 → int 1 + int 2 → int 3
(The result is an int
, NOT byte
!)byte b1 = 1, b2 = 2;
byte b3 = b1 + b2; // Compilation Error: possible loss of precision
// b1+b2 returns an int, cannot be assigned to byte
double
, the other operand is promoted to double
;float
, the other operand is promoted to float
;long
, the other operand is promoted to long
;int
.'-'
) can be summarized as follows:double
, float
, long
or int
, there is no promotion.byte
, short
, char
), the operand is promoted to int
.Remainder (Modulus) Operator
To evaluate the remainder (for negative and floating-point operands), perform repeated subtraction until the absolute value of the remainder is less than the absolute value of the second operand.
For example,
-5 % 2 ⇒ -3 % 2 ⇒ -1
5.5 % 2.2 ⇒ 3.3 % 2.2 ⇒ 1.1
-5 % 2 ⇒ -3 % 2 ⇒ -1
5.5 % 2.2 ⇒ 3.3 % 2.2 ⇒ 1.1
Exponent?
Java does not have an exponent operator. (The '^'
operator is for exclusive-or, NOT exponent). You need to use method Math.exp(x, y)
to evaluate x
raises to power y
.
'^'
operator is for exclusive-or, NOT exponent). You need to use method Math.exp(x, y)
to evaluate x
raises to power y
.3.4 Overflow/Underflow
Study the output of the following program:
/*
* Illustrate "int" overflow
*/
public class OverflowTest {
public static void main(String[] args) {
// Range of int is [-2147483648, 2147483647]
int i1 = 2147483647; // maximum int
System.out.println(i1 + 1); // -2147483648 (overflow!)
System.out.println(i1 + 2); // -2147483647
System.out.println(i1 * i1); // 1
int i2 = -2147483648; // minimum int
System.out.println(i2 - 1); // 2147483647 (overflow!)
System.out.println(i2 - 2); // 2147483646
System.out.println(i2 * i2); // 0
}
}
In arithmetic operations, the resultant value wraps around if it exceeds its range (i.e., overflow). Java runtime does NOT issue an error/warning message but produces an incorrect result.
On the other hand, integer division produces an truncated integer and results in so-called underflow. For example, 1/2
gives 0
, instead of 0.5
. Again, Java runtime does NOT issue an error/warning message, but produces an imprecise result.
It is important to take note that checking of overflow/underflow is the programmer's responsibility. i.e., your job!!!
Why computer does not flag overflow/underflow as an error? This is due to the legacy design when the processors were very slow. Checking for overflow/underflow consumes computation power. Today, processors are fast. It is better to ask the computer to check for overflow/underflow (if you design a new language), because few humans expect such results.
To check for arithmetic overflow (known as secure coding) is tedious. Google for "INT32-C. Ensure that operations on signed integers do not result in overflow" @ www.securecoding.cert.org.
1/2
gives 0
, instead of 0.5
. Again, Java runtime does NOT issue an error/warning message, but produces an imprecise result.3.5 Type-Casting
In Java, you will get a compilation error if you try to assign a double
or float
value of to an int
variable. This is because the fractional part would be lost. The compiler issues an error "possible loss in precision". For example,
double d = 3.5;
int i;
i = d; // Compilation Error: possible loss of precision (assigning a double value to an int variable)
int sum = 55.66f; // Compilation Error: possible loss of precision (assigning a float value to an int variable)
double
or float
value of to an int
variable. This is because the fractional part would be lost. The compiler issues an error "possible loss in precision". For example,Explicit Type-Casting and Type-Casting Operator
To assign the a double
value to an int
variable, you need to invoke the so-called type-casting operator - in the form of (int)value
- to operate on the double
operand and return a truncated value in int
. In other words, you tell the compiler you concisely perform the truncation and you are aware of the possible loss of precision. You can then assign the truncated int
value to the int
variable. For example,
double d = 3.5;
int i;
i = (int) d; // Cast double value of 3.5 to int 3. Assign the resultant value 3 to i
// Casting from double to int truncates.
Type-casting is an operation which takes one operand. It operates on its operand, and returns an equivalent value in the specified type.
There are two kinds of type-casting in Java:
- Explicit type-casting via a type-casting operator in the prefix form of
(new-type)operand
, as described above, and
- Implicit type-casting performed by the compiler automatically, if there is no loss of precision.
double
value to an int
variable, you need to invoke the so-called type-casting operator - in the form of (int)value
- to operate on the double
operand and return a truncated value in int
. In other words, you tell the compiler you concisely perform the truncation and you are aware of the possible loss of precision. You can then assign the truncated int
value to the int
variable. For example,(new-type)operand
, as described above, andImplicit Type-Casting in Assignment
Explicit type-casting is not required if you assign an int
value to a double
variable, because there is no loss of precision. The compiler will perform the type-casting automatically (i.e., implicit type-casting). For example,,
int i = 3;
double d;
d = i; // OK, no explicit type casting required
// d = 3.0
d = (double) i; // Explicit type casting operator used here
double aDouble = 55; // Compiler auto-casts int 55 to double 55.0
double nought = 0; // Compiler auto-casts int 0 to double 0.0
// int 0 and double 0.0 are different.
The following diagram shows the order of implicit type-casting performed by compiler. The rule is to promote the smaller type to a bigger type to prevent loss of precision, known as widening conversion. Narrowing conversion requires explicit type-cast to inform the compiler that you are aware of the possible loss of precision. Take note that char
is treated as an 16-bit unsigned integer in the range of [0, 65535]
. boolean
value cannot be type-casted (i.e., converted to non-boolean
).
Example: Suppose that you want to find the average (in double
) of the integers between 1
and 100
. Study the following codes:
int
value to a double
variable, because there is no loss of precision. The compiler will perform the type-casting automatically (i.e., implicit type-casting). For example,,char
is treated as an 16-bit unsigned integer in the range of [0, 65535]
. boolean
value cannot be type-casted (i.e., converted to non-boolean
).double
) of the integers between 1
and 100
. Study the following codes:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class Sum1To100
{
public static void main(String[] args) {
int sum = 0;
double average;
int number = 1;
while (number <= 100) {
sum += number; // Final sum is int 5050
++number;
}
average = sum / 100; // Won't work (average = 50.0 instead of
50.5)
System.out.println("Average is
" + average); // Average is 50.0
}
}
|
This is because both the
sum
and 100
are int
. The result of division is an int
, which is then implicitly casted to double
and assign to the double
variable average
. To get the correct answer, you can do either:average = (double)sum / 100; // Cast sum from int to double before division average = sum / (double)100; // Cast 100 from int to double before division average = sum / 100.0; average = (double)(sum / 100); // Won't work. why?
3.6 Compound Assignment Operators
Besides the usual simple assignment operator (=
) described earlier, Java also provides the so-called compound assignment operators as listed:
Operation
Description
Usage
Example
=
Assignment
Assign the value of
the LHS to the variable at the RHS
var = expr
x = 5;
+=
Compound addition and
assignment
var += expr
same as var = var + expr
x += 5;same as x = x +
5
-=
Compound subtraction
and assignment
var -= expr
same as var = var - expr
x -= 5;same as x = x -
5
*=
Compound
multiplication and assignment
var *= expr
same as var = var * expr
x *= 5;same as x = x *
5
/=
Compound division and
assignment
var /= expr
same as var = var / expr
x /= 5;same as x = x /
5
%=
Compound remainder
(modulus) and assignment
var %= expr
same as var = var % expr
x %= 5;same as x = x %
5
=
) described earlier, Java also provides the so-called compound assignment operators as listed:
Operation
|
Description
|
Usage
|
Example
|
=
|
Assignment
Assign the value of
the LHS to the variable at the RHS
|
var = expr
|
x = 5;
|
+=
|
Compound addition and
assignment
|
var += expr
same as var = var + expr
|
x += 5;same as x = x +
5
|
-=
|
Compound subtraction
and assignment
|
var -= expr
same as var = var - expr
|
x -= 5;same as x = x -
5
|
*=
|
Compound
multiplication and assignment
|
var *= expr
same as var = var * expr
|
x *= 5;same as x = x *
5
|
/=
|
Compound division and
assignment
|
var /= expr
same as var = var / expr
|
x /= 5;same as x = x /
5
|
%=
|
Compound remainder
(modulus) and assignment
|
var %= expr
same as var = var % expr
|
x %= 5;same as x = x %
5
|
3.7 Increment/Decrement
Java supports these unary arithmetic operators: increment (++
) and decrement (--
) for all numeric primitive types (byte
, short
, char
, int
, long
, float
and double
, except boolean
).
++
) and decrement (--
) for all numeric primitive types (byte
, short
, char
, int
, long
, float
and double
, except boolean
).
Operator
|
Description
|
Example
|
++
|
Increment the value of
the variable by 1.
x++ or ++x is the same as x += 1 or x = x +
1
|
int x = 5;
x++; // x is 6 ++x; // x is 7 |
--
|
Decrement the value of
the variable by 1.
x-- or --x is the same as x -= 1 or x = x -
1
|
int y = 6;
y--; // y is 5 --y; // y is 4 |
The increment (
++
) and decrement (--
) operate on its operand and store the result back to its operand. For example, x++
retrieves x, increment and stores the result back to x. Writing x = x++
is a logical error!!!
Unlike other unary operator (such as negate (
-
)) which promotes byte
, short
and char
to int
, the increment and decrement do not promote its operand (because there is no such need).
The increment/decrement unary operator can be placed before the operand (prefix), or after the operands (postfix), which affects its resultant value.
- If these operators are used by themselves (e.g.,
x++
or++x
), the outcomes are the same for pre- and post-operators, because the resultant values are discarded. - If '++' or '--' involves another operation (e.g.,
y=x++
ory=++x
), then pre- or post-order is important to specify the order of the two operations:
Operator
|
Description
|
Example
|
++var
|
Pre-Increment
Increment var, then use the new value of var
|
y = ++x;
same as x=x+1; y=x;
|
var++
|
Post-Increment
Use the old value
of var, then increment var
|
y = x++;
same as oldX=x; x=x+1; y=oldX; |
--var
|
Pre-Decrement
|
y = --x;
same as x=x-1; y=x; |
var--
|
Post-Decrement
|
y = x--;
same as oldX=x; x=x-1; y=oldX; |
For examples,
x = 5; System.out.println(x++); // Print x (5), then increment x (=6). Output is 5. (x++ returns the oldX.) x = 5; System.out.println(++x); // Increment x (=6), then print x (6). Output is 6. (++x returns x+1.)
Prefix operator (e.g,
++i
) could be more efficient than postfix operator (e.g., i++
)?!3.8 Relational and Logical Operators
Very often, you need to compare two values before deciding on the action to be taken, e.g. if mark is more than or equals to 50, print "PASS!".
Java provides six comparison operators (or relational operators):
Operator
|
Description
|
Usage
|
Example (x=5, y=8)
|
==
|
Equal to
|
expr1 == expr2
|
(x == y) → false
|
!=
|
Not Equal to
|
expr1 != expr2
|
(x != y) → true
|
>
|
Greater than
|
expr1 > expr2
|
(x > y) → false
|
>=
|
Greater than or equal
to
|
expr1 >= expr2
|
(x >= 5) → true
|
<
|
Less than
|
expr1 < expr2
|
(y < 8) → false
|
<=
|
Less than or equal to
|
expr1 >= expr2
|
(y <= 8) → true
|
In Java, these comparison operations returns a
boolean
value of either true
or false
.
Each comparison operation involves two operands, e.g.,
x <= 100
. It is invalid to write 1 < x < 100
in programming. Instead, you need to break out the two comparison operations x > 1
, x < 100
, and join with with a logical AND operator, i.e., (x > 1) && (x < 100)
, where &&
denotes AND operator.
Java provides four logical operators, which operate on
boolean
operands only, in descending order of precedences, as follows:
Operator
|
Description
|
Usage
|
!
|
Logical NOT
|
!booleanExpr
|
^
|
Logical XOR
|
booleanExpr1 ^ booleanExpr2
|
&&
|
Logical AND
|
booleanExpr1 && booleanExpr2
|
||
|
Logical OR
|
booleanExpr1 || booleanExpr2
|
The truth tables are as follows:
AND (&&) | true | false |
---|---|---|
true | true | false |
false | false | false |
OR (||) | true | false |
---|---|---|
true | true | true |
false | true | false |
NOT (!) | true | false |
---|---|---|
Result | false | true |
XOR (^) | true | false |
---|---|---|
true | false | true |
false | true | false |
Example:
// Return true if x is between 0 and 100 (inclusive) (x >= 0) && (x <= 100) // wrong to use 0 <= x <= 100 // Return true if x is outside 0 and 100 (inclusive) (x < 0) || (x > 100) //or !((x >= 0) && (x <= 100)) // Return true if year is a leap year // A year is a leap year if it is divisible by 4 but not by 100, or it is divisible by 400. ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)
Exercise: Study the following program, and explain its output.
/*
* Test relational and logical operators
*/
public class RelationalLogicalOpTest {
public static void main(String[] args) {
int age = 18;
double weight = 71.23;
int height = 191;
boolean married = false;
boolean attached = false;
char gender = 'm';
System.out.println(!married && !attached && (gender == 'm'));
System.out.println(married && (gender == 'f'));
System.out.println((height >= 180) && (weight >= 65) && (weight <= 80));
System.out.println((height >= 180) || (weight >= 90));
}
}
Write an expression for all unmarried male, age between 21 and 35, with height above 180, and weight between 70 and 80.
Exercise: Given the year, month (1-12), and day (1-31), write a boolean expression which returns true for dates before October 15, 1582 (Gregorian calendar cut over date).
Ans:
(year < 1582) || (year == 1582 && month < 10) || (year == 1582 && month == 10 && day < 15)
Operator Precedence
The precedence from highest to lowest is: '!'
(unary), '^'
, '&&'
, '||
'. But when in doubt, use parentheses!
System.out.println(true || true && false); // true (same as below)
System.out.println(true || (true && false)); // true
System.out.println((true || true) && false); // false
System.out.println(false && true ^ true); // false (same as below)
System.out.println(false && (true ^ true)); // false
System.out.println((false && true) ^ true); // true
'!'
(unary), '^'
, '&&'
, '||
'. But when in doubt, use parentheses!Short-Circuit Operation
The logical AND (&&
) and OR (||
) operators are known as short-circuit operators, meaning that the right operand will not be evaluated if the result can be determined by the left operand. For example, false && ...
gives false
; true || ...
give true
without evaluating the right operand. This may have adverse consequences if you rely on the right operand to perform certain operations, e.g. false && (++i < 5)
but ++i
will not be evaluated.
&&
) and OR (||
) operators are known as short-circuit operators, meaning that the right operand will not be evaluated if the result can be determined by the left operand. For example, false && ...
gives false
; true || ...
give true
without evaluating the right operand. This may have adverse consequences if you rely on the right operand to perform certain operations, e.g. false && (++i < 5)
but ++i
will not be evaluated.