This chapter compares the support for numbers, dates, and times provided in the Java class library with those in Microsoft .NET. Java and C# both provide language-level support for straightforward numeric operations; more complex features are implemented in the class libraries. Conversely, neither Java nor C# provides language-level support for working with dates and times; classes offer a better solution to the complexities of localization.
The .NET numeric types and the C# language syntax used to manipulate them are discussed in Chapter 5. As described in Chapter 5, .NET value types are implemented as structs and have object capabilities. This section discusses the object features of the numeric types, particularly those that support comparison, conversion, and formatting. In Java, the primitive wrapper classes such as java.lang.Integer and java.lang.Double predominantly provide these features. This section also covers the facilities provided by .NET to perform mathematical operations and generate pseudorandom numbers; both provide direct equivalents of the facilities offered by Java.
.NET implements all inbuilt numeric data types as structs in the System namespace. C# defines convenient keyword aliases for the fully qualified struct names, so the full names are rarely used. We discussed the C# keywords and their equivalent fully qualified struct names in Chapter 4.
All of the numeric structs derive from the System.ValueType class, which derives from System.Object. The Equals, GetHashCode, and ToString methods inherited from Object are overridden to provide behavior more appropriate to numeric types. GetType still returns a Type object for the struct instance. We’ll discuss these methods in the Members section later in this chapter. Additionally, all numeric structs implement a standard set of interfaces, provide type-specific members, and are serializable.
For a complete discussion of serialization, see Chapter 10.
All of the inbuilt numeric types implement the IComparable, IFormattable, and IConvertible interfaces from the System namespace.
The IComparable interface specifies the implementation of the following method:
int CompareTo(object obj);
The CompareTo method enables instances of numeric types to be compared with other numbers, with the return value indicating their relative ranking. This is the same as the Java java.lang.Comparable interface, and the return values are
< 0 if the instance value is less than the value of obj.
0 if the instance value is equal to the value of obj.
> 0 if the instance value is greater than the value of obj.
Unfortunately, although the CompareTo method accepts any Object reference as an argument, the numeric types throw a System.ArgumentException if the object and the argument are different types. No implicit conversion is performed; the types must match or have been explicitly cast to the same type. The value null is a valid value and is always considered less than any other value.
Implementation of the IComparable interface is useful when numeric types are used in collections, but for general purposes the >, ==, and < operators offer a simpler solution.
See Chapter 9, for information on the use of IComparable in collections.
IFormattable specifies the implementation of an overloaded ToString method that supports the rendering of numeric types to strings. Format specifiers and localization settings provide extensive control over the format of the string representation. See the Formatting Numbers section later in this chapter for more information.
The IFormattable interface is discussed in detail in Chapter 7.
The IConvertible interface specifies methods for converting the implementing class to a set of target representations. The methods are ToBoolean, ToByte, ToChar, ToDataTime, ToDecimal, ToDouble, ToInt16, ToInt32, ToInt64, ToSByte, ToSingle, ToString, ToType, ToUInt32, and ToUInt64. All methods take an instance of IFormatProvider as an argument. The implementing class refers to the IFormatProvider instance to obtain localization settings for use in the conversion process. If null is provided, the current default system settings will be used. If no meaningful conversion exists between two types, a System.InvalidCastException is thrown.
The IConvertible interface also includes the GetTypeCode member. GetTypeCode returns a member of the System.TypeCode enumeration that uniquely identifies the implementing type.
Although the inbuilt numeric types implement IConvertible, explicit interface implementation means it is not possible to invoke IConvertible members through an instance of the numeric type. The .NET documentation recommends conversion of numeric types be carried out using the static members of the System.Convert utility class as opposed to invoking IConvertible members directly. Alternatively, the implicit and explicit conversion support in C# is sufficient for most purposes. We’ll discuss the System.Convert class later, in the Converting Numbers section.
Aside from the methods required by the interface implementations described earlier, all numeric types implement the members described in Table 8-1.
Table 8-1. Numeric Type Members
Member | Description |
---|---|
Public Fields | |
MaxValue | Constant containing the maximum value representable by the numeric type. |
MinValue | Constant containing the minimum value representable by the numeric type. |
Methods | |
Equals() | Overridden method inherited from System.Object. Returns true if the argument provided is of the same type and value as the instance; otherwise, returns false. Implicit conversion is not used; however, explicitly casting types with the same value will return true. |
ToString() | The ToString method inherited from System.Object is overridden and calls the overloaded IFormattable.ToString method, passing it default formatting arguments. |
GetHashCode() | Overridden. Returns a hash code appropriate to the numeric type. |
GetType() | Inherited from System.Object. Returns a Type object for the numeric type. |
Parse() | Converts a string representation of a number to an instance of the numeric type. |
System.Single and System.Double contain additional members, described in Table 8-2, to support floating-point operations.
Table 8-2. Additional System.Single and System.Double Members
Member | Description |
---|---|
Public Fields | |
Epsilon | Constant containing the smallest positive value greater than 0 representable by the floating-point type. |
NaN | Constant representing "Not a Number." |
NegativeInfinity | Constant representing negative infinity. |
PositiveInfinity | Constant representing positive infinity. |
Methods | |
IsInfinity | Static method returns true if the specified number evaluates to negative or positive infinity. |
Static method returns true if the specified number evaluates to a value that is not a number. | |
IsNegativeInfinity | Static method returns true if the specified number evaluates to negative infinity. |
IsPositiveInfinity | Static method returns true if the specified number evaluates to positive infinity. |
TryParse() | Converts a string representation of a floating-point number to an instance of the numeric type. Returns true if the string is parsed successfully; does not throw an exception. |
C# language syntax supports implicit and explicit conversion from one numeric type to another. The .NET class library also includes a utility class named System.Convert that contains static methods for converting from one numeric type to another, including support for conversions between numbers and bool, string, and DateTime values.
The Convert class implements the following static methods: ToBoolean, ToByte, ToChar, ToDateTime, ToDecimal, ToDouble, ToInt16, ToInt32, ToInt64, ToSByte, ToSingle, ToString, ToUInt16, ToUInt32, and ToUInt64. Each of these methods provides overloads that take each of the numeric types and return the appropriately converted value. If no meaningful conversion exists between two types, a System.InvalidCastException is thrown.
All numeric types implement the IFormattable interface. IFormattable specifies the implementation of a ToString method with the following signature:
string ToString(string format, IFormatProvider formatProvider);
The format argument takes what’s called a numeric format string, which determines how the number is formatted. The formatProvider argument provides information on localization setting to assist in the formatting process. If format-Provider is null, default localization settings are used.
In the following example, we use the numeric format string E2, which indicates scientific notation to two decimal places, and we specify null as the IFormatProvider so that the default localization settings are used.
int SomeInt = 1234567; System.Console.WriteLine(SomeInt.ToString("E2", null));
The code produces the following output:
1.23E+006
As shown in this example, the ToString method of a numeric type can be called directly; more commonly, a number is formatted indirectly through one of the string formatting methods such as those provided by Console.WriteLine. When you’re using the string formatting methods, it’s important to understand that only the portion of the format specifier after the semicolon is passed to the ToString method of the number being formatted. For example, in the following statement:
Console.WriteLine("The amount is : {0,15:C2}", 1000000);
which, depending on localization settings, produces the following output:
The amount is : $1,000,000.00
only the C2 (in boldface) is used to format the literal 1000000, equivalent to the following direct ToString call:
1000000.ToString("C2", null);
String formatting methods are discussed fully in Chapter 7.
A standard numeric format string is a single letter followed by an optional pair of digits called the precision specifier. The purpose of the precision specifier depends on the format string used, and if not provided will default to a setting appropriate for the current system settings. The standard format strings are summarized in Table 8-3. The examples use an overloaded ToString method that takes a format string argument but that does not require an IFormatProvider instance.
Table 8-3. Numeric Format Strings
.NET interprets any format string that is a single character followed by optional digits as a standard numeric format string. If the character doesn’t match one of those in Table 8-3, ToString throws a FormatException. A format string containing more than one character is a custom format string.
Custom format strings map closely to the formatting functionality provided by the java.text.DecimalFormat class; custom format strings offer greater flexibility over how a number will be formatted. Custom format strings use a small selection of symbols to define the desired format of a numeric type. Table 8-4 provides a summary of the available symbols.
Table 8-4. Custom Numeric Format Strings
The java.lang.Math class is equivalent to the .NET System.Math class. Both classes provide a set of constants and static methods for performing mathematical operations. The methods common to both System.Math and java.lang.Math are, using the .NET names, as follows: Acos, Asin, Atan, Atan2, Ceiling, Cos, Exp, Floor, IEEERemainder, Max, Min, Pow, Sin, Sqrt, and Tan. Table 8-5 highlights the differences between the java.lang.Math and System.Math classes.
Table 8-5. Differences in Static Members of java.lang.Math and System.Math
Java | .NET | Comments |
---|---|---|
abs() | Abs() | .NET provides overloads that take a larger range of parameter types. |
N/A | Cosh() | Returns the hyperbolic cosine of the specified angle. |
log() | Log() | .NET provides an overloaded Log method that allows the base of the logarithm to be specified. |
Log10() | A convenience method for getting the base 10 logarithm of a number. | |
random() | N/A | See the next section, Random Numbers. |
rint() round() | Round() | The .NET Round method is more like Java rint than round. It returns a floating-point representation of the rounded number as opposed to an integer value. .NET provides an overloaded Round method that supports rounding to a specified precision. |
N/A | Sign() | Returns a value indicating the sign of the specified number. |
N/A | Sinh() | Returns the hyperbolic sine of the specified angle. |
N/A | Tanh() | Returns the hyperbolic tangent of the specified angle. |
toDegrees() | N/A | |
toRadians() | N/A |
The Java class java.util.Random and the .NET equivalent System.Random are simple classes for generating pseudorandom numbers. These classes are not suitable for high-security or cryptography applications.
The .NET System.Security.Cryptography namespace includes mechanisms for the generation of secure random numbers (SRNs).
Both Random classes act as number generators, providing a stream of random numbers in response to method calls. Instantiation of both classes is the same except when specifying a seed; java.util.Random takes a long, whereas System.Random takes an int. Once created, the .NET System.Random class cannot have its seed changed, whereas the Java class provides the setSeed method.
Java offers greater flexibility in the type of result returned from Random, including support for Boolean values and a wider selection of numeric types.
Table 8-6 summarizes the methods of the Java and .NET Random classes.
Table 8-6. A Comparison between Java and .NET Random Classes
.NET | Comments | |
---|---|---|
Constructors | ||
Random() | Random() | |
Random(long) | Random(int) | Uses the specified value as the seed for the random number generation algorithm. |
Methods | ||
nextBoolean() | N/A | |
nextBytes() | NextBytes() | |
nextDouble() | NextDouble() | |
nextFloat() | N/A | |
nextGaussian() | N/A | |
nextInt() | Next() | |
nextInt(int) | Next(int) | |
N/A | Next(int, int) | Returns a positive random integer between the specified values. |
nextLong() | N/A | |
setSeed() | N/A | .NET Random seed is set only during construction. |