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#

  1. Understanding the Basics: int and double in Java
  2. The Case of x = x * 0.90: Why the Error Occurs
  3. The Case of x *= 0.90: Why It Works
  4. Digging Deeper: Implicit Cast in Compound Assignment
  5. Potential Pitfalls: Lossy Conversion Without Warning
  6. When to Use Which? Best Practices
  7. Conclusion
  8. 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:

  1. x is an int (100), and 0.90 is a double literal (Java treats decimal literals as double by default).
  2. Multiplication: To multiply int and double, Java first converts x (100) to a double (100.0). The result of 100.0 * 0.90 is 90.0, which is a double.
  3. Assignment: Now, we try to assign the double result (90.0) back to the int variable x.

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= E2 is equivalent to E1 = (T) ((E1) op (E2)), where T is the type of E1, except that E1 is 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).
  • x is updated to 90—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 to T (the type of E1), even if E2 is a wider type (like double).
  • Single Evaluation of E1: Unlike E1 = E1 op E2, E1 is 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: 8

5. 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 truncation

This 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.90 fails because it attempts to assign a double result to an int without an explicit cast.
  • x *= 0.90 works because compound assignment includes an implicit (int) cast, truncating the double result to int.

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.

8. References#