Why x *= 0.90 Works for int in Java But x = x * 0.90 Causes Lossy Conversion Error?
If you’ve ever written Java code involving numeric operations, you might have stumbled upon a puzzling scenario: when you try to update an int variable using x = x * 0.90, the compiler throws a "lossy conversion" error. But if you use the compound assignment operator x *= 0.90, the code compiles without a hitch. Why the discrepancy?
This blog dives deep into Java’s type system, assignment rules, and the subtle behavior of compound operators to demystify this phenomenon. By the end, you’ll understand why one form causes an error and the other doesn’t—along with the potential pitfalls of relying on implicit conversions.
Table of Contents#
- Understanding the Basics: int and double in Java
- The Case of
x = x * 0.90: Why the Error Occurs - The Case of
x *= 0.90: Why It Works - Digging Deeper: Implicit Cast in Compound Assignment
- Potential Pitfalls: Lossy Conversion Without Warning
- When to Use Which? Best Practices
- Conclusion
- References
1. Understanding the Basics: int and double in Java#
Before we tackle the assignment puzzle, let’s recap how int and double work in Java:
int: A 32-bit signed integer (range: -2³¹ to 2³¹-1). It stores whole numbers without decimal points (e.g.,5,-42).double: A 64-bit double-precision floating-point number (IEEE 754 standard). It stores decimal values with higher precision (e.g.,3.14,0.90).
When you mix int and double in an operation (like multiplication), Java automatically performs a widening primitive conversion: the int is promoted to a double because double can represent a larger range of values. This ensures no loss of information during the operation itself. However, converting back from double to int is not automatic—and that’s where the confusion begins.
2. The Case of x = x * 0.90: Why the Error Occurs#
Let’s start with the problematic example:
public class Main {
public static void main(String[] args) {
int x = 100;
x = x * 0.90; // Compile-time error: incompatible types
}
}What Happens Here?#
Let’s break down the operation x * 0.90 step by step:
xis anint(100), and0.90is adoubleliteral (Java treats decimal literals asdoubleby default).- Multiplication: To multiply
intanddouble, Java first convertsx(100) to adouble(100.0). The result of100.0 * 0.90is90.0, which is adouble. - Assignment: Now, we try to assign the
doubleresult (90.0) back to theintvariablex.
Why the Error?#
Java is strict about type safety. Assigning a double to an int requires a narrowing primitive conversion, which can lose information (e.g., truncating decimal values or overflowing). Since this conversion is "lossy," Java forbids it unless you explicitly use a cast (e.g., (int)(x * 0.90)). Without the cast, the compiler throws an error:
error: incompatible types: possible lossy conversion from double to int
x = x * 0.90;
^
3. The Case of x *= 0.90: Why It Works#
Now, let’s modify the example to use the compound assignment operator *=:
public class Main {
public static void main(String[] args) {
int x = 100;
x *= 0.90; // No error! x becomes 90
System.out.println(x); // Output: 90
}
}What Changed?#
The compound assignment operator *= behaves differently. Unlike simple assignment (=), it includes an implicit cast back to the type of the left-hand variable.
The Secret: Implicit Casting in Compound Assignment#
Java’s Language Specification (JLS §15.26.2) defines compound assignment operators as follows:
A compound assignment expression of the form
E1 op= E2is equivalent toE1 = (T) ((E1) op (E2)), whereTis the type ofE1, except thatE1is evaluated only once.
In plain English: x *= 0.90 is shorthand for x = (int)(x * 0.90). The result of x * 0.90 (a double) is explicitly cast back to int before assignment.
Let’s Verify:#
For x = 100:
x * 0.90=100.0 * 0.90=90.0(double).- Implicit cast:
(int)90.0=90(int). xis updated to90—no error.
4. Digging Deeper: Implicit Cast in Compound Assignment#
To fully grasp why *= works, let’s generalize the rule for compound assignment:
For any compound operator op=, the expression E1 op= E2 is equivalent to:
E1 = (T)((E1) op (E2))where T is the type of E1.
Key Takeaways:#
- Implicit Narrowing Cast: The result of
(E1 op E2)is cast toT(the type ofE1), even ifE2is a wider type (likedouble). - Single Evaluation of E1: Unlike
E1 = E1 op E2,E1is evaluated only once (important for expressions with side effects, e.g.,arr[i++] *= 2).
Example with Another Operator:#
The same logic applies to other compound operators like +=, -=, etc. For example:
int a = 5;
a += 3.14; // Equivalent to a = (int)(a + 3.14) → (int)(8.14) → 8
System.out.println(a); // Output: 85. Potential Pitfalls: Lossy Conversion Without Warning#
While x *= 0.90 compiles, it’s not without risks. The implicit cast hides a critical detail: data loss due to truncation.
Example 1: Truncating Decimals#
int x = 11;
x *= 0.90; // x = (int)(11 * 0.90) → (int)(9.9) → 9 (decimal truncated)
System.out.println(x); // Output: 9 (not 10!)Example 2: Overflow Risks#
If the result of (E1 op E2) exceeds the range of int, the cast will wrap around (due to Java’s integer overflow behavior):
int maxInt = Integer.MAX_VALUE; // 2147483647
maxInt *= 2.0; // (int)(2147483647 * 2.0) → (int)(4294967294.0) → -2 (overflow!)
System.out.println(maxInt); // Output: -2 (unexpected!)The Danger:#
Developers may not realize the implicit cast is happening, leading to silent bugs. For example, a finance application using x *= 0.90 to apply a 10% discount could lose cents due to truncation.
6. When to Use Which? Best Practices#
Avoid Implicit Casts for Clarity#
Even though x *= 0.90 works, prefer explicit casting in simple assignment for readability:
int x = 100;
x = (int)(x * 0.90); // Clearer: intentional truncationThis makes the narrowing conversion visible to anyone reading the code.
Use Compound Assignment Judiciously#
Reserve *= for cases where the implicit cast is intentional and obvious (e.g., simple scaling of integers). Document the truncation behavior if precision matters.
Prefer double for Floating-Point Logic#
If your code requires decimal precision, use double (or BigDecimal for financial calculations) instead of int to avoid truncation:
double price = 100.0;
price *= 0.90; // 90.0 (no lossy conversion)7. Conclusion#
The key difference between x = x * 0.90 and x *= 0.90 lies in Java’s handling of compound assignment operators:
x = x * 0.90fails because it attempts to assign adoubleresult to anintwithout an explicit cast.x *= 0.90works because compound assignment includes an implicit(int)cast, truncating thedoubleresult toint.
While *= is convenient, always be mindful of silent data loss. When in doubt, use explicit casts to make your intent clear. Understanding Java’s type rules (as defined in the JLS) is the first step to writing robust, error-free code.