Conversions

Figure 109. Widening from byte literal to short Slide presentation

Figure 110. Narrowing from int literal to char variable Slide presentation

Figure 111. A widening «ladder» Slide presentation
byte   b = 42;  // Narrowing: constant int literal to byte
short  s = b;   // Widening
int    i = s;   // Widening
long   l = i;   // Widening
float  f = l;   // Widening
double d = f;   // Widening

Figure 112. A narrowing «ladder» Slide presentation
double d = 14.23;
float  f  = (float) d;  // Narrowing
long   l  = (long)  f;  // Narrowing
int    i  = (int)   l;  // Narrowing
short  s  = (short) i;  // Narrowing
byte   b  = (byte)  s;  // Narrowing

exercise No. 22

int and char

Q:

Explain the following output:

Code Output
char a = 0;
a -= 1;
int i = a;
System.out.println(i);
65535

Why do we see a result of 65535 rather than -1?

Tip

Read about the a char's value range.

A:

In contrast to byte, short and int a two byte char represents no negative but only positive values and zero:

decimal binary
0 0000 0000 0000 0000
1 0000 0000 0000 0001
2 0000 0000 0000 0010
...
65535 ( 2 16 - 1 ) 1111 1111 1111 1111

Like with other integer types the above table behaves in a cyclic way with respect to additions at its top and and subtractions at its bottom:

Code Output
char a = 65535;
a += 1;
int i = a;
System.out.println(i);
0

On machine level this may be conceived as an (incomplete) subtract operation:

 0000 0000 0000 0000
-0000 0000 0000 0001
--------------------
 1111 1111 1111 1111


exercise No. 23

float vs. double

Q:

We consider:

Code Output
System.out.println( 3.14 );
System.out.println( 3.14f );
System.out.println( 3.14d );
3.14
3.14
3.14

There seems to be no difference between the three literals 3.14, 3.14f and 3.14d. Is this actually true?

Tip

Read the section about floating point literals in Java. Write code exhibiting possible differences.

A:

The System.out.println( 3.14f ) statement's output is actually truncated with respect to output precision. Forcing 16 fractional digits to become visible reads:

Code Output
System.out.println(new DecimalFormat(
   "#0.0000000000000000").format(3.14f));
3.1400001049041750

The value 3.1400001049041750 is the closest possible approximation to 3.14 when using a 4-byte IEEE float. A 3.14f double literal won't be exact either but doubles the number of representing bytes to 8 thereby enhancing its representational precision substantially. This causes unexpected results:

Code Output
System.out.println(3.14f - 3.14d);
1.0490417468034252E-7

This difference is a result of 3.14d providing four additional bytes of precision therefore matching the exact value of 314 100 better than 3.14f.

Regarding types we have:

Literal Comment
3.14f 4-byte float literal
3.14d 8-byte double literal
3.14 Equivalent to 3.14d

Assignments to variables of type double are thus always guaranteed to work:

Code Comment
double d = 3.14f;
o.K., assigning float to double (widening)
double d = 3.14d;
o.K., assigning double to double
double d = 3.14;
o.K., assigning double to double as well.

Assignments to variables of type float may fail:

Code Comment
float f = 3.14f;
o.K., assigning float to float
float f = 3.14d;
Error, assigning double to float
float f = 3.14;
Error, assigning (implicit) double to float as well.

exercise No. 24

int to char narrowing problems

Q:

Reconsidering Figure 110, “Narrowing from int literal to char variable ” we observe the following two related snippets yielding compile time errors

  1. int i = 65;
    char c = i;

    On contrary the following code compiles well:

    char c = 65;
  2. char c = 66200;

    On contrary the following code compiles well:

    char c = 64200;

Explain these errors and their underlying reasons and provide a solution if possible.

Tip

Which data types are involved? Think about narrowing conversions and type casting.

A:

  • Assigning an int to a char variable effectively narrows from four to two bytes and is thus prohibited. A fix requires an explicit type cast:

    int i = 65;
    char c = (char) i;

    Since 65 fits well into a char being limited by 2 16 - 1 our cast will not have any negative impact.

  • We consider the binary representations of 66200 and 64200:

    System.out.println(Integer.toBinaryString(66200)); // Yields 1_00000010_10011000
    System.out.println(Integer.toBinaryString(64200)); // Yields   11111010_11001000

    Thus 64200 fits into a two byte char whereas 66200 being larger than 2 16 - 1 does not.

exercise No. 25

Get a byte from 139

Q:

Consider:

int i = 139;
byte b = (byte) i;
System.out.println(b);

Explain in detail why execution results in a value of -117.

A:

A four byte int representation of 139 reads 00000000_00000000_00000000_10001011. The cast b = (byte) i will strip the leading three bytes leaving us with b containing 10001011.

Since byte values in Java are being represented as signed values in two-complement notation this equals decimal -117.

exercise No. 26

Ariane, I miss you!

Q:

Reconsidering the Ariane 5 maiden flight crash read the comment buried in the solution of Inventing tinyint. . Try to mimic a code portion in Java showing the catastrophic error.

Start with a double variable using a value being suitable to be assigned to a short variable using a cast (narrowing).

Then in a second step raise this value breaking your short variable's upper limit.

A:

We start from:

double level = 2331.12; // smaller than 32767 == Short.MAX_VALUE

short value = (short) level;

System.out.println(value);

Execution yields an expected integer output of 2331. However increasing our level variable's value from 2331.12 to 42331.12 yields an output of -23205 due to an overflow.

exercise No. 27

Reducing long to int (difficult)

Q:

For changing a map's scale from fine to coarse Joe programmer intends to map positive long values to int values. This requires scaling down half the long data type's range [ 0 , 2 63 - 1 ] to the int's range of [ 0 , 2 31 - 1 ] :

From To
long remark int remark
0 0
1 0
... 0
4294967295 2 32 - 1 0
4294967296 2 32 1
4294967297 2 32 + 1 1
...
9223372036854775806 2 63 - 2 or Long.MAX_VALUE - 1 2147483647 2 31 - 1 or Integer.MAX_VALUE
9223372036854775807 2 63 - 1 or Long.MAX_VALUE 2147483647 2 31 - 1 or Integer.MAX_VALUE

Joe's idea is dividing long values by 2 32 . As a result a long value of - 2 63 will be reduced to the intended value of - 2 31 . Since 2 32 seems to be equal to 2 * (Integer.MAX_VALUE + 1)) (why?) Joe's first attempt reads:

final long longValue = 2147483648L;
final int reducedValue = (int) (longValue / (2 * (Integer.MAX_VALUE + 1)));
System.out.println(reducedValue);

Unfortunately the results are not promising. This code merely results in a runtime error:

/usr/lib/jvm/java-8-oracle/bin/java ...
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at qq.App.main(App.java:27)

Process finished with exit code 1

Explain the underlying problem and correct Joe's error.

Tip

It may be helpful thinking of a smaller example before. Consider two hypothetic signed integer types TinyLong and TinyInt of four and two bits respectively. The corresponding mapping will be:

TinyLong, n = 4 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7
TinyInt, n = 2 -2 -1 0 1

A:

Joe's intention with respect to our toy example types implies dividing TinyLong values by 2 2 (truncating). This indeed yields the desired result for non-negative values. So why does Joe encounter a division by zero runtime exception when executing longValue / (2 * (Integer.MAX_VALUE + 1))?

Unfortunately Joe's implementation is seriously flawed for even two reasons:

  1. The constant Integer.MAX_VALUE already suggests we will not be able to increase its value while staying as an int. The expression Integer.MAX_VALUE + 1 will be evaluated using int rather than long arithmetic returning:

      01111111_11111111_11111111_11111111
    + 00000000_00000000_00000000_00000001
    _____________________________________
      10000000_00000000_00000000_00000000

    This is the binary representation of the unintended result Integer.MIN_VALUE due to an arithmetic overflow. The expression 2 * (Integer.MAX_VALUE + 1) then gives rise to a second overflow error:

      10000000_00000000_00000000_00000000
    + 10000000_00000000_00000000_00000000
    _____________________________________
      00000000_00000000_00000000_00000000

Both errors combined surprisingly result in a value of 0 explaining the division by zero error message. There are two possible solutions:

(int) (longValue / (2L * (Integer.MAX_VALUE + 1L)))

Introducing 2L or 1L (one is sufficient) in favour of simply using 2 and 1 turns both addition and multiplication into operations involving at least one long argument. Thus for both operations the Java runtime will use long arithmetic returning the desired reducing factor of 2 32 of type long.

(int) (longValue / 2 / (Integer.MAX_VALUE + 1L))

Same result as before.

Note

This time the expression starts with longValue / 2 ... Since the variable longValue is of type long the expression longValue / 2 will be evaluated by the Java runtime using long arithmetics. The result will subsequently be divided by Integer.MAX_VALUE + 1L again using long arithmetic.