01 · Java basics — note 5 of 12
Casting and type promotion
Convert numeric types safely, navigate expression promotions, and stop silent overflow.
Java widens numbers to a bigger type on its own, but going to a smaller type needs a cast, and a cast keeps only the bits that fit.
The idea in real life
Think of pouring liquids between measuring cups in a kitchen. Pouring water from a one-cup cup into a four-cup jug is completely safe and never spills a single drop. Pouring water from a four-cup jug into a one-cup cup risks spilling liquid over the counter unless you know the jug holds only a small amount and you pour with deliberate care. In Java, widening conversions are the safe transfer into a larger container, while narrowing casts are the risky pour into a smaller container where excess data is discarded.
Why it exists
- Different calculations require different container sizes, from compact 8-bit bytes in network buffers to 64-bit integers for huge counts.
- The compiler prevents accidental data loss by refusing to squeeze large types into smaller variables automatically.
- The JVM only does arithmetic on
int,long,floatanddouble, so smaller types likebyteandshortare promoted tointfirst. - Knowing these rules ensures you widen operands before operations overflow and catch subtle bit truncations before they produce wrong answers.
How it works
Widening conversions
Java widens smaller numeric types to larger ones automatically without casting.
A widening primitive conversion changes a value of a smaller primitive type into a wider primitive type. Java performs widening conversions automatically whenever you assign a narrower value to a wider variable, pass a narrower argument to a wider method parameter, or mix types in an expression. You never need an explicit cast operator for widening.
As introduced in variables and primitive types, Java provides six numeric types along with the char type. The Java Language Specification (JLS §5.1.2) defines 19 specific widening primitive conversions:
| Source type | Widens automatically to | Conversion behavior |
|---|---|---|
byte | short, int, long, float, double | Sign-extends 8 bits into 16, 32, or 64 bits |
short | int, long, float, double | Sign-extends 16 bits into 32 or 64 bits |
char | int, long, float, double | Zero-extends unsigned 16 bits into 32 or 64 bits |
int | long, float, double | Exact to long; preserves overall magnitude to floating point |
long | float, double | Preserves overall magnitude; may round least significant digits |
float | double | Preserves exact 32-bit float value in 64-bit double precision |
Two boundaries in this table are worth special attention:
bytedoes not widen tochar. Abyteis signed (values-128to127), while acharis unsigned (values0to65,535). Converting abytetocharrequires both sign-extending tointand then truncating to 16 bits (JLS §5.1.4).shortdoes not widen tochar, andchardoes not widen toshort. Both occupy 16 bits, butshortuses two's complement signed representation whilecharrepresents unsigned Unicode code points.
The following program demonstrates widening conversions across integer and floating-point types. In this program:
smallNumberis a 8-bitbyteholding 42.widerNumberwidenssmallNumberto a 16-bitshort.standardNumberwidenswiderNumberto a 32-bitint.largeNumberwidensstandardNumberto a 64-bitlong.decimalNumberwidensstandardNumberto a 64-bitdouble.
public class Main {
public static void main(String[] args) {
byte smallNumber = 42;
short widerNumber = smallNumber;
int standardNumber = widerNumber;
long largeNumber = standardNumber;
double decimalNumber = standardNumber;
System.out.println("byte widened to int: " + standardNumber);
System.out.println("int widened to long: " + largeNumber);
System.out.println("int widened to double: " + decimalNumber);
}
}byte widened to int: 42
int widened to long: 42
int widened to double: 42.0Narrowing with a cast
Narrowing requires an explicit cast and discards high-order bits.
A narrowing primitive conversion converts a value of a wider type into a narrower type (JLS §5.1.3), such as converting long to int or int to byte. Because narrowing can discard information or alter the sign of a number, the Java compiler refuses to perform narrowing automatically.
To narrow a type, you must place the target type in parentheses immediately before the expression: (targetType) expression. This syntax is an explicit cast. It tells the compiler that you are aware of the conversion and accept any truncation or overflow that occurs.
When the original value fits within the range of the target type, the cast preserves the exact value. When the original value lies outside that range, Java does not report an error or throw an exception at runtime. Instead, it discards the higher-order bits and keeps only the lowest bits that fit the target type.
For example, when casting a 32-bit int with value 300 to an 8-bit byte:
- The integer 300 in binary is
00000000 00000000 00000001 00101100. - Casting to
bytediscards the top 24 bits, leaving the low 8 bits:00101100. - In decimal,
00101100equals32 + 8 + 4 = 44.
Java makes one helpful exception for constant expressions (JLS §5.2). If you assign a compile-time constant integer literal to a byte, short, or char variable, you do not need an explicit cast as long as the constant value fits within the target type's valid range:
byte validConstant = 100; // Compiles: 100 fits in byte (-128 to 127)However, if the expression uses a variable rather than a constant literal, the compiler requires an explicit cast even if the runtime value would fit:
Main.java:5: error: incompatible types: possible lossy conversion from int to byte
byte target = variable;
^The following program demonstrates narrowing casts that preserve values as well as casts that truncate bits. In this program:
normalCountholds 1,000,000 in along, which fits cleanly inside anint.narrowedCountstores the result of castingnormalCounttoint.smallValueholds 100 in anint, which fits inside abyte.narrowedSmallstores the result of castingsmallValuetobyte.overflowValueholds 300 in anint, which exceeds a byte's maximum range of 127.narrowedOverflowstores the truncated result of castingoverflowValuetobyte.
public class Main {
public static void main(String[] args) {
long normalCount = 1_000_000L;
int narrowedCount = (int) normalCount;
int smallValue = 100;
byte narrowedSmall = (byte) smallValue;
int overflowValue = 300;
byte narrowedOverflow = (byte) overflowValue;
System.out.println("long narrowed to int: " + narrowedCount);
System.out.println("int 100 narrowed to byte: " + narrowedSmall);
System.out.println("int 300 narrowed to byte: " + narrowedOverflow);
}
}long narrowed to int: 1000000
int 100 narrowed to byte: 100
int 300 narrowed to byte: 44Numeric promotion in expressions
Binary arithmetic promotes byte, short, and char operands to 32-bit integers.
Java does not perform arithmetic directly on byte, short, or char values. Before evaluating arithmetic operators, Java applies numeric promotion rules (JLS §5.6).
Under binary numeric promotion, if neither operand is a double, float, or long, Java automatically promotes both operands to 32-bit int before executing the operator. This means adding two byte variables produces an int result, not a byte.
If you try to store the sum of two byte variables directly back into a byte, the compiler rejects the code:
Main.java:5: error: incompatible types: possible lossy conversion from int to byte
byte sum = first + second;
^To store the result back into a byte, you must explicitly cast the entire sum: byte sum = (byte) (first + second);. Notice the parentheses around (first + second). Without parentheses, (byte) first + second casts only first to byte, which is immediately promoted back to int when added to second.
Unary operators also trigger promotion. Under unary numeric promotion (JLS §5.6.1), applying unary plus (+), unary minus (-), or bitwise complement (~) to a byte, short, or char operand promotes the operand to an int. Thus, -first produces a 32-bit int.
The following program demonstrates binary and unary numeric promotion with byte operands. In this program:
firstandsecondarebytevariables holding 40 and 50.sumAsIntreceives the uncast sum, which has typeint.sumAsBytecasts the evaluated sum back intobyte.negatedreceives the result of applying unary minus tofirst.
public class Main {
public static void main(String[] args) {
byte first = 40;
byte second = 50;
int sumAsInt = first + second;
byte sumAsByte = (byte) (first + second);
int negated = -first;
System.out.println("byte + byte as int: " + sumAsInt);
System.out.println("byte + byte cast back to byte: " + sumAsByte);
System.out.println("negated byte as int: " + negated);
}
}byte + byte as int: 90
byte + byte cast back to byte: 90
negated byte as int: -40char and int conversions
char stores 16-bit Unicode codes and promotes to int during arithmetic.
As covered in variables and primitive types, a char stores a 16-bit unsigned integer representing a Unicode code point. The character 'a', for example, has the numeric code 97.
Because char values are numeric under the hood, you can use arithmetic operators on characters, as discussed in operators. When you evaluate an expression like 'a' + 1, binary numeric promotion widens both 'a' (code 97) and 1 to int. The addition evaluates to the integer 98.
If your goal is to find the next character in sequence ('b'), you must cast the integer result back to char: (char) ('a' + 1).
In algorithm problems, you will frequently map lowercase English letters to array indices from 0 to 25. Subtracting 'a' from any lowercase character triggers numeric promotion and calculates the character's zero-based alphabet position:
'a' - 'a'evaluates to97 - 97 = 0.'b' - 'a'evaluates to98 - 97 = 1.'z' - 'a'evaluates to122 - 97 = 25.
The following program demonstrates converting characters to integers, advancing a character with a cast, and computing an alphabet index. In this program:
baseCharacterholds'a'.nextCodestores'a' + 1as anint.nextCharactercasts the advanced code back to achar.sampleLetterholds'c'.alphabetIndexcomputessampleLetter - 'a'.
public class Main {
public static void main(String[] args) {
char baseCharacter = 'a';
int nextCode = baseCharacter + 1;
char nextCharacter = (char) (baseCharacter + 1);
char sampleLetter = 'c';
int alphabetIndex = sampleLetter - 'a';
System.out.println("'a' + 1 as int: " + nextCode);
System.out.println("(char) ('a' + 1): " + nextCharacter);
System.out.println("Alphabet index of 'c': " + alphabetIndex);
}
}'a' + 1 as int: 98
(char) ('a' + 1): b
Alphabet index of 'c': 2Going deeper
Mixed expressions
Java promotes operands step by step based on operator precedence and types.
When an expression combines different primitive types, Java does not convert every variable to the widest type all at once. Instead, binary numeric promotion (JLS §5.6.2) applies to each operator one at a time, moving strictly according to operator precedence and associativity:
- If either operand is of type
double, the other operand is converted todouble. - Otherwise, if either operand is of type
float, the other operand is converted tofloat. - Otherwise, if either operand is of type
long, the other operand is converted tolong. - Otherwise, both operands are converted to type
int.
Consider the mixed expression count * factor + rate where count is an int (5), factor is a long (2L), and rate is a double (1.5):
- Multiplication first: Multiplication (
*) has higher precedence than addition (+). The operands arecount(int) andfactor(long). Following rule 3, Java widenscounttolong(5L). The multiplication evaluates in 64-bit integer arithmetic to10L. - Addition second: The remaining expression is
10L + rate. The operands are10L(long) andrate(double). Following rule 1, Java widens10Ltodouble(10.0). The addition evaluates in 64-bit floating point to11.5.
Because promotion happens operator by operator, subexpressions evaluated earlier can lose precision before a wider type ever touches them. In (1 / 2) * 3.0, the subexpression 1 / 2 involves two integers. Integer division truncates the result to 0 before multiplication with 3.0 occurs. Writing (1.0 / 2) * 3.0 ensures the initial division uses floating-point arithmetic and produces 0.5, yielding 1.5.
The following program traces step-by-step promotion in mixed expressions. In this program:
countis anintset to 5.factoris alongset to 2L.rateis adoubleset to 1.5.combinedResultevaluatescount * factor + rate.truncatedDivisionevaluates(1 / 2) * 3.0.floatingDivisionevaluates(1.0 / 2) * 3.0.
public class Main {
public static void main(String[] args) {
int count = 5;
long factor = 2L;
double rate = 1.5;
double combinedResult = count * factor + rate;
double truncatedDivision = (1 / 2) * 3.0;
double floatingDivision = (1.0 / 2) * 3.0;
System.out.println("5 * 2L + 1.5: " + combinedResult);
System.out.println("(1 / 2) * 3.0: " + truncatedDivision);
System.out.println("(1.0 / 2) * 3.0: " + floatingDivision);
}
}5 * 2L + 1.5: 11.5
(1 / 2) * 3.0: 0.0
(1.0 / 2) * 3.0: 1.5Overflow before widening
Integer multiplication overflows at 32 bits before assignment widens to long.
A common source of errors in algorithm code is writing an assignment like long total = width * height; where both width and height are 32-bit int variables.
The assignment operator (=) evaluates its entire right-hand expression before storing the result in the variable on the left. In width * height, both operands are int. Following binary promotion rule 4, the multiplication executes entirely in 32-bit signed integer arithmetic.
If width is 1,000,000 and height is 3,000, their mathematical product is 3,000,000,000. Because 3,000,000,000 exceeds Integer.MAX_VALUE (2,147,483,647), the multiplication overflows in 32-bit two's complement and wraps to -1,294,967,296. Only after this overflow occurs does Java widen the corrupt negative value to long.
To fix this, cast at least one operand to long before the operator runs:
long total = (long) width * height;The cast operator (long) binds tighter than multiplication. Java converts width to long first. Then, by binary numeric promotion rule 3, Java widens height to long and performs the entire multiplication in 64-bit integer arithmetic, producing the correct product without overflow.
The following program demonstrates 32-bit integer overflow during multiplication and the fix using an explicit cast. In this program:
widthholds 1,000,000 andheightholds 3,000.overflowedAreacalculates the product without a cast, overflowing into a negativelong.correctAreacastswidthtolongbefore multiplying, preserving the full product.
public class Main {
public static void main(String[] args) {
int width = 1_000_000;
int height = 3_000;
long overflowedArea = width * height;
long correctArea = (long) width * height;
System.out.println("Overflowed product in long: " + overflowedArea);
System.out.println("Correct product in long: " + correctArea);
}
}Overflowed product in long: -1294967296
Correct product in long: 3000000000Compound assignment's hidden cast
Compound assignment operators quietly insert a cast back to the target type.
As explained in operators, Java offers compound assignment operators such as +=, -=, *=, and /=. Beyond updating a variable in place, compound assignment operators contain an important specification rule: they secretly insert an explicit cast.
According to JLS §15.26.2, a compound assignment expression of the form variable op= expression is exactly equivalent to:
variable = (TargetType) ((variable) op (expression))where TargetType is the declared type of variable, with variable evaluated only once.
This hidden cast explains why short score = 30000; score = score + 10000; fails to compile with an incompatible types error (the sum is an int), but score += 10000; compiles cleanly without complaint. Java transforms the statement into score = (short) (score + 10000);.
While convenient, this automatic cast can silently mask severe truncation bugs. If score is 30,000 and you add 10,000, the mathematical sum 40,000 exceeds Short.MAX_VALUE (32,767). The inserted (short) cast discards the upper bits and leaves -25,536 in score. The compiler gives no warning, and no runtime exception is thrown.
The following program demonstrates silent overflow caused by the hidden cast inside compound assignment operators. In this program:
scoreis a 16-bitshortinitialised to 30,000.score += 10_000overflowsShort.MAX_VALUEand wraps to-25,536.levelis an 8-bitbyteinitialised to 100.level *= 2overflowsByte.MAX_VALUE(127) and wraps to-56.
public class Main {
public static void main(String[] args) {
short score = 30_000;
score += 10_000;
byte level = 100;
level *= 2;
System.out.println("short 30000 += 10000: " + score);
System.out.println("byte 100 *= 2: " + level);
}
}short 30000 += 10000: -25536
byte 100 *= 2: -56Casting doubles to integers
Floating-point casts truncate toward zero, clamp infinities, and turn NaN into zero.
Converting a double or float to an integer type requires an explicit cast (JLS §5.1.3). As discussed in floating point, floating-point numbers store fractional values that integer types cannot hold.
Java applies three specific rules when casting a floating-point value to an integer:
- Truncation toward zero: The fractional digits are discarded. Positive values round downward (
(int) 3.8becomes3), while negative values round upward toward zero ((int) -3.8becomes-3). This differs fromMath.floor, which always rounds downward toward negative infinity (Math.floor(-3.8)gives-4.0). - Clamping at boundaries: If the floating-point value is larger than
Integer.MAX_VALUE(2,147,483,647) or equalsDouble.POSITIVE_INFINITY, the cast clamps the result toInteger.MAX_VALUE. If the value is smaller thanInteger.MIN_VALUE(-2,147,483,648) or equalsDouble.NEGATIVE_INFINITY, the cast clamps the result toInteger.MIN_VALUE. - NaN becomes zero: If the floating-point value is
Double.NaN("Not a Number"), the cast produces0.
The following program demonstrates truncation toward zero, extreme value clamping, and NaN conversion. In this program:
positiveDecimalholds 3.8 andnegativeDecimalholds -3.8.positiveTruncatedandnegativeTruncatedstore the results of casting them toint.clampedMaxcasts 1e12 (1 trillion), which clamps toInteger.MAX_VALUE.clampedMincasts -1e12, which clamps toInteger.MIN_VALUE.nanConvertedcastsDouble.NaN, which produces 0.
public class Main {
public static void main(String[] args) {
double positiveDecimal = 3.8;
double negativeDecimal = -3.8;
int positiveTruncated = (int) positiveDecimal;
int negativeTruncated = (int) negativeDecimal;
int clampedMax = (int) 1e12;
int clampedMin = (int) -1e12;
int nanConverted = (int) Double.NaN;
System.out.println("(int) 3.8: " + positiveTruncated);
System.out.println("(int) -3.8: " + negativeTruncated);
System.out.println("(int) 1e12: " + clampedMax);
System.out.println("(int) -1e12: " + clampedMin);
System.out.println("(int) Double.NaN: " + nanConverted);
}
}(int) 3.8: 3
(int) -3.8: -3
(int) 1e12: 2147483647
(int) -1e12: -2147483648
(int) Double.NaN: 0Under the hood
Precision loss in widening
Widening to floating point can lose low-order digits without any warning.
Although converting an int to float or a long to double is classified by Java as a widening conversion (JLS §5.1.2) and requires no cast, it can silently lose precision.
The magnitude of the number is never lost because the floating-point exponent accommodates numbers far larger than 64-bit integers. However, floating-point types have fewer bits dedicated to precision (the significand) than integer types have in total:
- An
inthas 32 bits of precision. Afloathas only 24 bits of significand (23 stored bits plus 1 implicit leading bit). - A
longhas 64 bits of precision. Adoublehas only 53 bits of significand (52 stored bits plus 1 implicit leading bit).
Because a float has 24 bits of significand, it can represent every integer up to $2^24$ (16,777,216) exactly. Beyond $2^24$, the gap between representable floats exceeds 1. The odd number $16,777,217$ ($2^24 + 1$) requires 25 significant binary bits. When widened to float, IEEE 754 round-to-nearest-even rounds the value down to $16,777,216.0f$. Casting back to int yields $16,777,216$, losing the last digit.
Similarly, a double can represent every integer up to $2^53$ ($9,007,199,254,740,992L$) exactly. The value $2^53 + 1$ ($9,007,199,254,740,993L$) requires 54 significant binary bits. Widening it to double rounds away the least significant bit, producing $9,007,199,254,740,992.0$.
The following program demonstrates precision loss when widening large integers to floating-point types. In this program:
originalIntholds $16,777,217$.floatValuewidensoriginalInttofloat, rounding away the last bit.roundedIntcastsfloatValueback toint.originalLongholds $9,007,199,254,740,993L$.doubleValuewidensoriginalLongtodouble, rounding away the last bit.roundedLongcastsdoubleValueback tolong.
public class Main {
public static void main(String[] args) {
int originalInt = 16_777_217;
float floatValue = originalInt;
int roundedInt = (int) floatValue;
long originalLong = 9_007_199_254_740_993L;
double doubleValue = originalLong;
long roundedLong = (long) doubleValue;
System.out.println("Original int: " + originalInt);
System.out.println("Converted to float and back: " + roundedInt);
System.out.println("Original long: " + originalLong);
System.out.println("Converted to double and back: " + roundedLong);
}
}Original int: 16777217
Converted to float and back: 16777216
Original long: 9007199254740993
Converted to double and back: 9007199254740992What a narrowing cast does to the bits
Narrowing keeps the lowest bits and interprets them in two's complement.
At the machine level, narrowing an integer value to a smaller type discards the high-order bits and retains the lowest $n$ bits, where $n$ is the bit width of the destination type (JLS §5.1.3). The processor does not check whether the truncated value is sensible. It takes the remaining $n$ bits and interprets them directly using signed two's complement representation.
Let us trace what occurs during (byte) 200:
- The number 200 stored in a 32-bit
inthas the binary pattern:00000000 00000000 00000000 11001000 - Casting to
byteretains only the lowest 8 bits:11001000 - In an 8-bit signed
byte, the highest bit (bit 7) is the sign bit. Because bit 7 is1, the value is negative. - Using two's complement decoding: the value is $-2^7 + 2^6 + 2^3 = -128 + 64 + 8 = -56$. Alternatively, subtracting the modulus: $200 - 256 = -56$.
A positive number cast to a smaller container can flip into a negative number whenever its bit at position $n - 1$ is 1. This bit-level truncation never halts program execution; it silently stores the transformed two's complement value into memory.
Bytecode for casts
The JVM uses explicit conversion opcodes on the operand stack.
The Java Virtual Machine (JVM) operand stack operates primarily on 32-bit integers, 64-bit longs, 32-bit floats, and 64-bit doubles. There are no separate bytecode instructions for adding or subtracting byte, short, or char values. Those types are held on the stack as 32-bit integers.
When a Java program performs widening or narrowing conversions between primitive types, the compiler emits dedicated conversion instructions into bytecode:
i2l: Widens a 32-bitintto a 64-bitlongby sign-extending the top 32 bits.l2i: Narrows a 64-bitlongto a 32-bitintby discarding the upper 32 bits on the operand stack.i2b: Narrows a 32-bitinton the stack to an 8-bitbytevalue, truncating the value to 8 bits and then sign-extending it back to a 32-bitinton the stack.d2i: Converts a 64-bitdoubleto a 32-bitint, executing the IEEE 754 truncation, clamping, andNaNchecks directly.
Here is part of the javap -c output for a method with these conversions. The // comments are added here to explain each line; javap does not print them:
3: iload_1 // Load int variable 'count' onto stack
4: i2l // Widen int to long on stack
5: lstore_2 // Store into long variable 'widerCount'
6: lload_2 // Load long variable 'widerCount' onto stack
7: l2i // Narrow long to int (discards top 32 bits)
8: istore 4 // Store into int variable 'narrowedCount'
10: iload_1 // Load int variable 'count' onto stack
11: i2b // Narrow int to byte (truncates and sign-extends)
12: istore 5 // Store into byte variable 'narrowedByte'
14: ldc2_w #7 // Load double constant 3.14 onto stack
17: dstore 6 // Store into double variable 'ratio'
19: dload 6 // Load double variable 'ratio' onto stack
21: d2i // Convert double to int (truncates toward zero)
22: istore 8 // Store into int variable 'truncatedRatio'Notice instruction 11 (i2b). Even though narrowedByte is stored as a byte variable in local variable slot 5, on the operand stack it is manipulated as a 32-bit integer. This design of the JVM is why expressions on byte values give an int.
In Java
The following program demonstrates safe widening for accumulator products, safe clamping of floating-point coordinates to integer bounds, and character offset conversions for alphabet indexing.
In this program:
multiplySafely(quantity, unitPrice)multiplies two 32-bit integers without overflow by casting the first argument tolong, returning their 64-bit product.clampToGrid(coordinate, maxIndex)converts a floating-point coordinate to an integer cell index. It returns 0 for negative orNaNcoordinates, truncates valid decimals toward zero, and clamps values exceedingmaxIndextomaxIndex.letterToIndex(character)converts a lowercase ASCII letter into its zero-based index between 0 and 25 by subtracting'a'.quantityholds 2,000,000 andunitPriceholds 3,000.totalCoststores the safely calculated 64-bit product of 6,000,000,000.coordinateInside,coordinateBelow, andcoordinateBeyondtest normal, negative, and out-of-range floating-point coordinates againstmaxIndex(99).sampleLetterholds'h'andindexcaptures its 0-based alphabet index (7).
public class Main {
// Multiplies two 32-bit integers safely, returning their 64-bit product without overflow.
static long multiplySafely(int quantity, int unitPrice) {
return (long) quantity * unitPrice;
}
// Truncates a floating-point coordinate to an integer cell, clamping values to [0, maxIndex].
static int clampToGrid(double coordinate, int maxIndex) {
if (Double.isNaN(coordinate) || coordinate <= 0.0) {
return 0;
}
int cell = (int) coordinate;
return Math.min(cell, maxIndex);
}
// Converts a lowercase ASCII letter to its zero-based index in the range [0, 25].
static int letterToIndex(char character) {
return character - 'a';
}
public static void main(String[] args) {
int quantity = 2_000_000;
int unitPrice = 3_000;
long totalCost = multiplySafely(quantity, unitPrice);
double coordinateInside = 4.75;
double coordinateBelow = -2.5;
double coordinateBeyond = 150.0;
int maxIndex = 99;
int cellInside = clampToGrid(coordinateInside, maxIndex);
int cellBelow = clampToGrid(coordinateBelow, maxIndex);
int cellBeyond = clampToGrid(coordinateBeyond, maxIndex);
char sampleLetter = 'h';
int index = letterToIndex(sampleLetter);
System.out.println("Safe total cost: " + totalCost);
System.out.println("Grid cell for 4.75: " + cellInside);
System.out.println("Grid cell for -2.5: " + cellBelow);
System.out.println("Grid cell for 150.0: " + cellBeyond);
System.out.println("Alphabet index for 'h': " + index);
}
}Safe total cost: 6000000000
Grid cell for 4.75: 4
Grid cell for -2.5: 0
Grid cell for 150.0: 99
Alphabet index for 'h': 7Traps
Multiplying two ints before assigning to long
Two
intoperands multiply as a 32-bitint. If their product exceeds2_147_483_647, it overflows before the assignment widens it tolong. Cast the first operand tolongso multiplication executes in 64 bits.✕ WRONGlong total = first * second;✓ RIGHTlong total = (long) first * second;Casting negative doubles expecting downward rounding
Casting a
doubletointtruncates toward zero rather than downward. A coordinate of-2.7becomes-2, not-3. When grouping negative coordinates into spatial buckets, useMath.floorbefore casting.✕ WRONGint bucket = (int) coordinate;✓ RIGHTint bucket = (int) Math.floor(coordinate);Unexpected sign flip during byte narrowing
Casting an
intbetween 128 and 255 tobytesets the high bit to 1, turning it into a negative number in two's complement. If you need the raw 0 to 255 value as an integer, mask with& 0xFF.✕ WRONGbyte stored = (byte) unsignedValue;✓ RIGHTint masked = unsignedValue & 0xFF;Adding an int variable to a char without a cast
Arithmetic on
charpromotes both operands toint. Even whenoffsetis 1, the resultcurrent + offsetis anint, which cannot be stored in acharvariable without an explicit cast.✕ WRONGchar next = current + offset;✓ RIGHTchar next = (char) (current + offset);Losing precision when assigning large int to float
Widening an
inttofloatis automatic, butfloathas only 24 bits of significand. Values greater than16_777_216lose their least significant bits. Usedoubleor keep IDs inlongorint.✕ WRONGfloat metric = largeId;✓ RIGHTdouble metric = largeId;Assigning Math.round on double directly to int
Math.round(double)returns along, not anint. Assigning it directly to anintfails at compile time with a lossy conversion error. Use an explicit cast to(int)or store the result in along.✕ WRONGint rounded = Math.round(score);✓ RIGHTint rounded = (int) Math.round(score);Compound assignment hiding silent numeric overflow
The compound assignment operator
+=inserts a hidden cast back toshort. IfshortCount + deltaexceeds 32,767, the value silently wraps around to negative without any compile or runtime error.✕ WRONGshortCount += delta;✓ RIGHTshortCount = (short) Math.min(Short.MAX_VALUE, shortCount + delta);Casting the result of integer division instead of operands
Parentheses around
numerator / denominatorcause integer division to run first, discarding the fraction before the cast occurs. Castnumeratordirectly so division runs in floating point.✕ WRONGdouble ratio = (double) (numerator / denominator);✓ RIGHTdouble ratio = (double) numerator / denominator;