Java supports integer and floating-point arithmetic directly in the
language. Higher-level math operations are supported through the
java.lang.Math
class. As
you may have seen by now, wrapper classes for primitive data types allow
you to treat them as objects. Wrapper classes also hold some methods for
basic conversions.
First, a few words about built-in arithmetic in Java. Java handles
errors in integer arithmetic by throwing an ArithmeticException
:
int
zero
=
0
;
try
{
int
i
=
72
/
zero
;
}
catch
(
ArithmeticException
e
)
{
// division by zero
}
To generate the error in this example, we created the intermediate
variable zero
. The compiler is somewhat
crafty and would have caught us if we had blatantly tried to perform
division by a literal zero.
Floating-point arithmetic expressions, on the other hand, don’t throw exceptions. Instead, they take on the special out-of-range values shown in Table 11-1.
The following example generates an infinite result:
double
zero
=
0.0
;
double
d
=
1.0
/
zero
;
if
(
d
==
Double
.
POSITIVE_INFINITY
)
System
.
out
.
println
(
"Division by zero"
);
The special value NaN
(not a
number) indicates the result of dividing zero by zero. This value has the
special mathematical distinction of not being equal to itself (NaN != NaN
evaluates to true
). Use Float.isNaN()
or Double.isNaN()
to test for NaN
.
The java.lang.Math
class is Java’s math library. It holds a suite of static methods
covering all of the usual mathematical operations like sin()
, cos()
, and sqrt()
. The Math
class isn’t very object-oriented (you
can’t create an instance of Math
).
Instead, it’s really just a convenient holder for static methods that
are more like global functions. As we saw in Chapter 6, it’s possible to use the static import
functionality to import the names of static methods and constants like
this directly into the scope of our class and use them by their simple,
unqualified names.
Table 11-2 summarizes the
methods in java.lang.Math
.
Table 11-2. Methods in java.lang.Math
Method | Argument type(s) | Functionality |
---|---|---|
| Absolute value | |
| Arc cosine | |
| Arc sine | |
| Arc tangent | |
| | Angle part of rectangular-to-polar coordinate transform |
| Smallest whole number greater than or
equal to | |
| Cube root of | |
| | Cosine |
| Hyperbolic cosine | |
| | |
| Largest whole number less than or
equal to | |
| Precision calculation of the | |
| | Natural logarithm of |
| | Log base 10 of |
| | The value |
| The value | |
| | |
| Random-number generator | |
| Converts double value to integral value in double format | |
| Rounds to whole number | |
| Get the sign of the number at 1.0, –1.0, or 0 | |
| Sine | |
| | Hyperbolic sine |
| Square root | |
| Tangent | |
| | Hyperbolic tangent |
| Convert radians to degrees | |
| Convert degrees to radians |
log()
, pow()
, and sqrt()
can throw a runtime ArithmeticException
.
abs()
, max()
, and min()
are overloaded for all the scalar
values, int
, long
, float
, or double
, and return the corresponding type.
Versions of Math.round()
accept
either float
or double
and return int
or long
, respectively. The rest of the methods
operate on and return double
values:
double
irrational
=
Math
.
sqrt
(
2.0
);
// 1.414...
int
bigger
=
Math
.
max
(
3
,
4
);
// 4
long
one
=
Math
.
round
(
1.125798
);
// 1
For convenience, Math
also
contains the static final double values E
and PI
:
double
circumference
=
diameter
*
Math
.
PI
;
If the long
and
double
types are not large or precise
enough for you, the java.math
package
provides two classes, BigInteger
and
BigDecimal
, that
support arbitrary-precision numbers. These full-featured classes have a
bevy of methods for performing arbitrary-precision math and precisely
controlling rounding of remainders. In the following example, we use
BigDecimal
to add two very large
numbers and then create a fraction with a 100-digit result:
long
l1
=
9223372036854775807L
;
// Long.MAX_VALUE
long
l2
=
9223372036854775807L
;
System
.
out
.
println
(
l1
+
l2
);
// -2 ! Not good.
try
{
BigDecimal
bd1
=
new
BigDecimal
(
"9223372036854775807"
);
BigDecimal
bd2
=
new
BigDecimal
(
9223372036854775807L
);
System
.
out
.
println
(
bd1
.
add
(
bd2
)
);
// 18446744073709551614
BigDecimal
numerator
=
new
BigDecimal
(
1
);
BigDecimal
denominator
=
new
BigDecimal
(
3
);
BigDecimal
fraction
=
numerator
.
divide
(
denominator
,
100
,
BigDecimal
.
ROUND_UP
);
// 100 digit fraction = 0.333333 ... 3334
}
catch
(
NumberFormatException
nfe
)
{
}
catch
(
ArithmeticException
ae
)
{
}
If you implement cryptographic or scientific algorithms for fun,
BigInteger
is crucial. Other than
that, you’re not likely to need these classes.
As we mentioned in Chapter 4, Java uses the IEEE 754 standard to represent floating-point numbers (float and double types) internally. Those of you familiar with how floating-point math works will already know that “decimal” numbers are represented in binary in this standard by separating the number into three components: a sign (positive or negative), an exponent representing the magnitude in powers of 2 of the number, and a mantissa using up most of the bits to represent the precise value irrespective of its magnitude. While for most applications the precision of float and double-type floating-point numbers is sufficient enough that we don’t need to worry about running into limitations, there are times when specialized apps may wish to work with the floating-point values more directly.
By definition, floating-point numbers trade off precision and
scale. Even the smallest Java floating-point type, float
, can represent
(literally) astronomical numbers ranging from negative
10–45 to positive
1038. This is accomplished, put in decimal
terms, by having the mantissa part of the floating-point value represent
a fixed number of “digits” and the exponent tell us where to put the
decimal point. As the numbers get larger in magnitude, the “precision”
therefore gets shifted to the “left” as more digits appear to the left
of the decimal point. What this means is that floating-point numbers can
very precisly (with a large number of digits) represent small values
like pi, but for bigger numbers (in the billions and trillions)
those digits will be taken up with the more signifcant digits.
Therefore, the gap between any two consecutive numbers that can be
represented by a floating-point value grows larger as the numbers get
bigger.
For some applications, knowing the limitations may be important.
The java.lang.Math
class therefore
provides a few methods for interrogating floats and doubles about their
precision. The Math.ulp()
method
retrieves the “unit of least precision” for a given floating-point
number, which is the smallest value that bits in the mantissa represent
at their current exponent. Another way to say this is that the ulp()
is the approximate distance from the
floating-point number to the next closest higher or lower floating-point
number that can be represented. Adding positive values smaller than half
the ULP to a float will not yield a new number. Adding values between
half and the full ULP will result in the value plus the ULP. The
Math.nextUp()
method is
a convenience that will take a float and tell you the next number that
can be represented by adding the ULP.
float
trillionish
=
(
float
)
1
e12
;
// trillionish ~= 999,999,995,904
float
ulp
=
Math
.
ulp
(
f
);
// ulp = 65536
float
next
=
Math
.
nextUp
(
f
);
// next ~= 1000000061440
trillionish
+=
32767
;
// trillionish still ~= 999,999,995,904. No change!
Additionally, the java.lang.Math
class contains the method
getExponent()
, which
retrieves the exponent part of a floating-point number (and from there
one could determine the mantissa by division). It is also possible to
get the raw bits of a float or double using their corresponding wrapper
class methods floatToIntBits()
and
doubleTo
RawLongBits()
and pick out the (IEEE
standard) bits yourself.
You can use the java.util.Random
class
to generate random values. It’s a pseudorandom-number generator that is
initialized with a 48-bit seed.[31] Because it’s a pseudorandom algorithm, you’ll get the same
series of values every time you use the same seed value. The default
constructor uses the current time to produce a seed, but you can specify
your own value in the constructor:
long
seed
=
mySeed
;
Random
rnums
=
new
Random
(
seed
);
After you have a generator, you can ask for one or more random values of various types using the methods listed in Table 11-3.
Table 11-3. Random-number methods
Method | Range |
---|---|
| |
–2147483648 to 2147483647 | |
| 0 to (n – 1) inclusive |
–9223372036854775808 to 9223372036854775807 | |
0.0 inclusive to 1.0 exclusive | |
0.0 inclusive to 1.0 exclusive | |
Gaussian distributed double with mean 0.0 and standard deviation of 1.0 |
By default, the values are uniformly distributed. You can use the
nextGaussian()
method to create a
Gaussian (bell curve) distribution of double
values, with a mean of 0.0 and a
standard deviation of 1.0. (Lots of natural phenomena follow a Gaussian
distribution rather than a strictly uniform random one.)
The static
method Math.random()
retrieves a random double
value. This method initializes a
private
random-number generator in
the Math
class the first time it’s
used, using the default Random
constructor. Thus, every call to Math.random()
corresponds to a call to
nextDouble()
on that random-number
generator.
[31] The generator uses a linear congruential formula. See The Art of Computer Programming, Volume 2: Semi-numerical Algorithms by Donald Knuth (Addison-Wesley).
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.