Java variables exercises — BMI, seconds and the print shop inventory (and the percentage that got stuck at 100)

In the Java variables practice we triggered the three traps of the topic on purpose: integer division, overflow and == with texts. Now it’s your turn, with a twist: here they won’t be the stars. They show up as tools (or as obstacles) inside ordinary problems, which is how they show up in exams.

What happened to me with exercise 2 the first time I did it: the breakdown into days, hours and minutes came out perfect, and the percentage of the day got stuck at 100 %, whatever number of seconds over a day I tried. You’ll find it in «What’s wrong here?».

The output is real, from Java 21 on a computer set to English, apart from the javac messages, which are the same in any language.

How to use this article

There are three exercises of rising difficulty. Each one has its task, the output your program should give (it’s the real output of the solution) and a box with hints. Try each one for at least ten minutes before opening the hint, and the hint before the solution. After that come «What’s wrong here?» with three very common mistakes, the commented solutions, an exam-style quiz and the topic’s cheat sheet.

As in the practice, the data is written in the program: we don’t use Scanner, if or loops yet (they arrive in flow control and in loops). That forces you to solve everything with variables, operators and types, which is exactly what you need to master right now.

Basic level

Exercise 1: BMI calculator

Write a program that, for a weight of 70 kg and a height of 1.75 m, works out the Body Mass Index, the healthy weight range for that height and which category it belongs to. As we don’t have if yet, each category is shown as a boolean (true for the right one).

  • BMI = weight / (height × height).
  • Healthy range: BMI between 18.5 and 24.9. The minimum weight is 18.5 × height² and the maximum, 24.9 × height².
  • Categories: underweight (< 18.5), normal weight (from 18.5 to under 25), overweight (from 25 to under 30) and obesity (30 or more).

Expected output:

=== BMI CALCULATOR ===
Weight (kg): 70.0
Height (m):  1.75
BMI:             22.86
Healthy weight:  between 56.7 kg and 76.3 kg
Underweight:   false
Normal weight: true
Overweight:    false
Obesity:       false

height^2 isn’t a power: ^ in Java is bitwise XOR, and with a double it doesn’t even compile. Multiply height * height or use Math.pow(height, 2). A comparison like bmi >= 18.5 && bmi < 25 is already a boolean: store it in a variable and print it with println. The limits 18.5 and 24.9 work well as final constants.

Intermediate level

Exercise 2: time converter

Write a program that breaks 90061 seconds down into days, hours, minutes and seconds, also shows it in clock format (1 d 01:01:01), works out what percentage of a full day it is and, as a check, rebuilds the total from the four pieces.

Expected output:

=== TIME CONVERTER ===
Total seconds: 90061
1 d, 1 h, 1 min and 1 s
Clock format: 1 d 01:01:01
Percentage of a day: 104.24 %
Check: 90061 seconds

The whole breakdown is integer division and remainder: first the days (/ 86400), and with what’s left (% 86400), the hours, and so on downwards. You don’t need any double until the percentage, and there the cast goes before dividing. To get hours with two digits (01), printf with %02d. And to write a % in a printf, %%.

Final challenge

Exercise 3: the print shop inventory

The faculty print shop has three products (made-up prices):

ProductBase priceUnits
Laptop899.99 €5
Mouse29.99 €23
Keyboard79.99 €12

Write a program that shows a table with the base price, the price with IGIC (the Canary Islands sales tax, 7 % at the general rate) and the units; the total number of units; the inventory value (price × units of each product, added up) worked out in two ways, with double and with cents in a long; the most expensive and the cheapest product, and the average price per unit. No arrays and no if: three sets of variables.

Expected output:

PRINT SHOP INVENTORY
Product         Base     +IGIC  Stock
Laptop        899.99    962.99      5
Mouse          29.99     32.09     23
Keyboard       79.99     85.59     12
Units: 40
Value as double (println):  6149.599999999999
Value as double (printf):   6149.60 €
Value in cents: 614960 → 6149.60 €
Most expensive: 899.99 € | cheapest: 29.99 €
Average price per unit: 153.74 €

To turn a price into cents, Math.round(price * 100), which returns a long. The value in cents can be written as euros with / 100 and % 100 (quotient and remainder). For the most expensive without if, Math.max takes two numbers: nest one inside the other. And look closely at the println line with double: it isn’t a bug in your program.

What’s wrong here?

Three fragments with very common mistakes. Before reading the explanation, decide what each one prints (or whether it compiles).

Fragment 1: the percentage stuck at 100

public class Percentage {
    public static void main(String[] args) {
        int seconds = 90061;
        double percentage = seconds / 86400 * 100;
        System.out.printf("%.2f %%%n", percentage);
    }
}

What it prints:

100.00 %

seconds / 86400 are two int values: 90061 / 86400 = 1. Then 1 × 100 = 100, which is stored in the double as 100.0. The variable being double doesn’t help, because the division has already been done as integers. The fix is to convert before dividing: (double) seconds / 86400 * 100, which gives 104.24. That was my stubborn 100 % from the start.

Fragment 2: the stock value

public class StockValue {
    public static void main(String[] args) {
        double price = 29.99;
        int stock = 23;
        int value = price * stock;
        System.out.println(value);
    }
}

What javac says:

StockValue.java:5: error: incompatible types: possible lossy conversion from double to int
        int value = price * stock;
                          ^
1 error

price * stock is double × int, and the result is double (689.77). Storing it in an int would lose the cents, and Java won’t do that without permission. There are two fixes, depending on what you want: double value = price * stock; if you want the amount, or working in cents (long cent = Math.round(price * 100) * stock;) if you want an exact whole number. The cast (int) (price * stock) would compile, but it would throw away the 77 cents.

Fragment 3: the username that doesn’t match

public class Username {
    public static void main(String[] args) {
        String name = "laura";
        String username = name.substring(0, 1) + "santana";
        System.out.println(username);
        System.out.println(username == "lsantana");
    }
}

What it prints:

lsantana
false

The first line shows the text is lsantana. And still == gives false: username was built at run time with substring and +, so it’s a new object, different from the literal "lsantana". == compares the arrows, not the letters. Fix: username.equals("lsantana").

Commented solutions

Solution to exercise 1

/** Exercise 1: BMI with fixed data and the classification with booleans (no if, which arrives in the next topic). */
public class BMICalculator {
    public static void main(String[] args) {
        final double BMI_MIN = 18.5;              // limits of the healthy range
        final double BMI_MAX = 24.9;
        double weight = 70;                       // the int 70 turns into 70.0 by itself
        double height = 1.75;

        double bmi = weight / (height * height);  // height^2 in Java would be something else (XOR)
        double weightMin = BMI_MIN * height * height;
        double weightMax = BMI_MAX * height * height;
        boolean underweight = bmi < BMI_MIN;
        boolean normalWeight = bmi >= BMI_MIN && bmi < 25;
        boolean overweight = bmi >= 25 && bmi < 30;
        boolean obesity = bmi >= 30;

        System.out.println("=== BMI CALCULATOR ===");
        System.out.println("Weight (kg): " + weight);
        System.out.println("Height (m):  " + height);
        System.out.printf("BMI:             %.2f%n", bmi);
        System.out.printf("Healthy weight:  between %.1f kg and %.1f kg%n", weightMin, weightMax);
        System.out.println("Underweight:   " + underweight);
        System.out.println("Normal weight: " + normalWeight);
        System.out.println("Overweight:    " + overweight);
        System.out.println("Obesity:       " + obesity);
    }
}

Why it’s written like this:

  • double weight = 70;: the 70 is an int, but it fits in a double and Java converts it on its own (widening). That’s why the output says 70.0.
  • The constants BMI_MIN and BMI_MAX avoid writing 18.5 and 24.9 in several places: if the criterion changed, you’d change one line.
  • bmi >= BMI_MIN && bmi < 25: for normal weight we use < 25 and not <= 24.9. Why? Because a BMI of 24.95 is neither normal weight (it’s over 24.9) nor overweight (it doesn’t reach 25): it would end up with no category. With < 25 there are no gaps between the categories.

Sums by hand: 1.75 × 1.75 = 3.0625; 70 / 3.0625 = 22.857… → 22.86. Minimum weight: 18.5 × 3.0625 = 56.66 → 56.7; maximum: 24.9 × 3.0625 = 76.26 → 76.3. (The previous version of this article said 80.1 kg: it was wrong, it didn’t follow from its own formula.)

Solution to exercise 2

/** Exercise 2: breaking down 90061 seconds with / and %, and the percentage of a day with the cast in the right place. */
public class TimeConverter {
    public static void main(String[] args) {
        final int SEC_DAY = 24 * 60 * 60;          // 86400: let Java work it out
        final int SEC_HOUR = 60 * 60;              // 3600
        int totalSeconds = 90061;

        int days = totalSeconds / SEC_DAY;         // whole days
        int remainder = totalSeconds % SEC_DAY;    // what is left after taking out the days
        int hours = remainder / SEC_HOUR;
        remainder = remainder % SEC_HOUR;          // we reuse remainder
        int minutes = remainder / 60;
        int seconds = remainder % 60;

        double percentage = (double) totalSeconds / SEC_DAY * 100;   // cast BEFORE dividing
        int back = days * SEC_DAY + hours * SEC_HOUR + minutes * 60 + seconds;

        System.out.println("=== TIME CONVERTER ===");
        System.out.println("Total seconds: " + totalSeconds);
        System.out.printf("%d d, %d h, %d min and %d s%n", days, hours, minutes, seconds);
        System.out.printf("Clock format: %d d %02d:%02d:%02d%n", days, hours, minutes, seconds);
        System.out.printf("Percentage of a day: %.2f %%%n", percentage);
        System.out.println("Check: " + back + " seconds");
    }
}
90061 seconds, step by step90061 / 86400 gives 1 day and 90061 % 86400 leaves 3661; 3661 / 3600 gives 1 hour and leaves 61; 61 / 60 gives 1 minute and 61 % 60 gives 1 second. The check goes back to 90061.90061/ 864001 day% 864003661/ 36001 hour% 360061/ 601 min% 601 s1 × 86400 + 1 × 3600 + 1 × 60 + 1 = 90061/ takes the whole units; % passes what is left on to the next step
Each integer division takes out one unit and each remainder goes down to the next step.

The trace of the breakdown, step by step:

StepSumResultWhat’s left in remainder
Days90061 / 86400190061 % 86400 = 3661
Hours3661 / 360013661 % 3600 = 61
Minutes61 / 601—
Seconds61 % 601—

Design decisions:

  • SEC_DAY = 24 * 60 * 60 instead of writing 86400: Java does the sum (you can’t get it wrong) and the name explains what it is. There’s no risk of overflow here: 86400 fits easily in an int.
  • remainder is reused: first it keeps what’s left after the days and then what’s left after the hours. It saves variables, but don’t do it if it makes the code harder to read.
  • The percentage: (double) 90061 / 86400 = 1.04237… × 100 = 104.24 %.
  • The check line rebuilds the total: 1 × 86400 + 1 × 3600 + 1 × 60 + 1 = 90061. If it ever doesn’t match, the bug is in the breakdown.

Solution to exercise 3

/**
 * Challenge: inventory of three products at the faculty print shop (made-up prices, 7 % IGIC).
 * No arrays and no if: three sets of variables, Math.max/Math.min and the sums in cents.
 */
public class Shop {
    public static void main(String[] args) {
        final double IGIC = 0.07;                   // Canary Islands tax, general rate

        String name1 = "Laptop";    double price1 = 899.99;  int stock1 = 5;
        String name2 = "Mouse";     double price2 = 29.99;   int stock2 = 23;
        String name3 = "Keyboard";  double price3 = 79.99;   int stock3 = 12;

        // Inventory value with double: price * stock and add up
        double valueDouble = price1 * stock1 + price2 * stock2 + price3 * stock3;

        // The same in cents (long): exact whole numbers
        long cent1 = Math.round(price1 * 100);      // 89999
        long cent2 = Math.round(price2 * 100);      // 2999
        long cent3 = Math.round(price3 * 100);      // 7999
        long valueCents = cent1 * stock1 + cent2 * stock2 + cent3 * stock3;

        double mostExpensive = Math.max(price1, Math.max(price2, price3));
        double cheapest = Math.min(price1, Math.min(price2, price3));
        int units = stock1 + stock2 + stock3;

        System.out.println("PRINT SHOP INVENTORY");
        System.out.printf("%-10s %9s %9s %6s%n", "Product", "Base", "+IGIC", "Stock");
        System.out.printf("%-10s %9.2f %9.2f %6d%n", name1, price1, price1 * (1 + IGIC), stock1);
        System.out.printf("%-10s %9.2f %9.2f %6d%n", name2, price2, price2 * (1 + IGIC), stock2);
        System.out.printf("%-10s %9.2f %9.2f %6d%n", name3, price3, price3 * (1 + IGIC), stock3);
        System.out.println("Units: " + units);
        System.out.println("Value as double (println):  " + valueDouble);
        System.out.printf("Value as double (printf):   %.2f €%n", valueDouble);
        System.out.println("Value in cents: " + valueCents + " → " + valueCents / 100 + "." + valueCents % 100 + " €");
        System.out.println("Most expensive: " + mostExpensive + " € | cheapest: " + cheapest + " €");
        System.out.printf("Average price per unit: %.2f €%n", (double) valueCents / units / 100);
    }
}

What to look at in the output:

  • The println of the double says 6149.599999999999. The sums are 899.99 × 5 = 4499.95; 29.99 × 23 = 689.77; 79.99 × 12 = 959.88; total, 6149.60. But 899.99 can’t be stored exactly in binary (just as 1/3 can’t be written exactly in decimal), and the small errors add up. printf with %.2f rounds and hides it, but the stored number is the other one.
  • In cents, the result is exact: 89999 × 5 + 2999 × 23 + 7999 × 12 = 449995 + 68977 + 95988 = 614960. To write it in euros, valueCents / 100 gives 6149 (integer division) and valueCents % 100 gives 60.
  • Math.max(price1, Math.max(price2, price3)): the inner one picks the larger of 2 and 3; the outer one, the larger of that and 1. The same with Math.min. Without if, we can only know the price, not the product’s name; with if (next topic) you can keep the name too.
  • The average price per unit: (double) valueCents / units / 100 = 614960 / 40 / 100 = 153.74 €. The cast goes in front so that the first division already has decimals.
  • Prices with IGIC: 899.99 × 1.07 = 962.99; 29.99 × 1.07 = 32.09; 79.99 × 1.07 = 85.59 (rounded by printf).

Writing the euros as valueCents / 100 + "." + valueCents % 100 works here because the remainder is 60. If it were 5, you’d get 6149.5 instead of 6149.05. With printf("%d.%02d", valueCents / 100, valueCents % 100) the leading zero is guaranteed.

Exam-style quiz

Now test yourself as in the exam. The FP2 Java quiz works like this: 15 questions in 15 minutes about the whole Java syllabus, with single answer, multiple answer (wrong answers subtract), true or false, fill in the gaps, matching, short answer and «what does this code print?». Here are 11 original questions about variables and types in Java. Answer without running anything, press Check and read the explanation for each one. These questions are also the bank of the FP2 theory mock exam, which will mix them with those from the other Java topics.

Question 1Multiple · wrong ticks subtract

Which of these primitive types store whole numbers (no decimals)?

  1. byte
  2. short
  3. int
  4. long
  5. double
  6. float
  7. boolean
Solution

Answer: byte · short · int · long

The four integer types differ in size: byte (8 bits), short (16), int (32, the usual one) and long (64, for very large numbers, with L at the end of the literal). float and double are for decimals.

Question 2Short answer

What is the wrapper class of the primitive type char called?

Solution

Answer: Character

Each primitive has its class: int → Integer, char → Character, double → Double, boolean → Boolean… You need them, for example, to put numbers in a collection (List<Integer>), and they bring useful methods such as Character.isDigit(c).

Question 3Short answer

What does this fragment print?

int a = 7, b = 2;
double c = a / b;
double d = (double) a / b;
System.out.println(c + " " + d);
Solution

Answer: 3.0 3.5

a / b are two ints, so it’s integer division: 7 / 2 = 3, and only afterwards is it stored in the double as 3.0. In d, the cast turns a into 7.0 before dividing, and 7.0 / 2 = 3.5. The classic mistake is thinking the target variable being double is enough.

Question 4Short answer

What does this fragment print? (Write the output on one line.)

System.out.print('A' + 1);
System.out.print(" ");
System.out.println("" + 'A' + 1);
Solution

Answer: 66 A1

A char is a number underneath (the code of 'A' is 65). 'A' + 1 is an addition of numbers: 66. On the second line, "" + 'A' is already the text "A" and the 1 is glued on: A1. Starting with "" is a trick to force concatenation.

Question 5Single choice

Which of these declarations does NOT compile?

  1. int n = 3.5;
  2. double x = 3;
  3. long g = 5000000000L;
  4. float f = 2.5f;
Solution

Answer: int n = 3.5;

Java only converts automatically towards «bigger» types (from int to long or double). Towards a smaller one it requires you to ask for it with a cast, to make clear you accept losing the decimals or overflowing.

Question 6True or false

True or false?

  1. A variable declared int can’t store a String later.
  2. A variable final int MAX = 10; can’t be assigned again.
  3. With var n = 5; the compiler infers that n is an int.
  4. An uninitialised local variable is 0 and can be used without problems.
  5. A boolean can be used as a number (0 or 1), as in C.
Solution
  • T — A variable declared int can’t store a String later.
  • T — A variable final int MAX = 10; can’t be assigned again.
  • T — With var n = 5; the compiler infers that n is an int.
  • F — An uninitialised local variable is 0 and can be used without problems.
  • F — A boolean can be used as a number (0 or 1), as in C.

Java has static typing: the type is decided when you declare and doesn’t change (var only saves you typing it; the type is still fixed). Local variables have no default value: using one without assigning it gives the error variable might not have been initialized. And boolean only accepts true and false.

Question 7Short answer

What does this fragment print?

int x = Integer.MAX_VALUE;
x = x + 1;
System.out.println(x == Integer.MIN_VALUE);
Solution

Answer: true

An int has 32 bits: its maximum is 2,147,483,647. Adding 1 raises no error: the value overflows and wraps round to the minimum (−2,147,483,648). That’s why long is used for very large numbers.

Question 8Fill in the gaps

Fill in how to read a value from the keyboard:

First you import java.util.____. Then you create the reader with new Scanner(____). An integer is read with ____() and a whole line of text with ____().

Words:

  • System.out
  • readLine
  • nextLine
  • System.in
  • nextInt
  • nextString
  • Scanner
Solution

First you import java.util.Scanner. Then you create the reader with new Scanner(System.in). An integer is read with nextInt() and a whole line of text with nextLine().

System.in is standard input (the keyboard) and System.out, the output. Scanner has a nextX() per type (nextInt, nextDouble, nextBoolean…) and nextLine() for the whole line. readLine and nextString don’t exist in Scanner.

Question 9Match

Match each literal with its type:

42
42L
4.2
4.2f
'4'
"42"

Definitions:

  • int
  • char
  • String
  • boolean
  • double
  • float
  • long
Solution
  • 42 → int
  • 42L → long
  • 4.2 → double
  • 4.2f → float
  • '4' → char
  • "42" → String

A whole number with nothing else is an int; with L, a long. A number with a point is a double unless it has f. Single quotes enclose one character (char) and double quotes, a text (String).

Question 10Short answer

What does this fragment print?

String s = "12";
int n = Integer.parseInt(s) + 3;
System.out.println(s + 3 + " " + n);
Solution

Answer: 123 15

Integer.parseInt("12") turns the text into the number 12, so n is 15. But s + 3 is text plus a number: it concatenates and gives 123. It’s the equivalent of Python’s int(s).

Question 11Single choice

What does this line print?

System.out.println((int) 9.99 + (int) -2.7);
  1. 7
  2. 8
  3. 7.29
  4. 10
Solution

Answer: 7

Casting to int truncates: it drops the decimals and moves towards zero, for both positive and negative numbers. To round, Math.round(9.99) gives 10.

Watch it step by step in Python Tutor

pythontutor.com runs the code line by line and shows the value of every variable. Choose Java and paste this version of exercise 2 with fragment 1 inside:

public class TimeTutor {
    public static void main(String[] args) {
        int total = 90061;
        int days = total / 86400;
        int remainder = total % 86400;
        int hours = remainder / 3600;
        remainder = remainder % 3600;
        int minutes = remainder / 60;
        int seconds = remainder % 60;
        double wrong = total / 86400 * 100;
        double right = (double) total / 86400 * 100;
        System.out.println(days + " " + hours + " " + minutes + " " + seconds);
        System.out.println(wrong + " " + right);
    }
}

Output:

1 1 1 1
100.0 104.2372685185185

What to watch:

  1. See how remainder changes value: first 3661 and, after remainder = remainder % 3600;, 61. It’s the trace table of the solution.
  2. In double wrong = total / 86400 * 100; the value shows as 100.0: integer division had already left a 1.
  3. In double right = … you get 104.2372685185185, with all its decimals. printf would round it to 104.24.
  4. Notice that days, hours, minutes and seconds are int the whole time: the breakdown doesn’t need a single decimal.

Cheat sheet: Java variables

The essentials in 30 seconds: every variable has a fixed type (int, double, char, boolean, String…). Java calculates with the type of the operands, so int / int truncates and an int that goes past 2,147,483,647 wraps round. Going up a type is automatic; going down needs a cast and truncates. Texts are compared with equals().

I want…CodeResult
An average with decimals(7 + 8) / 2.07.5
The same with a cast(double) (7 + 8) / 27.5
The classic mistake(double) ((7 + 8) / 2)7.0
A whole-number percentage25 / 100.0 * 37293.0
Quotient and remainder30630 / 4 and 30630 % 47657 and 2
Drop decimals(int) 9.999
RoundMath.round(9.99)10 (long)
Round to 1 decimalMath.round(7.14 * 10) / 10.07.1
A big number365L * 24 * 60 * 60 * 100031536000000
Next letter(char) ('L' + 1)'M'
Text digit to number'7' - '0'7
Text to numberInteger.parseInt("24")24
Compare textsa.equals(b)true if they are equal

Templates from the exercises:

// Breakdown with / and %
int days = total / 86400;   int remainder = total % 86400;
int hours = remainder / 3600;   remainder = remainder % 3600;
int minutes = remainder / 60;   int seconds = remainder % 60;

// Money in cents
long cent = Math.round(price * 100);
long value = cent * units;
System.out.printf("%d.%02d €%n", value / 100, value % 100);

// Classify without if: one boolean per category
boolean normalWeight = bmi >= 18.5 && bmi < 25;
SymptomLikely cause
The average comes out with .0 and without the halfDivision between two int values
A percentage or a discount comes out 0n / 100 with a whole-number n
A huge number comes out negativeint overflow: long and L
integer number too largelong literal without L
possible lossy conversion from double to intA missing cast (or the variable has the wrong type)
Two identical texts give falseCompared with == instead of equals()
NumberFormatException with a price like 15,50parseDouble with a decimal comma
6149.599999999999 instead of 6149.60double isn’t exact: cents or printf

Before handing in, ask yourself:

  1. Is there a division where both sides are integers? Do I want decimals there?
  2. Can any sum go past 2 billion?
  3. Have I used (int) where I wanted to round?
  4. Have I compared any String with ==?

If you want to keep going, the next topic is Java flow control: with if and switch, the BMI will be able to say «Normal weight» directly and the print shop, which product is the most expensive.

Keep goingAll Java articles

Did this article help you? Here is how you can support SergioLearns →