1. Mathematics
Integers and floating point numbers already appeared in the first exercises; we were using the common mathematical operators to crunch numbers. But Java does not have an operator for everything, and so the class Math
offers further methods and constants; early on we used Math.random()
, for example. In the following tasks of this chapter, we will focus on various roundings in particular, and we will look at methods of the Math
class. In addition, the java.math
package provides two classes that can be used to represent arbitrarily large numbers — there are tasks for these as well.
Prerequisites
know rounding methods of the class `Math
know
BigInteger
and `BigDecimal
Data types used in this chapter:
1.1. The class Math
The class Math
contains a large number of mathematical functions, but it is important to also look at wrapper classes, for example, or at the Scanner
or Formatter
when it comes to converting a number to a string or parsing a string so that there is a primitive type at the end again.
1.1.1. Quiz: Rule of thumb ⭐
There are different approaches and different kinds of problems for rounding numerical values:
On the one hand, a floating point number can be converted to an integer,
on the other hand, the number of decimal places can be reduced for a floating point number.
The Java library supports different possibilities for conversion and rounding, among other things with
the explicit type conversion
(int) double
→int
Math.floor(double)
→double
Math.ceil(double)
→double
Math.round(double)
→long
Math.rint(double)
→double
BigDecimal
andsetScale(..)
→BigDecimal
NumberFormat
, configured withsetMaximumFractionDigits(…)
, thenformat(…)
→String
DecimalFormat
, configured withsetRoundingMode(…)
, thenformat(…)
→String
Formatter
with a format string, thenformat(…)
→String
.
Four of the methods originate from Math
, but when it comes to very large and precise floating point numbers or string representation, other classes are involved. Since the methods differ slightly in the result, the following task should make these differences clear once again.
Task:
Fill in the following table with the results of the roundings:
double
valued
(int) d
(int) floor(d)
(int) ceil(d)
round(d)
(int) rint(d)
2.5
1.9
1.6
1.5
1.1
1.1
1.5
1.6
1.9
2.5
1.1.2. Check if Tin Tin cheated on rounding. ⭐
The accountant Tin Tin does the accounting for Captain CiaoCiao of income and expenses. She gets positive and negative floating point values and writes down the total as a rounded integer for a summary at the end. Captain CiaoCiao suspects that Tin Tin is not being completely honest and is pocketing the penny amounts, when she should use commercially rounding. As a reminder, if the number at the first omitted decimal place is a 0, 1, 2, 3, or 4, round down; if it is 5, 6, 7, 8, or 9, round up.
To test his guess, Captain CiaoCiao needs a program that can sum and check different rounding modes.
Task:
Given an array of floating point numbers (positive and negative) and the sum converted to an integer by Tin Tin.
Captain CiaoCiao wants to find out which rounding was used to form the integer of the sum. Therefore, the elements in the array are to be summed and compared to Tin Tin’s sum. The rounding is done after the numbers are added.
Implement a method
RoundingMode detectRoundingMode(double[] numbers, int sum)
that gets adouble
array of numbers and the sum of Tin Tin and checks which rounding mode was used.To allow the rounding mode to be represented, introduce an enumeration type:
enum RoundingMode { CAST, ROUND, FLOOR, CEIL, RINT, UNKNOWN; }
The enumeration elements represent different rounding modes:
(int)
, that is, a type conversion.(int) Math.floor(double)
(int) Math.ceil(double)
(int) Math.rint(double)
(int) Math.round(double)
Which rounding is bad for Captain CiaoCiao and good for Tin Tin? Which variation could Tin Tin use to cheat?
Example:
The call might look like this:
double[] numbers = { 199.99 }; System.out.println( detectRoundingMode( numbers, 200 ) );
Notes:
There is an enumeration type
RoundingMode
in thejava.math
package, but for our case it does not fit the task.It may well happen that more than one rounding mode fits — such as when the sum of the floating point values itself gives an integer — then the method is free to choose one of the rounding modes.
1.2. Huge and very precise numbers
The classes java.math.BigInteger
and java.math.BigDecimal
can be used to represent arbitrarily large integer and floating point numbers.
1.2.1. Calculate arithmetic mean of a large integer ⭐
To calculate the arithmetic mean of two numbers, they are added together and divided by two. This works well if the sum of the two values does not exceed the largest number that can be represented. If there is an overflow, the result is wrong. There are some algorithms in computer science that can handle this problem as well, but we can make it a little easier and take the higher data type in each case. For example, if we want to calculate the mean value of two int
variables, we convert the two int
to a long
, add the numbers, divide and convert back to the int
. With the long
data type, there is no larger primitive data type, but the BigInteger
type can be used well for the case.
Task:
Calculate the arithmetic mean of two
long
values so that there is no overflow and wrong results. The result should be along
again.
1.2.2. Number by number over the phone ⭐
A new deal has made Bonny Brain a lot of money. The number is so big that you can’t just announce it over the phone at all; it has to be conveyed in chunks.
Task:
Write a new method
BigInteger completeNumber(int... parts)
that gets a variable number of numbers and returns the big total number at the end.
Example:
completeNumber(123, 22, 989, 77, 9)
returns aBigInteger
with the value12322989779
.
1.2.3. Develop class for fractions and truncate fractions ⭐⭐
Bonny Brain is trying a new recipe for a rum punch. Fractions like "1/4 liter grape juice" or "1/2 liter rum" keep appearing in the directions for making it. For the party, she prepares 100 servings, and fractions like "100/4 liters" occur. The fractions can be shortened so that Bonny Brain knows, for example, that 25 liters of grape juice must be purchased.
Task:
Create a new class
Fraction
.Let there be a constructor
Fraction(int numerator, int denominator)
that stores numerator and denominator inpublic final
variables.Consider whether there can be faulty arguments, which we should report through exceptions.
Every created fraction should be simplified automatically. Use the
gcd(…)
method ofBigInteger
, which calculates the greatest common divisor (ggT)_. Remember: The ggT of numerator and denominator is the largest number by which both are divisible. You can simplify the fraction by dividing both numerator and denominator by this number.Both numerator and denominator can be negative, but then you can flip the sign so that both values become positive.
The objects are all supposed to be immutable, and therefore the variables can be
public
, since they are not supposed to be changed after initialization via the constructor. In other words, the classFraction
does not contain any setters.
Add the constructor
Fraction(int value)
, where the denominator automatically becomes1
.Implement a method to multiply fractions and detect overflows.
Implement a method
reciprocal()
that returns the inverse of a fraction, that is, swaps the numerator and denominator. With the help of this method, the division of fractions can be implemented.Fraction
shall extendjava.lang.Number
and implement all mandatory methods.Fraction
shall implementComparable
, because fractions can be converted to a decimal number, and decimal numbers have a natural order.Fraction
shall implementequals(…)
andhashCode()
correctly.Implement a
toString()
method that returns a string as bare as possible.
Fraction
.1.3. Suggested solutions
1.3.1. Quiz: Rule of thumb
value d  (int) d  (int) floor(d)  (int) ceiling(d)  round(d)  (int) rint(d) 





























































Math.round(double)
and Math.rint(double)
differ for .5
numbers "on the edge":
round(double)
rounds commercially. As can be seen in the table,round(2.5)
,round(1.5)
,round(1.5)
, andround(2.5)
round up; the numbers always become larger. If one always rounds up only commercially, this produces small errors, because the procedure is not symmetrical. To be fair, some.5
would also have to be rounded down; this is where another method comes into play.rint(double)
behaves likeround(double)
for numbers not ending in.5
, but when ending in.5
the following rule applies: the last digit to be retained becomes even. This behavior is called symmetric (also mathematical, scientific) rounding, because it rounds up or down in half the cases, respectively.
In summary:
procedure  result 

type conversion  Truncates decimal places of a floating point number. 
 Rounds to the next smaller number against ∞. 
 Round to the next larger number against +∞. 
 Rounds the value according to commercial rules. 
 Rounds 
1.3.2. Check if Tin Tin cheated on rounding.
Now we come to the question of which rounding is bad for Captain CiaoCiao and good for Tin Tin. The difference between the rounded value and the sum is the difference Tin Tin can pocket. Example: If the total is 222.22 and Tin Tin rounds to 222, there is a difference of 0.22 that Captain CiaoCiao misses and goes into Tin Tin’s pocket. What do the differences look like for the example numbers?
value d  d  (int) d  d  floor(d)  d  ceil(d)  d  round(d)  d  rint(d) 

2.5  0.5  0.5  0.5  0.5  0.5 
1.9  0.9  0.1  0.9  0.1  0.1 
1.6  0.6  0.4  0.6  0.4  0.4 
1.5  0.5  0.5  0.5  0.5  0.5 
1.1  0.1  0.9  0.1  0.1  0.1 
1.1  0.1  0.1  0.9  0.1  0.1 
1.5  0.5  0.5  0.5  0.5  0.5 
1.6  0.6  0.6  0.4  0.4  0.4 
1.9  0.9  0.9  0.1  0.1  0.1 
2.5  0.5  0.5  0.5  0.5  0.5 
Total  0  5  5  2  0 
We can read that for the numbers Tin Tin chose, she gets the most out of rounding off all the sums, because that makes the numbers smaller, and she gets the difference in each case, whether the numbers are negative or positive.
To the proposed solution:
public enum RoundingMode {
CAST, ROUND, FLOOR, CEIL, RINT, UNKNOWN
}
private static RoundingMode detectRoundingMode( double value, int rounded ) {
return rounded == (int) value ? RoundingMode.CAST :
rounded == Math.round( value ) ? RoundingMode.ROUND :
rounded == Math.floor( value ) ? RoundingMode.FLOOR :
rounded == Math.ceil( value ) ? RoundingMode.CEIL :
rounded == Math.rint( value ) ? RoundingMode.RINT :
RoundingMode.UNKNOWN;
}
public static RoundingMode detectRoundingMode( double[] numbers, int sum ) {
double realSum = 0;
for ( double number : numbers )
realSum += number;
return detectRoundingMode( realSum, sum );
}
The proposed solution is composed of two methods. The public method detectRoundingMode(…)
sums the elements of the array and calls the private method detectRoundingMode(double, int)
to determine the actual rounding mode. The method sequentially compares the integer value with the different rounded variants, and if there is a match, the enumeration element is returned. If the totals and the rounded value do not match at all, the return is RoundingMode.UNKNOWN
.
1.3.3. Calculate arithmetic mean of a large integer
private final static BigDecimal TWO = BigDecimal.valueOf( 2 );
public static long meanExact( long x, long y ) {
BigInteger bigSum = BigInteger.valueOf( x ).add( BigInteger.valueOf( y ) );
BigInteger bigMean = bigSum.divide( BigInteger.TWO );
return bigMean.longValue();
}
In the first step we build a new object of type BigInteger
for the parameters x
and y
using the factory method valueOf(long)
. In the second step the sum is built. In the third step the sum is divided by 2. For the division we also have to use a BigInteger
object for the divisor. Since the divisor is constant, the solution defines its own constant — the class BigInteger
has constants for 0, 1 and 10, but not for 2.
When averaging integers, there is always the question of rounding. The addition of two even or two odd numbers always results in an even number, but the sum of an even number with an odd number is odd. If odd numbers are divided by two, there is a remainder, and the question is whether to round up or down, round the result to 0, or round to plus or minus infinity — there are quite a few possibilities. The BigInteger
method divide(…)
behaves like the division of two integers, it is rounded towards 0.
The end result is a number, not greater than Long.MAX_VALUE
and not smaller than Long.MIN_VALUE
, so converting the number from the BigInteger
with longValue()
gives no loss.
1.3.4. Number by number over the phone
static BigInteger completeNumber( int... parts ) {
StringBuilder bigNumber = new StringBuilder( parts.length * 2 );
for ( int part : parts )
bigNumber.append( part );
return new BigInteger( bigNumber.toString() );
}
We can solve the task using strings or math. Using a temporary string the task is easy to solve.
In the first step we run the array, take all the numbers one by one and append them to a StringBuilder
. If we assume that each number consists on average of two digits, we can estimate approximately how big the result will be; therefore we use the parameterized constructor of StringBuilder
with a capacity.
After appending all the digits, StringBuilder
is converted to a String
and this feeds the constructor of BigInteger
. Here the actual conversion of the string into a BigInteger
begins.
Of course, the task can also be solved without a temporary StringBuilder
. Let us take the call as an example
completeNumber(123, 22, 989, 77, 9);
If at the end we want to create the BigInteger
with 12322989779
, we could first build the BigInteger
with 123
, then multiply the number with 100
and add 22
, resulting in 12322
. In the next step, we take the result, multiply it by 1000
and add 989
. Then we multiply again by 100
, add 77
, multiply the result by 10
and add 9
. That is, we always multiply the old value by 10^{n}, where n is the number of digits in the coming number.
1.3.5. Develop class for fractions and truncate fractions
import java.math.BigInteger;
public final class Fraction extends Number implements Comparable<Fraction> {
public final int numerator;
public final int denominator;
public Fraction( int numerator, int denominator ) {
if ( denominator == 0 )
throw new ArithmeticException( "denominator of a fraction can't be 0" );
// denominator always positive
if ( denominator < 0 ) {
numerator = numerator;
denominator = denominator;
}
// shortcut if denominator == 1
if ( denominator == 1 ) {
this.numerator = numerator;
this.denominator = 1;
}
else {
// try to simplify every fraction
int gcd = gcd( numerator, denominator );
// might be 1, but divide anyway
this.numerator = numerator / gcd;
this.denominator = denominator / gcd;
}
}
private static int gcd( int a, int b ) {
return BigInteger.valueOf( a )
.gcd( BigInteger.valueOf( b ) )
.intValue();
}
public Fraction( int value ) {
this( value, 1 );
}
public Fraction reciprocal() {
return new Fraction( denominator, numerator );
}
public Fraction multiply( Fraction other ) {
return new Fraction( Math.multiplyExact( numerator, other.numerator ),
Math.multiplyExact( denominator, other.denominator ) );
}
@Override
public int intValue() {
return numerator / denominator;
}
@Override
public long longValue() {
return (long) numerator / denominator;
}
@Override
public double doubleValue() {
return (double) numerator / denominator;
}
@Override
public float floatValue() {
return (float) numerator / denominator;
}
@Override
public int compareTo( Fraction other ) {
return Double.compare( doubleValue(), other.doubleValue() );
}
@Override
public boolean equals( Object other ) {
if ( other == this )
return true;
if ( ! (other instanceof Fraction otherFraction) )
return false;
return numerator == otherFraction.numerator
&& denominator == otherFraction.denominator;
}
@Override
public int hashCode() {
return numerator + Integer.reverse( denominator );
}
@Override
public String toString() {
return numerator == 0 ? "0" :
denominator == 1 ? "" + numerator :
numerator + " / " + denominator;
}
}
Let’s start with the constructor Fraction
, which takes the numerator and denominator. The denominator must never be 0
, this is punished with an exception. We could in principle build our own Exception
class, but an ArithmeticException
fits well.
Numerator and denominator can be negative or positive, there are four cases:
numerator > 0
,denominator > 0
.numerator < 0
,denominator > 0
numerator > 0
,denominator < 0
numerator < 0
,denominator < 0
Let us consider two cases:
If the numerator and denominator are both negative, we can reverse the sign so that both values become positive.
If the denominator is negative and the numerator is positive, we can shift the sign to the numerator.
Both cases can be handled with a control flow statement that if the denominator is negative, we flip both signs. A negative denominator then becomes positive. If the numerator was also negative, the numerator becomes positive, so a negative numerator and negative denominator thus both become positive. If the denominator was negative and the numerator was positive, we thus shift the sign to the numerator, because the numerator becomes negative.
Numerator and denominator are now prepared, and if the denominator is 1
, then the fraction does not need to be truncated, and we save the following work and leave the constructor, otherwise we continue in the else
block.
The last part of the constructor deals with the reduction of the fraction. The task briefly explained how to proceed here: we need the greatest common divisor. The constructor delegates here to its own method gcd(int, int)
, which first builds a BigInteger
for two numbers, then lets it calculate the greatest common divisor and then returns it as an integer. In our scenario, the parameter types are int
so the divisor will be smaller in any case, with intValue()
we read from the BigInteger
. After the constructor asks for the greatest common divisor, we divide the numerator and denominator by it, initializing our object variables. In principle, gcd could also be 1, so we don`t need a division, but we save this special case distinction here, because a division by 1
is not expensive.
The second constructor takes only one numerator, and in that case the denominator is 1
. We delegate to our own constructor, but in general we can do the storing ourselves, since we don`t need the special treatment for a negative denominator, and the denominator is not 0
either.
The first of the two methods reciprocal()
creates a new Fraction
object by turning the denominator into the numerator and the numerator into the denominator. The method multiply(…)
multiplies its own numerator with the numerator of the passed object and symmetrically does the same for the denominator. Multiplication quickly produces large numbers and the value range of int
could be blown up, so Math.multiplyExact(…)
makes sure that the product fits into an int
; otherwise an exception follows.
The next category of methods comes from the base type Number
. We divide numerator by denominator and return the result at the different methods.
The next method compareTo(…)
comes from the Comparable
interface. We make it simple and calculate the quotient and compare our quotient with the passed fraction.
In the next step we override the method equals(…)
and hashCode()
. A good hash code will return a different hash code if the numerator or denominator changes. The realization of hashCode()
does this by flipping the bits of the denominator, so if the number of required bits of the numerator and denominator together does not exceed 32 (the number of bits of an int
), we can detect each change by a different hashcode.
The last method is toString()
. If the numerator is 0
, we don`t even have to look at the denominator and return the string 0
. If the denominator is 1
, we can return only the numerator. Otherwise, we return the numerator and denominator separated by a fraction bar.
Java 8 Backport Java 16 introduces pattern variables, which the proposed solution uses at:
Users of earlier Java versions must write instead:
