6.7 TYPE CONVERSION FOR THE PRIMITIVE TYPES


6.7 TYPE CONVERSION FOR THE PRIMITIVE TYPES

Automatic type conversions are often carried out during initialization; assignment; matching of an argument with the corresponding parameter during a function invocation; conversion of the operands to a common type; and when the type of expression in a return statement does not match a function's return type. One can also force a type conversion by using the cast operator. Automatic type conversions are also referred to as implicit type conversions, whereas the type conversions brought about by the use of a cast operator are sometimes referred to as explicit type conversions.

Because Java is more strongly typed than C++, many automatic type conversions that are allowed in C++ are illegal in Java. As a case in point, while the following is acceptable to C++

      int i = 98;      char c = i;             // initialization from an int 

the second statement would NOT be acceptable to Java, although Java would be happy with the statement

      char c = 98;            // initialization from an int literal 

As we will explain in the rest of this section, automatic conversion from an int to a char is allowed only for initialization from literals in Java.

This section will discuss how one talks about the different kinds of type conversions for the primitive types in C++ and Java, which conversions are carried out automatically, and which can only be brought about through casting.

6.7.1 Implicit Type Conversions in C++

For automatic type-conversion for the primitive types, C++ makes a distinction between a promotion and a standard conversion. In a promotion, integral types stay integral and non-integral types stay non-integral. In addition, there must not be a loss of information. Examples of integral promotions include bool to int, char to int, short to int, and their unsigned counterparts; and examples of non-integral promotions include float to double, double to long double, and so on.[7] The following program illustrates promotions in initialization, assignment, in function invocation, and in return from a function:

 
//Promo.cc #include <iostream> using namespace std; void g1(short x) {cout << "short version invoked x: " << x << endl;} void g2(long x) { cout << "long version invoked x: " << x << endl; } int g3(short x) { cout << "promotion to match return type" << endl; return x; } int main() { //promotion in initialization: char ch = 'a'; int x = ch; //promotion in assignment: long j; j = x; cout << j << endl; //output: 97 //(A) //promote char into a short for function invocation: g1( ch ); //output: short version invoked x:97 //(B) //promote int into a long for function invocation: g2( x ); //output: long version invoked x:97 //(C) short s = 16; //promotion inside g3 when matching returned value to return type: int w = g3( s ); //output: promotion to match return type //(D) return 0; }

The program output is shown in the commented out portions of the lines (A), (B), (C), and (D).

The other kind of automatic type conversion for the primitive types in C++-the standard conversion-can entail loss of information. There are two ways to characterize this information loss: a loss of information regarding the overall value of a number, and a loss of precision. For example, when you convert an int to a short, the system would retain only the two lowest bytes of the int, which for a large enough int can cause its value to become completely meaningless. On the other hand, if an int is converted into a float, you don't lose information regarding the magnitude of the number since a float can hold much larger numbers than an int. What you can lose in this case is precision; as was mentioned earlier, the precision of a float is limited to only six significant digits. Therefore, if you convert a large int into a float and then re-convert the float back into an int, some of the least significant digits could be different between the two integers. This example is included in the program below.

Examples of standard conversions in C++ are int to double, double to int, int to float, float to int, and so on. Here is an example of a C++ program that illustrates some of the automatic standard conversions, and the loss of information entailed in some of them.

 
//StandardConvert.cc #include <iostream> using namespace std; void g1(short x) {cout << "short version invoked x: " << x << endl;} void g2( int x ) { cout << "int version invoked x: " << x << endl; } void g3( float x ) {cout << "float version invoked x: " << x << endl;} float g4( int x ) { cout << "standard conversion to match return type" << endl; return x; } int main() { char ch = 'a'; //standard conversion in initialization: float x = ch; long j; //standard conversion in assignment: j = x; cout << j << endl; //output: 97 //(A) int i = 1234567890: //standard conversion from int to float: float fi = i; //error in converting int to float: cout << i - (int) fi << endl;// output: -46 //(B) //value too large to fit into an int: float y = 1e20f; //value too large to fit into a float: double z = 1e100; //standard-convert float into a short for function invocation: g1( y ); // output: short version invoked x: -32768 //(C) //standard-convert float into an int for function invocation: g2( y ); // output: int version invoked x: -2147483648 //(D) //standard-convert double into a float for function invocation: g3( z ); // output: float version invoked x: inf //(E) //standard-convert returned value to return type inside g4: float w = g4( i ); // output: standard convert to match return type //(F) //error in returned value: cout << i - (int) w << endl; // output: -46 //(G) return 0; }

This program output is shown in the commented out portions of the lines (A) through (G) above. The output in line (A) shows the result of automatic standard conversion during initialization from a char to a float and then from float to a long. There is obviously no loss of information here. The output in line (B) shows the loss of precision when a large int, in this case of value 1234567890, is converted to a float.

For the output shown in line (C), when the float value of 1e20 is converted into a short when matching the argument to the parameter type, the value becomes -32768 inside the function g1. This happens because the float is first converted into an int on the basis of IEEE 754 round-toward-zero mode and then from that is extracted the largest value that can be represented by a short. As the output in line (D) shows, the int that 1e20 is converted into is -2147483647. The output in line (E) shows what happens when we convert a double that is too large to be represented by a float into a float in a function call; the result is an overflow as represented by the symbolic constant inf. The output in line (G) shows what can happen as a result of the standard conversion performed when the type of the expression in a return statement does not match the return type of a function.

Converting a non-integral type to an integral type, as in lines (C) and (D) above, calls for a non-trivial and implementation dependent conversion. If the floating-point number is neither a NaN nor an inf, the floating value is typically rounded toward zero using the IEEE 754 standard round-toward-zero mode and its low-order bytes retained that can be accommodated in the target type. This means that a floating number such as 32.54 would be converted to the integer value 32 and a number like 32.54 to the integer value 32. Special cases deal with the symbolic values NaN and inf for the floating-point numbers.

Converting an integral type to a non-integral type is typically carried out using the IEEE 754 round-to-nearest mode, which means converting to the nearest representable non-integral type.[8]

So far we have talked about C++'s automatic type conversions during initializations, assignments, when matching arguments with parameters during function invocations, and when the type of the expression in a return statement does not match the return type of a function. Automatic type conversions in C++ are also carried out when arithmetic operations are performed if the operands are of different types. The strategy for these automatic type conversions, which are also known as binary numeric promotions, is to convert the operands to the "narrowest" type that will safely accommodate both operands. For example, if you are adding an int to a float, since the range of numbers that a float can accommodate is larger than the range that can be accommodated in an int, the narrowest type that would accommodate both would be float.

6.7.2 Implicit Type Conversions in Java

The automatic type conversions for the primitive types in Java fall into two categories: widening conversions and narrowing conversions. Widening primitive conversions include the promotions permitted in C++. Additional conversion in Java that are of the widening variety include integral to wider non-integral conversions, such as from int to float and double, etc.

Here are the 19 widening conversions permitted automatically in Java for initialization, assignment, for matching arguments with parameters during function invocation, and when the type of the returned value does not match the declared return type of a method:

  • from byte to short, int, long, float, or double

  • from short to int, long, float, or double

  • from char to int, long, float, or double

  • from int to long, float, or double

  • from long to float or double

  • from float to double

These do not entail any loss of information or precision when the conversions are from an integral type to another integral type, and from a non-integral type to another non-integral type. However, there may be a loss of precision (meaning that while the overall magnitude is preserved, some of the least significant bits may be lost) when converting from an integral type to a non-integral type, such as from long to float.[9]

Here is a program that illustrates some of the automatic widening type conversions:

 
//Widening.java class Test { static void g1( short x ) { System.out.println( "short version invoked, x = " + x ); } static void g2( int x ) { System.out.println( "int version invoked, x = " + x ); } static float g3( int x ) { System.out.println( "widening conversion on return" ); return x; } public static void main( String[] args ) { byte b1 = 16; byte b2 = 24; // char c1 = b1; // ERROR //(A) // c1 = b2; // ERROR //(B) //widening from byte to short: short s = b1; System.out.println( s ); // output: 16 //(C) //widening from char to int: char c = 'a'; int i1 = c; System.out.println( i1 ); // output: 97 //(D) //widening from int to float: int i2 = 1234567890; float f1 = i2; System.out.println( i2 - (int) f1 );// output: -46 //(E) //widening from float to double: float f2 = 1e20f; double d1 = f2; System.out.println(d1); //output: 1.0000000200408773E20 //(F) //widening from byte to short in method invocation: g1( b1 ); // output: short version invoked, x = 16 //(G) //widening from short to int in method invocation: g2( s ); // output: int version invoked, x = 16 //(H) //widening from int to float in method invocation: float f3 = g3( i2 ); // output: widening conversion on return //(I) //Error in widening conversion from int to float: System.out.println( i2 - (int) f3 ); // output: -46 //(J) } }

The output of the program is shown in the commented out portions of the lines (C) through (J).

As pointed out in lines (A) and (B), a widening conversion is not permitted from a byte to a char either for initialization or for assignment. This may seem like a curious omission since a char with its two bytes of storage is after all wider than a byte. The reason has to do with the fact that a byte is signed, whereas a char is unsigned. So if you were to convert a byte into a char, you could end up with an entirely meaningless value. Recall that the value of a byte can range from128.to 127. Consider a value such as 128. It will be stored as a single byte using the two's complement representation, which in hex is Ox80.[10] If this were to be converted into a two-byte char using the usual rules for widening conversion from a signed integer type to an integral type, the system would simply sign-extend the two's complement representation, meaning that the wider format of the destination type would be filled with the sign bit. This would give us a hex of OxFF80 for the two bytes of the char. The integer value of this bit pattern is 65,408.

Line (C) shows the result of a widening conversion from a byte to a short. As expected, there is no loss of information or precision. Line (D) similarly shows a widening conversion from a char to an int. No loss of information or precision again. In both cases, integral types stayed integral. Line (E) shows the result of a widening conversion from a large int to a float and the consequent loss of precision. Line (F) demonstrates a widening conversion from a non-integral type to a non-integral type-in this case from a float to a double. The rest of the code in the program deals with automatic widening conversions performed when arguments are dispatched to functions and when a value is returned by a function. When the function g1 is called in line (G), the supplied argument is automatically converted from a byte to a short. The same thing happens in line (H), except that the conversion is from a short to an int. Line (J) demonstrates the error incurred when a function tries to return a large int after converting it to a float.

Compared to a widening conversion, a narrowing conversion in Java (like many of the standard conversions in C++) can entail loss of information besides loss of precision. A majority of the permissible narrowing conversions require an explicit use of the cast operator. Java allows very few narrowing conversions to take place automatically. The automatic narrowing conversions are limited to initializations that require conversions from an int literal to a byte, to a short, or to a char provided the value to be converted can be represented by the target types. For example, the following is legal syntax

      byte b = 15;      // narrowing conversion from an int literal to byte 

In particular, Java does not allow any narrowing conversions to take place automatically when matching arguments with parameters in function invocations. The error reports, all produced by the compiler, in the following program illustrate this fact. The output of the program is shown in the commented out portions of the lines (A), (B), and (C). If the program statements that are completely commented out are un-commented, the nature of the resulting compilation errors is shown in an abbreviated manner in the vicinity of the statements.

 
//Narrowing.java class Test { static void g1( short x ) {} static void g2( int x ) {} static void g3( float x ) {} public static void main( String[] args ) { int i = 98; // char c = i; // not allowed for initialization char c = 98; // ok for initialization from literal System.out.println( c ); // output: b //(A) byte b = 97; // ok for initialization from literal System.out.println( b ); // output: 97 //(B) // float y = 1e100; // double to float not allowed double z = 1e100; // float y = z; // double to float not allowed float y = (float) z; // but ok with cast System.out.println( y ); // output: Infinity //(C) /* g1( y ); // ERROR: // cannot automatically convert float to short g2( y ); // ERROR: // cannot automatically convert float to int g3( z ); // ERROR: // cannot automatically convert double to float */ } }

In Java, automatic type conversions of the widening kind are also carried out when arithmetic, comparison, and logical operations are performed if the operands are of different types. The strategy for these automatic type conversions, which are also known as binary numeric promotions, is the same as in C++, that is to convert the operands to the "narrowest" type that will safely accommodate both operands, with the additional stipulation that the operands will never be narrower than an int. So if you add a long to an int, both operands will be converted to long. But if you add a short to an int, both operands will be converted to int.

6.7.3 Explicit Type Conversion in C++

A programmer can also force a type conversion between related types by using a cast operator. In the following program, the integer value of a character is printed out by using the C++'s cast operator static_cast. The program asks a user to type in a character and then returns the integer value of that character.

 
//ExplicitCast1.cc #include <iostream.h> using namespace std; int main() { char ch, ch_prev; while (1) { cout << "Enter a character: " ; cin >> ch; if ( ch == ch_prev ) break; //(A) ch_prev = ch; cout << "Integer value of the character is: " << static_cast<int>(ch) >> endl; //(B) } return 0; }

The conversion from a character to its corresponding ASCII integer in this program is carried out by static_cast<int> in line (B). In general, the operation static_cast<T2> (T1) converts a value of type T1 to a value of type T2 provided an implicit conversion exists from the former type to the latter type. Recall from Section 6.7.1 that implicit (meaning automatic) type conversions in C++ include both the error-free promotions and the possibly error-producing standard conversions.

When casting to a narrower type, such as when converting an int into a short, or an int into a char, the upper bytes that cannot be accommodated in the narrower target type are discarded. Since the uppermost bit for a signed type is the sign bit, this can cause the sign to reverse for the target type, besides, of course, the magnitude getting changed. This is illustrated with the help of three examples in the following program:

 
//ExplicitCast2.cc #include <iostream> int main( ) { int i1 = 312; int i2 = -255; int i3 = 32768; cout << il << ": " << "cast to short is " << static_cast<short>(il) << ", cast to char is " << static_cast<int>( static_cast<char>(il) ) << endl; cout << i2 << ": " << "cast to short is " << static_cast<short>(i2) << ", cast to char is " << static_cast<short>( static_cast<char>(i2) ) << endl; cout << i3 << ": " << "cast to short is " << static_cast<short>(i3) << ", cast to char is " << static_cast<int>( static_cast<char>(i3) ) << endl; return O; }

The produces the following output:

      312: cast to short is 312, cast to char is 56      -255: cast to short is -255, cast to char is 1      32768: cast to short is -32768, cast to char is O 

To focus on the last output line above, the bit pattern for the number 32768 as an int is

      00000000 00000000 10000000 00000000 

When converted to a short, only the lower two bytes are retained, giving us

      1000000 00000000 

which is the two's complement representation of 32768.

When an integral type is cast explicitly to a wider integral type, the source type is sign-extended to fill the wider type. What that means is that if the uppermost bit (the sign bit) of the source type is a zero, the extra bits of the wider type are filled with zeroes. Otherwise, the extra bits are filled with 1's.

In the C++ code that was written before standardization, explicit type conversion was carried out by cast operators using the syntax

      type( expression ) 

For backwards compatibility, it is still legal to use this form of casting. So line (B) of ExplicitCast1. cc could be replaced by

      cout << "Integer value of the character is: " << int(ch) << endl; 

One could also use the C-language cast, as in the following replacement for the above statement:

      cout << "Integer value of the character is: " << (int) ch << endl; 

But, inC++, it is safer to use the operator static_cast because it cannot cast away const.

C++ also provides three other cast operators for explicit type conversion: dynamic_cast for run-time type identification (RTTI), const_cast for removing the const qualifier, and reinterpret_cast for converting between unrelated types (as in converting an integer into a memory address). These cast operators are further discussed in Section 10 of Chapter 16.

In case the reader is puzzled by line (A) in the C++ program ExplicitCast1. cc we showed at the beginning of this section, it is to provide a way for program termination. As such, the job of the program is to supply you with the ASCII integer code for any character you enter through the keyboard as the program is executing the infinite while loop. You can terminate the program by asking the program to respond to the same character twice in a row.[11]

6.7.4 Explicit Type Conversion in Java

The Java program below uses a cast in line (A) to carry out a narrowing conversion from an int to a char. The program will try to print out as many characters as possible (depending on the print representations available to the program) for all integers between 0 and 10,000.

 
//ExplicitCast1.java import java.io.*; class Test { public static void main( String[] args ) { try { PrintWriter out = new PrintWriter( new FileOutputStream( "out_file" ) ); char ch_value; for (int i=0; i< 10000; i++) { ch_value = (char) i; //(A) out.println( "for i= " + i + "char is " + ch_value ); } out.close(); } catch( 10Exception e) { } } }

When an explicit conversion is from an integral type to a narrower integral type, the upper bits of the source type that cannot be accommodated in the target type are discarded. Since the uppermost bit for signed types is the sign bit, casting to a narrower type can reverse the sign of a value, in addition to changing its magnitude. The following program shows examples of this.

 
//ExplicitCast2.Java class Test { public static void main( String [] args ) { int i1 = 312; int i2 = -255; int i3 = 32768; System.out.println( il + ": " + "cast to short is " + (short) il + ", cast to byte is " + (byte) i1 ); System.out.println( i2 + ": " + "cast to short is " + (short) i2 + ", cast to byte is " + (byte) i2 ); System.out.println( i3 + ": " + "cast to short is " + (short) i3 + ", cast to byte is " + (byte) i3 ); } }

The output of this program is

      312: cast to short is 312, cast to byte is 56      -255: cast to short is -255, cast to byte is 1      32768: cast to short is -32768, cast to byte is O 

To explain the second line of the output above, using two's complement representation the number255 as an int is stored as the bit pattern[12]

      11111111 11111111 11111111 11111111 00000001 

When this number is cast to a byte, only the lowest byte is retained

      00000001 

whose integer value is just 1. When the same number is cast to a short, the lowest two bytes are retained:

      11111111 00000001 

Since the uppermost bit (the sign bit) is set, the magnitude of the integer value represented by this bit pattern is obtained by reversing the pattern and adding 1, which gives us

      00000000 11111111 

which yields a magnitude value of 255, giving the short a value of 255.

The same thing happens for the other integral conversions of the narrowing kind. For example, when an int is cast to a char, the upper two bytes are discarded and only the bottom two bytes retained for the char.

When narrowing conversions take non-integral types into integral types, if the value of the floating-point number is valid and finite, the integer value is obtained by rounding toward zero the floating-point value using the IEEE 754 round-toward-zero mode. Special cases for when the floating-point value is too large, or too small, or NaN, and so on, are considered separately.

[7]In order to maintain backward compatibility with C, the integral type conversions to long and the non-integral type conversion to long double are not considered promotions. The original purpose of a promotion in C was to bring the operands to their "natural" sizes for arithmetic operations [54]. Integral conversions to long and non-integral conversions to long double are included in standard conversions.

[8]The IEEE 754 standard defines four rounding modes for floating-point arithmetic: round toward plus infinity, round toward minus infinity, round toward zero (also called truncate), and round to nearest.

[9]See the previous subsection for the distinction between the loss of information regarding the overall value of a number and the loss of precision.

[10]We obtain this by taking the bit pattern of +128 (which is 10000000), reversing it to obtain 01111111, and adding 1 to the result. We get the bit pattern 10000000, whose hex representation is Ox80.

[11]In a Unix environment, you could also terminate that program by typing control-d, which would be trapped by the operating system, causing program termination.

[12]Which can be obtained by writing down the bit pattern for +255:

      00000000 00000000 00000000 11111111 

reversing the O's and 1's in this pattern and then adding 1 to the result.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net