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.
Table of Contents
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):
| Product | Base price | Units |
|---|---|---|
| Laptop | 899.99 € | 5 |
| Mouse | 29.99 € | 23 |
| Keyboard | 79.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;: the70is anint, but it fits in adoubleand Java converts it on its own (widening). That’s why the output says70.0.- The constants
BMI_MINandBMI_MAXavoid 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< 25and 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< 25there 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");
}
}
The trace of the breakdown, step by step:
| Step | Sum | Result | What’s left in remainder |
|---|---|---|---|
| Days | 90061 / 86400 | 1 | 90061 % 86400 = 3661 |
| Hours | 3661 / 3600 | 1 | 3661 % 3600 = 61 |
| Minutes | 61 / 60 | 1 | — |
| Seconds | 61 % 60 | 1 | — |
Design decisions:
SEC_DAY = 24 * 60 * 60instead 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 anint.remainderis 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
printlnof thedoublesays6149.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.printfwith%.2frounds 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 / 100gives 6149 (integer division) andvalueCents % 100gives 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 withMath.min. Withoutif, we can only know the price, not the product’s name; withif(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.
Which of these primitive types store whole numbers (no decimals)?
byteshortintlongdoublefloatboolean
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.
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).
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.
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.
Which of these declarations does NOT compile?
int n = 3.5;double x = 3;long g = 5000000000L;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.
True or false?
- A variable declared
intcan’t store aStringlater. - A variable
final int MAX = 10;can’t be assigned again. - With
var n = 5;the compiler infers thatnis anint. - An uninitialised local variable is 0 and can be used without problems.
- A
booleancan be used as a number (0 or 1), as in C.
Solution
- T — A variable declared
intcan’t store aStringlater. - T — A variable
final int MAX = 10;can’t be assigned again. - T — With
var n = 5;the compiler infers thatnis anint. - F — An uninitialised local variable is 0 and can be used without problems.
- F — A
booleancan 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.
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.
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.
Match each literal with its type:
42 | |
42L | |
4.2 | |
4.2f | |
'4' | |
"42" |
Definitions:
- int
- char
- String
- boolean
- double
- float
- long
Solution
42→ int42L→ long4.2→ double4.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).
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).
What does this line print?
System.out.println((int) 9.99 + (int) -2.7);
787.2910
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:
- See how
remainderchanges value: first 3661 and, afterremainder = remainder % 3600;, 61. It’s the trace table of the solution. - In
double wrong = total / 86400 * 100;the value shows as100.0: integer division had already left a 1. - In
double right = …you get104.2372685185185, with all its decimals.printfwould round it to 104.24. - Notice that
days,hours,minutesandsecondsareintthe 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… | Code | Result |
|---|---|---|
| An average with decimals | (7 + 8) / 2.0 | 7.5 |
| The same with a cast | (double) (7 + 8) / 2 | 7.5 |
| The classic mistake | (double) ((7 + 8) / 2) | 7.0 |
| A whole-number percentage | 25 / 100.0 * 372 | 93.0 |
| Quotient and remainder | 30630 / 4 and 30630 % 4 | 7657 and 2 |
| Drop decimals | (int) 9.99 | 9 |
| Round | Math.round(9.99) | 10 (long) |
| Round to 1 decimal | Math.round(7.14 * 10) / 10.0 | 7.1 |
| A big number | 365L * 24 * 60 * 60 * 1000 | 31536000000 |
| Next letter | (char) ('L' + 1) | 'M' |
| Text digit to number | '7' - '0' | 7 |
| Text to number | Integer.parseInt("24") | 24 |
| Compare texts | a.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;
| Symptom | Likely cause |
|---|---|
The average comes out with .0 and without the half | Division between two int values |
| A percentage or a discount comes out 0 | n / 100 with a whole-number n |
| A huge number comes out negative | int overflow: long and L |
integer number too large | long literal without L |
possible lossy conversion from double to int | A missing cast (or the variable has the wrong type) |
Two identical texts give false | Compared with == instead of equals() |
NumberFormatException with a price like 15,50 | parseDouble with a decimal comma |
6149.599999999999 instead of 6149.60 | double isn’t exact: cents or printf |
Before handing in, ask yourself:
- Is there a division where both sides are integers? Do I want decimals there?
- Can any sum go past 2 billion?
- Have I used
(int)where I wanted to round? - Have I compared any
Stringwith==?
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.
Did this article help you? Here is how you can support SergioLearns →




IC2 practical mock exam — flowcharts, fork and threads in C (and the screenshots you can’t leave out)