Math is infinite.
Computers are finite.
Thus computer math is full of compromises.
In order to make int math fast, ints are limited to a fixed range of values from -2,147,483,648 to 2,147,483,647 (a.k.a. -231 to 231 - 1) and division truncates so it still produces an int.
Division can lose arbitrarily large amounts of precision, e.g. 99999/100000 ==
0
Addition and subtraction “overflow”, wrapping around from the largest positive int to the largest negative and vice versa.
The largest negative value, also called the “weird number”, has no positive counterpart. Thus multiplying it by -1 or taking its absolute value also wraps around to itself! Weird.
Integer.MAX_VALUE + 1 == Integer.MIN_VALUE // overflow
Integer.MIN_VALUE - 1 == Integer.MAX_VALUE // overflow
Integer.MAX_VALUE / 2 * 2 == 2147483646 // truncation loses 1
Integer.MAX_VALUE * 2 / 2 == -1 // wrapping way around
-1 * Integer.MIN_VALUE == Integer.MIN_VALUE // weird
Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE // weird
The double type is more complex and works well to represent scientific kinds of numbers from very small (4.9E-324) to very large (1.7976931348623157E308) and has more than 9 quadrillion gradations between 0.0 and 1.0 while still allowing for pretty efficient computation.
But it has a bunch of compromises built in such as limited precision (about 16 decimal places) and the fact that it is a binary, rather than decimal, format.
Values requiring more precision than a double can represent are only approximate.
Order of operations matters because of rounding of intermediate results.
Values with fractional parts that can’t be expressed with an integral power of 2 in the denominator can’t be represented exactly.
This is for the same reason that 1/3 can’t be represented exactly in a finite decimal number.
This includes such common numbers as 0.1, and 0.05.
On the other hand, 0.5, 0.25, and 0.125 are all fine.
// Very big +/- very small loses the small number entirely
1e9 + 1e-9 == 1e9
1e16 - 1 == 1e16
// We'd expect 0.1 + 0.2 to equal 0.3
0.1 + 0.2 == 0.30000000000000004
// Mathematically these should be equal and yet ...
5.0 * 2.0 / 3.0 != 5.0 * (2.0 / 3.0)
// 0.1 can't be represented exactly. Neither of these
// are actually an exact value. The true value is
// 0.1000000000000000055511151231257827021181583404541015625
0.1 == 0.10000000000000001
You mostly just need to be aware of these issues.
In particular know that == can provide surprising results with doubles since values that should be the same mathematically may not be exactly the same.
In the real world, if the limits of ints are just a bit too confining we can sometimes use Java’s long datatype which is a 64-bit integer and which is basically just as fast on modern CPUs.
For doing efficient computation with doubles there’s a whole field called Numerical Analysis devoted to figuring out the best ways to do complex calculations while minimizing the loss of precision.
There’s also a class called BigInteger that lets us do math with arbitrarily large integer values, at the cost of some speed.
Similarly, there’s a class BigDecimal that can represent arbitrary precision decimal numbers with control over how many decimal places are used for unrepresentable values like 1/3.
Serious computations involving money are very particular and there tend to be special purpose classes for dealing with them.
Whatever you do, don’t use doubles to represent money as they can’t even accurately represent a nickle or a dime!
In a pinch use ints or longs to count pennies and only convert to dollars and cents when displaying values to humans.
Remainder operator has three main uses:
Checking if a number is even: n % 2 == 0
Getting the last (ones-place) digit of a number 1234 % 10 ⟹ 4
Looping numbers around a fix range, like on a clock. In 47 hours, the hour will be (currentHour + 47) % 12
In Java the % operator is the remainder operator not the modulo operator.
Dividing two integers, \(a\) and \(b\) (\(b \ne 0\)) gives us a quotient \(q\) and a remainder \(r\) such that:
\(bq + r = a\)
Or, solving for \(r\):
\(r = a - bq\)
\(15/12 = 1 \text{ with remainder } 3\)
\(12(1) + 3 = 15\)
What about \(-15/12\)?
\(q = -1, r = -3 \quad\text{gives}\quad 12(-1) + -3 = -15\)
But also:
\(q = -2, r = 9 \quad\text{gives}\quad 12(-2) + 9 = -15\)
In real rumbers \(-15/12 = -1.25\).
If we truncate \(-1.25\) (round toward zero) we get \(-1\).
If we floor \(-1.25\) (round toward \(-\infty\)) we get \(-2\).
Remainder gives us \(r\) after a truncating division; modulo gives us \(r\) after a floor division.
Because int division truncates, Java’s % is remainder.
n % 2 == 0 is always true if n is even, positive or negative.
n % 2 == 1 is only true if n is odd and positive.
-3 % 2 ⟹ -1
So the best way to check if something is odd is n % 2 != 0.