Math Concepts: Basics

Data types and basic operations.

Overview

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.

Types

Definitions

Primitives

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.

Wrapper classes

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.

Classes for extended range or precision

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).

Basic operations

Arithmetic operators defined by the Java language

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.

Rounding operations

Modulo operation (remainder)

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

Exponents (powers), roots, and logarithms

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$).