عرض عملي: محوّل وحدات وتجاوز سعة العدد الصحيح
في هذا الدرس 11 موضوعات
هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.
Lesson Overview
This is a guided demo. You will build one small program step by step, following along on your own machine, and use every idea from the module: choosing types, naming, constants, conversion, var, a planned integer-division mistake, and an integer overflow that you will cause on purpose and then explain.
The program is a unit converter with fixed values: a temperature from Celsius to Fahrenheit, a distance from kilometres to miles, and a price in whole cents. The numbers are hard-coded; reading input from a user comes in a later module. At the end of the demo you will have everything needed for the module's lab, which has the same shape with different values.
Why this matters: converters look trivial, but they are exactly where type bugs live: integer division, wrong precision, money in a double, a value too big for its box. Seeing each bug appear once, on purpose, makes it recognisable the next time, when it appears by accident in a larger program.
This lesson builds on Lessons 1 to 6 and on the compile-and-run steps from BEG-006 to BEG-008. Use a terminal (javac and java) or IntelliJ IDEA; menu names differ between versions and operating systems, so look for the equivalent action. The outputs below come from JDK 21.
Learning Objectives
By the end of this lesson you will be able to:
- Choose a suitable type for a temperature, a distance, and a price, and justify each choice.
- Use
finalconstants for conversion factors instead of magic numbers. - Reproduce an integer-division bug and fix it by making one operand a
double. - Cause an integer overflow on purpose, predict the wrapped value, and explain it in a comment.
- Use
varfor obvious declarations and explicit types where they help the reader. - Keep a short test log: expected value, actual value, and fix.
Written Explanation
The plan
You will work in five steps, running the program after each one:
- Celsius to Fahrenheit (
double). - Kilometres to miles (
double, with a constant). - A price as whole cents (
int). - The planned mistake:
5 / 2. - The deliberate overflow.
Create a file Converter.java with a class called Converter and a main method, as in BEG-008. After each step, compile with javac Converter.java, run with java Converter, and compare with the output shown. Your output should match; if the wording of a compiler message differs slightly, that is normal.
Before you start, say your prediction aloud for each step. Predicting first is the habit that makes you fast at debugging.
Step 1: Celsius to Fahrenheit
The formula is F = C * 9 / 5 + 32. A temperature has a fractional part, so use double (Lesson 2).
double celsius = 25.0;
double fahrenheit = celsius * 9 / 5 + 32;
System.out.println(celsius + " C = " + fahrenheit + " F");Expected output: 25.0 C = 77.0 F. Check by hand: 25 times 9 is 225, divided by 5 is 45, plus 32 is 77. The order matters: because celsius is a double, celsius * 9 is a decimal multiplication, and / 5 is decimal division too.
A trap to try deliberately: write 9 / 5 * celsius + 32 instead. Both 9 and 5 are int, so 9 / 5 is integer division, 1, and the result is 57.0, not 77.0. The expression is evaluated left to right, so the integer division happens before the double ever joins in. A conversion factor with a fraction is safer as 9.0 / 5.0.
Step 2: Kilometres to miles, with a constant
One kilometre is about 0.621371 miles. That factor is a magic number unless we name it, and it never changes while the program runs, so it is a final constant (Lesson 4).
final double MILES_PER_KM = 0.621371;
double km = 10.0;
double miles = km * MILES_PER_KM;
System.out.println(km + " km = " + miles + " miles");Expected output: 10.0 km = 6.21371 miles. The constant is in UPPER_SNAKE_CASE, the variables in camelCase. If you wrote MILES_PER_KM = 0.6; later, the compiler would stop you.
Step 3: money in whole cents
A price of 19.99 must not be a double (Lesson 2). Store it as 1999 cents in an int, and let the code name the unit:
int priceCents = 1999;
int quantity = 3;
int totalCents = priceCents * quantity;
System.out.println("Total: " + totalCents / 100 + "." + totalCents % 100);Expected output: Total: 59.97. Here integer division is exactly what we want: 5997 / 100 is 59 whole units and 5997 % 100 is 97 cents. (This prints incorrectly for totals whose cents are below 10, such as 5905, which would show 59.5. Formatting numbers properly is a later topic. For now, choose test values with two-digit cents.)
Step 4: the planned mistake
Suppose you want half of 5:
int half = 5 / 2;
System.out.println("half = " + half);Prediction: you probably expect 2.5. The output is half = 2. Both operands are int, so the division is an integer division and the fraction is dropped (Lesson 5). Note that the compiler did not complain: this is a wrong answer, not a syntax error. Fix it by making one operand a double and the variable a double:
double halfFixed = 5 / 2.0;
System.out.println("halfFixed = " + halfFixed);Output: halfFixed = 2.5. Both steps were needed: changing only the variable (double half = 5 / 2;) would still give 2.0, because the integer division happens first.
Step 5: the deliberate overflow
Now cause the big surprise on purpose:
int biggest = Integer.MAX_VALUE;
int wrapped = biggest + 1;
System.out.println("biggest = " + biggest);
System.out.println("wrapped = " + wrapped);Expected output:
biggest = 2147483647
wrapped = -2147483648Why: an int has 32 bits. Integer.MAX_VALUE is the largest value they can hold, so adding 1 does not fit. Java does not warn. It keeps only the low 32 bits of the true result, which is the smallest int. The odometer rolled past its maximum and landed on the other end. Write that into a comment next to the lines, because the lab requires an explanation:
// Overflow: an int cannot go past 2147483647, so adding 1
// wraps around to the smallest int, -2147483648.Putting it together
Here is the finished program, using final constants and var where the right side is a plain literal (Lesson 6), and an explicit type where it helps the reader:
public class Converter {
public static void main(String[] args) {
// Temperature: Celsius to Fahrenheit
final double CELSIUS_TO_F_SCALE = 9.0 / 5.0;
var celsius = 25.0;
var fahrenheit = celsius * CELSIUS_TO_F_SCALE + 32;
System.out.println(celsius + " C = " + fahrenheit + " F");
// Distance: kilometres to miles
final double MILES_PER_KM = 0.621371;
var km = 10.0;
var miles = km * MILES_PER_KM;
System.out.println(km + " km = " + miles + " miles");
// Money: whole cents, never double
var priceCents = 1999;
var quantity = 3;
var totalCents = priceCents * quantity;
System.out.println("Total: " + totalCents / 100 + "." + totalCents % 100);
// Planned mistake and its fix
int half = 5 / 2; // 2: both operands are int
double halfFixed = 5 / 2.0; // 2.5: one operand is double
System.out.println(half + " vs " + halfFixed);
// Deliberate overflow: an int cannot go past 2147483647, so
// adding 1 wraps around to the smallest int, -2147483648.
int biggest = Integer.MAX_VALUE;
int wrapped = biggest + 1;
System.out.println(biggest + " + 1 = " + wrapped);
}
}Output:
25.0 C = 77.0 F
10.0 km = 6.21371 miles
Total: 59.97
2 vs 2.5
2147483647 + 1 = -2147483648Examples
Example 1: a trap in the order of operations (simple)
Run these two lines with celsius equal to 25.0:
System.out.println(9 / 5 * celsius + 32);
System.out.println(celsius * 9 / 5 + 32);Output:
57.0
77.0The two expressions look algebraically the same, but the first does 9 / 5 as an integer division first. The lesson: when a fraction must stay a fraction, write it as a decimal (9.0 / 5.0) or start the expression with a double operand.
Example 2: a price converted with whole numbers (practical)
A shop converts 49.99 in one currency into another at a rate of 1.08, using only integers. Keep the price in cents and the rate in hundredths:
int priceCents = 4999;
int rateHundredths = 108;
long converted = priceCents * rateHundredths / 100L;
System.out.println(converted);Output: 5398, that is 53.98. The calculation truncates to a whole cent, which is a business decision, not a language one: a real shop must decide how to round. The 100L makes the division a long operation, which protects larger amounts. (A decimal calculation priceCents * 1.08 / 100 gives 53.989200000000004, the kind of tail that makes double unattractive for money.)
Example 3: reading your own overflow (realistic)
Imagine a counter that adds up bytes transferred. After a lot of traffic it holds 2147483647 and then one more byte arrives. With an int the total silently becomes -2147483648, and a report shows a negative amount of data. The cause is the same as Step 5, only in a larger system. The fix is to choose the type from the largest realistic value, not from habit: a running total of bytes should be a long from the start. A short comment (as the lab asks) turns a mystery into documentation: "int wraps past 2147483647; use long for totals".
Backend Insight
Every backend that handles data does conversions: units in a payload, currencies in an order, timestamps in a database. The demo shows the patterns that matter at larger scale:
- Money stays in an exact type. Whether it is cents in a
longor aBigDecimal(introduced later), the amount is never adouble. A rounding policy (round half up, truncate) is a requirement you write down, not a default you inherit. - Constants for conversion factors.
MILES_PER_KMcan be reviewed and tested; a number scattered in ten places cannot. - Overflow checks at boundaries. Where numbers come from outside, such as sizes and counts in requests, a service validates ranges and uses a type that is big enough. Production incidents often start with a number that grew past a type the designer thought was "big enough".
- Tests with known answers. The demo checks each step against an answer worked out by hand. Backend tests do the same with a unit converter's expected outputs; you will meet testing in a later module.
Common Mistakes
Mistake 1: Dividing integers and expecting decimals
5 / 2 is 2. Make one operand a double before the division (5 / 2.0), and store the result in a double.
Mistake 2: Putting the fraction in an integer expression
9 / 5 * celsius loses the .8. Use 9.0 / 5.0, or put the double first.
Mistake 3: Using double for the price
double price = 19.99; can give results with tiny rounding tails after arithmetic. Keep whole cents.
Mistake 4: Forgetting that overflow is silent
Integer.MAX_VALUE + 1 compiles and runs without any error. If you only look at "no errors", you will miss it. Always compare the output to your prediction.
Mistake 5: Explaining the overflow with "a bug in Java"
Overflow is defined behavior in the language: the result wraps around. The "mistake" belongs to the program that chose a box too small for the value. Explain it in a comment in terms of the type's range.
Exercises
Try each exercise yourself before opening the solutions. Compile and run them in your converter if you can.
Level A: Foundation
A1. Which type would you use for distanceKm, priceCents, isOnSale, and unitLabel (for example "km")?
أظهر الحل A1
A1. double distanceKm, int priceCents (or long for large amounts), boolean isOnSale, String unitLabel.
A2. Predict the value of 22 / 7, 22 / 7.0, and 22 % 7.
أظهر الحل A2
A2. 3, about 3.142857142857143, and 1.
A3. Why is MILES_PER_KM written in capital letters with underscores, and why is it final?
أظهر الحل A3
A3. It is a constant by convention (UPPER_SNAKE_CASE) and final so that the compiler stops anyone from changing a value that must stay fixed; the name replaces a magic number with a meaning.
Level B: Application
B1. Write the lines that convert 72 Fahrenheit to Celsius with the formula C = (F - 32) * 5 / 9, using double variables, and give the result to two decimal places by reasoning (you may check by running).
أظهر الحل B1
B1.
double fahrenheit = 72.0;
double celsius = (fahrenheit - 32) * 5 / 9;
System.out.println(celsius);(72 - 32) is 40, times 5 is 200, divided by 9 is about 22.22. Because fahrenheit is a double, all of it is decimal arithmetic.
B2. A recipe needs 750 grams of flour per cake. Write one line that computes the grams for 1.5 cakes, using a double, and say what it prints. Then explain what goes wrong with int grams = 750 * (3 / 2);.
أظهر الحل B2
B2. double grams = 750 * 1.5; prints 1125.0. In 750 * (3 / 2) the parentheses force 3 / 2 to be an integer division first, giving 1, so the result is 750 instead of 1125.
B3. Predict, then say what to change so that no overflow happens: int a = 50_000; int b = a * a;.
أظهر الحل B3
B3. 50_000 * 50_000 is 2,500,000,000, which is greater than Integer.MAX_VALUE (2,147,483,647), so it wraps to a negative number, -1794967296. Fix: long b = (long) a * a;, which gives 2500000000.
Level C: Challenge
C1. Extend the demo: convert 3,000,000 metres to millimetres (multiply by 1000). Show what an int version prints, and give the correct version. Then say what happens for 1,000,000 metres.
أظهر الحل C1
C1. With 1,000,000 metres the answer, 1,000,000,000 millimetres, still fits in an int, so there is no overflow. With 3,000,000 metres the answer is 3,000,000,000, which does not fit: int mm = 3_000_000 * 1000; wraps to -1294967296. The correct version is long mm = 3_000_000L * 1000;, which gives 3000000000.
C2. A shop total is totalCents = 100_000_000 * 25; (a hundred million cents times 25). Predict whether it overflows an int, state the correct value, and write the corrected line.
أظهر الحل C2
C2. 100_000_000 * 25 is 2,500,000,000, which exceeds the int maximum, so it overflows and wraps to -1794967296. The correct value is 2500000000. Corrected line: long totalCents = 100_000_000L * 25;.
Mini Challenge
Write your own mini-converter, MyConverter, with three conversions of your choice using fixed values: for example pounds to kilograms, hours to minutes, and a price with quantity in cents. Use at least one final constant, at least one var, one deliberate integer-division mistake together with its fix, and one deliberate overflow with a comment that says why the result is what it is. Print every result with a label.
Check your work: it compiles; each printed value matches a hand calculation; money is in whole cents; constants are in UPPER_SNAKE_CASE; and both the division bug and the overflow are explained in comments.
Summary & Next Lesson
Key takeaways
- Choose types from the data:
doublefor measurements, whole cents in an integer for money,longwhen totals can grow large. - Name conversion factors with
finalconstants. - Integer division silently drops fractions: write
5 / 2.0or9.0 / 5.0, and watch the order of operations. - Overflow wraps silently:
Integer.MAX_VALUE + 1is-2147483648; explain it in a comment and choose a bigger type when needed. - Run, predict, compare: a bug you predicted is a bug you understand.
Next lesson
The next step is the module's lab, BEG-009-LAB1, "Unit converter (fixed values)": the same kind of program with your own values and a written explanation of the overflow.
Self-check
- Why does
9 / 5 * celsiusgive a wrong temperature? - Why are prices kept as whole cents?
- What is
Integer.MAX_VALUE + 1? - Which change fixes
int half = 5 / 2;if you want2.5? - Why is a conversion factor declared
final?
أظهر الإجابات
9 / 5is an integer division that gives1before the decimal value joins in, so the fraction is lost.- A
doublecannot store most decimal fractions exactly; whole cents in an integer type are exact. -2147483648: the value wraps around.- Make one operand a
doubleand the variable adouble:double half = 5 / 2.0;. - So that the value cannot be changed by mistake and the name explains the number.
References
- Oracle, The Java Language Specification, Java SE 21, section 4.2.2: Integer Operations. It says integer operators never signal overflow and results wrap around, keeping the low-order bits.
- Oracle, The Java Language Specification, Java SE 21, Chapter 5: Conversions and Contexts. It describes widening and narrowing conversions used in this demo.
- Oracle, The Java Tutorials: Primitive Data Types. It advises against
doublefor currency and recommendsBigDecimal. - The finished program and all quoted outputs were compiled and run with JDK 21. The conversion factor 0.621371 miles per kilometre is a commonly used approximation, not checked against a standards body here. The rounding example (truncating to a whole cent) is a teaching choice, not a rule from any source.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.