Data types and basic operations.
Virtually all general-purpose programming languages include numeric data types and arithmetic operators for performing simple calculations and storing the results. It’s virtually impossible to advance from a total beginner to an intermediate (or even advanced beginner) level as programmer, without having a solid grasp of these basic types and operations.
Some additional capabilities (e.g. exponentiation, logarithms, trigonometric functions, expected precision/magnitude computations) are generally provided in the language itself, or in the standard library included with the interpreter or compiler. Even if a programmer doesn’t have all of these committed to memory (very few programmers do, in fact), they should have a working understanding of as many of them as possible, a clear view of the range of capabilities provided, and knowledge of where to go to get detailed documentation or other information.
Integer
An integer is a number without a fractional part—for example, 7, -3, and 0 are all integers, while 1.5 is not. The set of all integers (often referred to as $\mathbb{Z}$) consists of the natural numbers (1, 2, 3, …), referred to collectively as $\mathbb{N}$, their additive inverses (-1, -2, -3, …), and the number 0.
The Java language defines several primitive integer data types; additionally, the Java standard library defines wrapper object types corresponding to nearly all of the integer primitive types. In use, these types differ from each other primarily in the range of values that can be represented by each.
Rational number
A rational number is a number that can be expressed as a fraction $p/q$, where $p$ and $q$ are both integers, with $q \neq 0$. The set of rational numbers, denoted $\mathbb{Q}$, is a superset of $\mathbb{Z}$ (the set of integers).
Neither the Java language nor the Java standard library has direct support for rational numbers, but there are many 3rd-party libraries (including the widely used Apache Commons Math library) that do.
Real number
A real number is a value of a continuous, rather than discrete, quantity. We can think of real numbers as those numbers that can represent exactly any finite value along a number line, from $0$ at the center, extending (in opposite directions) towards $-\infty$ and $\infty$. The set of all real numbers is usually denoted as $\mathbb{R}$, and is a superset of the set of rational numbers, $\mathbb{Q}$.
The Java language and standard libraries represent real numbers (exactly or approximately) via 2 different approaches:
Fixed-precision floating-point, consisting of 3 components:
A sign (+1 or -1).
An exponent (positive or negative).
A mantissa, a fractional value in the interval $\left[1, 2\right)$. (Under certain conditions, this is interpreted as a fractional value in $\left[0, 1\right)$.)
The value represented by these components is $sign \cdot mantissa \cdot 2^{exponent}$.
Arbitrary-precision floating-point, with 2 components:
An arbitrary-length integer (positive or negative); this is sometimes called the unscaled value.
A scale exponent (positive or negative).
The value represented is $unscaled \cdot 10^{-scale}$. This can be understood simply as an integer shifted to the right or left by some number of decimal digit places.
It’s important to observe that, even with the arbitrary-precision representation, the precision of encoded values has limits: there are an infinite number of real values that cannot be represented exactly with either of the above approaches. It’s also interesting to note that while most formal definitions of real numbers do not include $-\infty$ and $\infty$, the floating-point representations used by Java and many other programming languages are capable of representing $-\infty$ and $\infty$, as well as NaN (“not a number”—e.g. the value resulting from $0/0$).
Complex numbers
A complex number takes the form $z = a + bi$, where $a$ and $b$ are real numbers, and $i = \sqrt{-1}$. This gives us a definition of the set of complex numbers, $\mathbb{C}$, as
\[\begin{aligned} \mathbb{C} &= \left\{a + bi \mid a,b \in \mathbb{R} \text{ and } i = \sqrt{-1} \right\}. \end{aligned}\](The notation used here is set-builder notation.)
Neither the Java language nor the Java standard library supports complex numbers directly; however, Apache Commons Math library does, as do some other 3rd-party libraries.
The Java language defines 5 integer primitive types (including char
, which is treated as an integer type for numeric computations, and as a single Unicode character in string-related operations), and 2 floating-point primitive types. Values of these 7 types, along with those of the primitive boolean
type, are not objects; they have no behavior (methods), only state (data). However, these primitive types are the basic building blocks—not only of their corresponding wrapper types, but also (indirectly) of all Java classes.
Integer
Type | Size (bits) | Range (inclusive) |
---|---|---|
byte |
8 | $-128 \ldots 127$ |
char |
16 | $0 \ldots \text{65,535}$ |
short |
16 | $-\text{32,768} \ldots \text{32,767}$ |
int |
32 | $-2^{31} \ldots (2^{31} - 1)$ |
long |
64 | $-2^{63} \ldots (2^{63} - 1)$ |
Floating-point
Type | Size (sign/exponent/mantissa bits) | Range | Sig. digits |
---|---|---|---|
float |
1/8/23 | $(-2^{128}, 2^{128})$ | ~7 |
double |
1/11/52 | $(-2^{1024}, 2^{1024})$ | ~16 |
The java.lang
package of the Java standard library includes a wrapper class for each of the primitive types defined in the Java language. Since these are object types, rather than primitive types, they can (and do) define methods. The wrapper classes include methods for parsing and constructing string representations of numeric values, testing for special values, and performing additional operations not provided by the Java language itself. These classes also include constants for the maximum and minimum values representable by the integer types, and for the largest and smallest magnitudes representable by the floating-point types.
Primitive type | Wrapper class |
---|---|
byte |
Byte |
short |
Short |
int |
Integer |
long |
Long |
float |
Float |
double |
Double |
(Note that while the char
primitive has a corresponding Character
wrapper type, the latter is not a subclass of java.lang.Number
, and is thus not considered a numeric wrapper type.)
The Java compiler will, in many (but not all) cases, automatically generate code to wrap a primitive in an instance of its corresponding wrapper type when the latter is expected, or to unwrap a primitive from an instance of its wrapper type when the former is expected. These operations are called auto-boxing and auto-unboxing, respectively.
int a = 15;
Integer b = a + 10; // int value auto-boxed, assigned to Integer.
Integer c = a + b; // Integer auto-unboxed for addition; result
// auto-boxed for assignment.
int d = b * c; // Integers auto-unboxed for multiplication;
// result assigned to int.
The following classes are found in the java.math
package of the Java standard library, and support extended range and precision integer and decimal values, and operations on those values. Unlike the primitive and wrapper types, the size of instances of these types is not fixed by definition, nor at compile time, but depends on the values assigned to them, up to the maximum sizes shown here.
Type | Max. size (bits) | Range (inclusive) |
---|---|---|
BigInteger |
$2^{32}$ | $(-2^{(2^{31} - 1)} + 1) \ldots (2^{(2^{31} - 1)} - 1)$ |
BigDecimal |
$32 + 2^{32}$ | $(-2^{(2^{31} - 1)} + 1) \cdot 10^{(2^{31})} \ldots (2^{(2^{31} - 1)} - 1) \cdot 10^{(2^{31})}$ |
Both BigInteger
and BigDecimal
have a maximum of 646,456,993 significant digits. The smallest absolute value representable by BigDecimal
is $10^{(1 - 2^{31})}$.
As you might infer from the above information, a BigDecimal
instance is composed of a BigInteger
instance (the unscaled value), along with an int
specifying how many decimal digit places the BigInteger
value should be shifted to the right or the left (the scale).
The Java language defines several arithmetic operators—as well as bitwise, logical, reference, and string operators. These are listed—along with their evaluation precedence and other details—in Java Operators.
Signum
The signum function (or sign function) of a number is simply a value corresponding to its sign, where 1 denotes positive, -1 denotes negative, and 0 denotes 0.
\[\begin{aligned} \text{sgn}\left( x \right) &= \left\{ \begin{aligned} 1 & \text{, if } x > 0 \\ 0 & \text{, if } x = 0 \\ -1 & \text{, if } x < 0 \end{aligned} \right. \end{aligned}\]The Java standard library provides the Math.signum
method for obtaining the sign of a floating-point value:
double x = 1.5;
System.out.println(Math.signum(x)); // 1.0
double y = -2.5;
System.out.println(Math.signum(y)); // -1.0
The Math.signum
method can also be used with an integer value, which will automatically be widened to a floating-point representation. Otherwise, a ternary operation can be used—enclosing another ternary operation or an unsigned shift right:
int a = 2;
int b = -3;
System.out.println(Math.signum(a)); // 1.0
System.out.println(Math.signum(b)); // -1.0
System.out.println((a == 0) ? 0 : ((a > 0) ? : 1 : -1)); // 1
System.out.println((b == 0) ? 0 : ((b > 0) ? : 1 : -1)); // -1
System.out.println((a == 0) ? 0 : 1 - 2 * (a >>> 31)); // 1
System.out.println((b == 0) ? 0 : 1 - 2 * (b >>> 31)); // -1
Absolute value
The absolute value of a number is the distance from the origin (zero) to that number, without regard to direction (i.e. the distance is always non-negative).
\[\begin{aligned} \left\vert x \right\vert &= \left\{ \begin{aligned} x & \text{, if } x \geq 0 \\ -x & \text{, if } x < 0 \end{aligned} \right. \end{aligned}\]We can view the absolute value and the signum function as complementary operations:
\[\begin{aligned} x &= \text{sgn}\left( x \right) \cdot \left\vert x \right\vert \end{aligned}\]In Java, the absolute value of an integer or floating-point value can be computed with the Math.abs
method. Alternatively, the overhead of a method call can be avoided (at the cost of reduced code clarity) by using a ternary operation:
double x = 1.5;
System.out.println(Math.abs(x)); // Prints 1.5.
double y = -2.5;
System.out.println(Math.abs(y)); // 2.5
System.out.println((y >= 0) ? y : -y); // 2.5
Floor
The floor of a number is the largest integer that is less than or equal to that number. In other words, the floor of a number is the result obtained by rounding that number down towards $-\infty$.
\[\begin{aligned} \left\lfloor x \right\rfloor &= \max\left\{ m \in \mathbb{Z} \mid m \leq x \right\}. \end{aligned}\](This is another example of set-builder notation.)
The Java standard library method Math.floor
is used for floor rounding of floating-point values to integer values:
double x = 1.5;
System.out.println(Math.floor(x)); // Prints 1.
double y = -2.5;
System.out.println(Math.floor(y)); // Prints -3.
Java also provides the Math.floorDiv
method, which performs integer division with automatic floor rounding.
Ceiling
The ceiling of a number is the smallest integer that is greater than or equal to that number. In other words, the ceiling of a number is the result obtained by rounding that number up towards $\infty$.
\[\begin{aligned} \left\lceil x \right\rceil &= \min\left\{ m \in \mathbb{Z} \mid m \geq x \right\}. \end{aligned}\]The Math.ceil
method in the Java standard library can be used to perform ceiling rounding:
double x = 1.5;
System.out.println(Math.ceil(x)); // Prints 2.
double y = -2.5;
System.out.println(Math.ceil(y)); // Prints -2.
Truncation
While not as common as floor or ceiling in mathematics, truncation—or rounding towards zero—is useful in many computational problems. The simplest way to define this operation is in terms of the floor and ceiling operations:
\[\begin{aligned} \text{trunc}\left( x \right) &= \left\{ \begin{aligned} \left\lfloor x \right\rfloor \text{, if } x \geq 0 \\ \left\lceil x \right\rceil \text{, if } x < 0 \end{aligned} \right. \end{aligned}\]Some programming languages have a function specifically for truncation. In Java, however, truncation is performed implicitly when casting a floating-point value to an integer-type value.
double x = 1.5;
System.out.println((int) x); // 1
double y = -2.5;
System.out.println((int) y); // -2
Also, when an integer-type dividend is divided by an integer-type divisor, using the Java /
operator, the result is automatically truncated.
int a = 7;
int b = 3;
System.out.println(a / b); // 2
System.out.println(a / -b); // -2
Rounding to nearest integer
Rather than rounding towards $-\infty$ (floor), $\infty$ (ceiling) or 0 (truncate), we often want to round to the integer closest to the original value. This is usually what is meant when we refer to rounding without specifying a rounding strategy.
But how should we round a value equidistant from its two nearest integers—for example, should ½ be rounded to 0, or to 1? In the method shown below, the Java library uses the round half up convention, in which a tie results in rounding towards $\infty$. This is expressed mathematically as
\[\begin{aligned} \text{round}\left( x \right) &= \left\lfloor x + \dfrac{1}{2} \right\rfloor = \left\lceil \dfrac{\left\lfloor 2x \right\rfloor}{2} \right\rceil \end{aligned}\]The Java standard library provides the Math.round
method to support nearest-integer rounding using the round half up convention:
double x = 1.25;
double y = 2.5;
double z = -3.5;
System.out.println(Math.round(x)); // 1
System.out.println(Math.round(y)); // 3
System.out.println(Math.round(z)); // -3
The modulo operation is the computation of the remainder obtained after division of one number by another. Mathematically, this is defined as
\[\begin{equation} \begin{aligned} a \bmod n &= a - n \left\lfloor \dfrac{a}{n} \right\rfloor \text{, where } n \in \mathbb{N} \end{aligned} \label{modulo-traditional} \end{equation}\]Alternatively (but not equivalently), we may define it as
\[\begin{equation} \begin{aligned} a \bmod n &= a - n \cdot \text{trunc}\left( \dfrac{a}{n} \right) \text{, where } n \in \mathbb{N} \end{aligned} \label{modulo-java} \end{equation}\]In number theory, the divisor (or modulus) is generally assumed to be a positive integer, as shown above. In computation, it is often useful to broaden this assumption, allowing the divisor to be positive or negative, and integer or real-valued. (In any event, the result of the modulo operation using a divisor of 0 is undefined; if using floating-point values in Java, the result will be Float.NaN
or Double.NaN
.)
Java has two mechanisms for performing the modulo operation: the Math.floorMod
method, which implements $\eqref{modulo-traditional}$, and the %
operator (included in Java Operators, which implements $\eqref{modulo-java}$. This distinction leads to the two approaches giving different results when the signs of the dividend and divisor are different. (Both %
and Math.floorMod
allow negative values for both the divisor and the dividend—however, Math.floorMod
only allows integer-type dividend and divisor, while %
supports floating-point values as well.)
int a = 7;
int b = 3;
System.out.println(a % b); // 1
System.out.println(Math.floorMod(a, b)); // 1
int c = -3;
System.out.println(a % c); // 1
System.out.println(Math.floorMod(a, c)); // -2
Assume we have 3 numbers, $b$, $p$, and $c$, with
\[\begin{equation} \begin{aligned} b^p &= c \end{aligned} \label{exponent} \end{equation}\]We might read this as “$b$ raised to the $p^{\text{th}}$ power equals $c$.”
If we recognize that $\left(a^m\right)^n = a^{mn}$, and if $p \neq 0$, we might solve for $b$ in this fashion:
\[\begin{aligned} \left(b^p\right)^{1/p} &= c^{1/p} \\ b &= c^{1/p} \end{aligned}\]By definition, and by the conventions used with the √ notation, this gives us
\[\begin{equation} \begin{aligned} b &= \sqrt[p]{c} \end{aligned} \label{root} \end{equation}\]Thus, one solution to $\eqref{exponent}$ is found in $\eqref{root}$. We can read the latter as “$b$ equals the $n^{\text{th}}$ root of $c$.” We also see that computing the $n^{\text{th}}$ root of a number is the same as raising that number to the $\left(1/n\right)^{\text{th}}$ power; in general, this is how we compute roots in Java.
If $b > 0$ and $c > 0$, we can express $\eqref{exponent}$ in terms of a solution for $n$:
\[\begin{equation} \begin{aligned} \log_b\left(b^n\right) &= \log_b c \\ n \log_b b &= \log_b c \\ n &= \log_b c \end{aligned} \label{logarithm} \end{equation}\]Finally, an identity of logarithms tells us that we can express the base-$b$ logarithm of $\eqref{exponent}$ using a different base—for example, $e$, the base of natural logarithms:
\[\begin{equation} \begin{aligned} \log_b c &= \dfrac{\ln c}{\ln b} \end{aligned} \label{logarithm-equivalence} \end{equation}\]The Math
class of the Java standard library defines the log
(natural logarithm), log10
(base-10, or common, logarithm), pow
(power), and exp
($e^x$) methods to perform the above operations.
double x = 10;
double y = 2;
double z = Math.pow(x, y);
System.out.println(z); // 100.0
System.out.println(Math.pow(z, 1 / y)); // 10.0
System.out.println(Math.log10(z)); // 2.0
System.out.println(Math.log(z)); // 4.605170185988092
System.out.println(Math.log(z) / Math.log(x)); // 2.0
System.out.println(Math.exp(Math.log(z))); // 100.00000000000004
There are also a number of methods in the Math
class that implement special-case power, root, and logarithmic computations. These include sqrt
(equivalent to $x^{1/2}$ or $\sqrt{x}$), cbrt
($x^{1/3}$ or $\sqrt[3]{x}$), expm1
($e^x - 1$), log1p
($\ln \left( 1 + x \right)$), and scalb
($x \cdot 2^y$).