2. When the app executes, another compiler (known as the just-in-time compiler
4.11 Floating-Point Numbers and Type decimal
In our next app, we depart temporarily from ourGradeBookcase study to declare a class calledAccountthat maintains a bank account’s balance. Most account balances are not whole numbers (such as 0, –22 and 1024), rather they’re numbers that include a decimal point, such as 99.99 or –20.15. For this reason, classAccountrepresents the account bal- ance as a real number. C# provides three simple types for storing real numbers—float,
double, anddecimal. Typesfloatanddoubleare calledfloating-pointtypes. The pri- mary difference between them anddecimalis thatdecimalvariables store a limited range of real numbersprecisely, whereas floating-point variables store onlyapproximationsof real numbers, but across a much greater range of values. Also,doublevariables can store num- bers with larger magnitude and finer detail (i.e., more digits to the right of the decimal point—also known as the number’sprecision) than floatvariables. A key use of type
decimalis representingmonetary amounts.
Real-Number Precision and Storage Requirements
Variables of typefloatrepresentsingle-precision floating-point numbersand haveseven significant digits. Variables of type double represent double-precision floating-point numbers. These require twice as much storage asfloatvariables and provide 15–16 sig- nificant digits—approximately double the precision offloatvariables. Variables of type
decimalrequire twice as much storage asdoublevariables and provide 28–29 significant digits. In some apps, even variables of typedoubleanddecimalwill be inadequate—such apps are beyond the scope of this book.
Most programmers represent floating-point numbers with typedouble. In fact, C#
treats allreal numbers you type in an app’s source code (such as7.33 and0.0975) as
doublevalues by default. Such values in the source code are known asfloating-point lit- erals. To type adecimal literal, you must type the letter “M” or “m” (which stands for
“money”) at the end of a real number (for example,7.33Mis adecimalliteral rather than a double). Integer literals are implicitly converted into typefloat,doubleor decimal when they’re assigned to a variable of one of these types. See Appendix B for the ranges of values forfloats,doubles,decimalsand all the other simple types.
Although floating-point numbers are not always 100% precise, they have numerous applications. For example, when we speak of a “normal” body temperature of 98.6, we do not need to be precise to a large number of digits. When we read the temperature on a thermometer as 98.6, it may actually be 98.5999473210643. Calling this number simply 98.6 is fine for most applications involving body temperatures. Due to the impre- cise nature of floating-point numbers, typedecimalis preferred over the floating-point types whenever the calculations need to be exact, as with monetary calculations. In cases where approximation is enough,doubleis preferred over typefloatbecausedoublevari- ables can represent floating-point numbers more accurately. For this reason, we use type
decimalthroughout the book for monetary amounts and typedoublefor other real num- bers.
Real numbers also arise as a result of division. In conventional arithmetic, for example, when we divide 10 by 3, the result is 3.3333333…, with the sequence of 3s repeatinginfinitely. The computer allocates only a fixed amount of space to hold such a value, so clearly the stored floating-point value can be only an approximation.
4.11 Floating-Point Numbers and Typedecimal 129
AccountClass with an Instance Variable of Typedecimal
Our next app (Figs. 4.15–4.16) contains a simple class namedAccount(Fig. 4.15) that maintains the balance of a bank account. A typical bank servicesmanyaccounts, each with itsown balance, so line 7 declares an instance variable namedbalanceof typedecimal. Variablebalanceis aninstance variablebecause it’s declaredinthe body of the class (lines 6–36) butoutsidethe class’s method and property declarations (lines 10–13, 16–19 and 22–35). Every instance (i.e., object) of classAccountcontains its own copy ofbalance.
Common Programming Error 4.3
Using floating-point numbers in a manner that assumes they’re represented precisely can lead to logic errors.
1 // Fig. 4.15: Account.cs
2 // Account class with a constructor to 3 // initialize instance variable balance.
4
5 public class Account 6 {
7 8
9 // constructor
10 public Account( initialBalance )
11 {
12 Balance = initialBalance; // set balance using property 13 } // end Account constructor
14
15 // credit (add) an amount to the account 16 public void Credit( amount )
17 {
18 Balance = Balance + amount; // add amount to balance 19 } // end method Credit
20
21 // a property to get and set the account balance
22 public Balance
23 {
24 get
25 {
26 return balance;
27 } // end get
28 set
29 {
30 31 32 33
34 } // end set
35 } // end property Balance 36 } // end class Account
Fig. 4.15 | Accountclass with a constructor to initialize instance variablebalance.
private decimal balance; // instance variable that stores the balance
decimal
decimal
decimal
// validate that value is greater than or equal to 0;
// if it is not, balance is left unchanged if ( value >= 0 )
balance = value;
AccountClass Constructor
ClassAccountcontains a constructor, a method, and a property. Since it’s common for someone opening an account to place money in the account immediately, the constructor (lines 10–13) receives a parameterinitialBalanceof typedecimalthat represents the ac- count’s starting balance. Line 12 assignsinitialBalanceto the propertyBalance, invok- ingBalance’ssetaccessor to initialize the instance variablebalance.
AccountMethodCredit
MethodCredit(lines 16–19) doesn’t return data when it completes its task, so its return type isvoid. The method receives one parameter namedamount—adecimalvalue that’s added to the propertyBalance. Line 18 uses both thegetandsetaccessors ofBalance. The expressionBalance + amountinvokes propertyBalance’sgetaccessor to obtain the current value of instance variablebalance, then addsamountto it. We then assign the re- sult to instance variablebalance by invoking theBalanceproperty’ssetaccessor (thus replacing the priorbalancevalue).
AccountPropertyBalance
PropertyBalance(lines 22–35) provides agetaccessor, which allows clients of the class (i.e., other classes that use this class) to obtain the value of a particularAccountobject’s
balance. The property has typedecimal(line 22).Balancealso provides an enhancedset accessor.
In Section 4.5, we introduced properties whosesetaccessors allow clients of a class to modify the value of aprivateinstance variable. In Fig. 4.7, classGradeBookdefines propertyCourseName’ssetaccessor to assign the value received in its parametervalueto instance variablecourseName (line 19). ThisCourseName property doesnotensure that
courseNamecontains onlyvaliddata.
The app of Figs. 4.15–4.16 enhances the setaccessor of class Account’s property
Balanceto perform this validation (also known asvalidity checking). Line 32 (Fig. 4.15) ensures that value is nonnegative. If the value is greater than or equal to0, the amount stored invalueis assigned to instance variablebalancein line 33. Otherwise,balanceis left unchanged.
AccountTestClass to Use ClassAccount
ClassAccountTest (Fig. 4.16) creates twoAccount objects (lines 10–11) and initializes them respectively with50.00Mand-7.53M(the decimal literals representing the real num- bers 50.00 and-7.53). The Accountconstructor (lines 10–13 of Fig. 4.15) references propertyBalanceto initializebalance. In previous examples, the benefit of referencing the property in the constructor was not evident. Now, however, the constructor takes ad- vantage of the validation provided by thesetaccessor of theBalanceproperty. The con- structor simply assigns a value to Balance rather than duplicating the set accessor’s validation code. When line 11 of Fig. 4.16 passes an initial balance of-7.53to theAc-
countconstructor, the constructor passes this value to thesetaccessor of propertyBal-
ance, where the actual initialization occurs. This value is less than0, so thesetaccessor doesnotmodifybalance, leaving this instance variable with its default value of0.
4.11 Floating-Point Numbers and Typedecimal 131
1 // Fig. 4.16: AccountTest.cs
2 // Create and manipulate Account objects.
3 using System;
4
5 public class AccountTest 6 {
7 // Main method begins execution of C# app 8 public static void Main( string[] args )
9 {
10 Account account1 = new Account( 50.00M ); // create Account object 11 Account account2 = new Account( -7.53M ); // create Account object 12
13 // display initial balance of each object using a property 14 Console.WriteLine( "account1 balance: ",
15 account1.Balance ); // display Balance property 16 Console.WriteLine( "account2 balance: \n", 17 account2.Balance ); // display Balance property 18
19 20
21 // prompt and obtain user input
22 Console.Write( "Enter deposit amount for account1: " );
23
24 Console.WriteLine( "adding to account1 balance\n", 25 depositAmount );
26 account1.Credit( depositAmount ); // add to account1 balance 27
28 // display balances
29 Console.WriteLine( "account1 balance: ", 30 account1.Balance );
31 Console.WriteLine( "account2 balance: \n", 32 account2.Balance );
33
34 // prompt and obtain user input
35 Console.Write( "Enter deposit amount for account2: " );
36
37 Console.WriteLine( "adding to account2 balance\n", 38 depositAmount );
39 account2.Credit( depositAmount ); // add to account2 balance 40
41 // display balances
42 Console.WriteLine( "account1 balance: ", account1.Balance );
43 Console.WriteLine( "account2 balance: ", account2.Balance );
44 } // end Main
45 } // end class AccountTest
account1 balance: $50.00 account2 balance: $0.00
Enter deposit amount for account1: 49.99 adding $49.99 to account1 balance
Fig. 4.16 | Create and manipulateAccountobjects. (Part 1 of 2.)
{0:C}
{0:C}
decimal depositAmount; // deposit amount read from user
depositAmount = Convert.ToDecimal( Console.ReadLine() );
{0:C}
{0:C}
{0:C}
depositAmount = Convert.ToDecimal( Console.ReadLine() );
{0:C}
{0:C}
{0:C}
Lines 14–17 in Fig. 4.16 output the balance in eachAccountby using theAccount’s
Balanceproperty. WhenBalanceis used foraccount1(line 15), the value ofaccount1’s balance is returned by thegetaccessor in line 26 of Fig. 4.15 and displayed by theCon-
sole.WriteLinestatement (Fig. 4.16, lines 14–15). Similarly, when propertyBalanceis called foraccount2from line 17, the value of theaccount2’s balance is returned from line 26 of Fig. 4.15 and displayed by theConsole.WriteLinestatement (Fig. 4.16, lines 16–
17). The balance ofaccount2is0because the constructor ensured that the account could not begin with a negative balance. The value is output byWriteLinewith the format item
{0:C}, which formats the account balance as a monetary amount. The:after the0indi- cates that the next character represents aformat specifier, and theCformat specifier after the:specifies a monetary amount (Cis for currency). The cultural settings on the user’s machine determine the format for displaying monetary amounts. For example, in the United States,50displays as$50.00. In Germany,50displays as50,00€. [Note:Change the Command Prompt’s font to Lucida Console for the€symbol to display correctly.]
Figure 4.17 lists a few other format specifiers in addition toC.
Line 19 declares local variabledepositAmountto store each deposit amount entered by the user. Unlike the instance variable balance in class Account, the local variable
account1 balance: $99.99 account2 balance: $0.00
Enter deposit amount for account2: 123.21 adding $123.21 to account2 balance account1 balance: $99.99
account2 balance: $123.21
Format
specifier Description
Corc Formats the string as currency. Displays an appropriate currency symbol ($in the U.S.) next to the number. Separates digits with an appropriateseparator character (comma in the U.S.) and sets the number of decimal places to two by default.
Dord Formats the string as a whole number (integer types only). Displays number as an integer.
Norn Formats the string with a thousands separator and a default of two decimal places.
Eore Formats the number using scientific notation with a default of six decimal places.
Forf Formats the string with a fixed number of decimal places (two by default).
Gorg Formats the number normally with decimal places or using scientific notation, depending on context. If a format item does not contain a format specifier, format
Gis assumed implicitly.
Xorx Formats the string as hexadecimal.
Fig. 4.17 | stringformat specifiers.
Fig. 4.16 | Create and manipulateAccountobjects. (Part 2 of 2.)
4.11 Floating-Point Numbers and Typedecimal 133
depositAmountinMainisnotinitialized to0by default. Also,a local variable can be used only in the method in which it’s declared. However, this variable does not need to be initial- ized here because its value will be determined by the user’s input.The compiler does not allow a local variable’s value to be read until it’s initialized.
Line 22 prompts the user to enter a deposit amount foraccount1. Line 23 obtains the input from the user by calling theConsole class’s ReadLinemethod, then passing the
stringentered by the user to theConvertclass’sToDecimalmethod, which returns the
decimalvalue in thisstring. Lines 24–25 display the deposit amount. Line 26 calls object
account1’sCreditmethod and suppliesdepositAmountas the method’s argument. When the method is called, the argument’s value is assigned to parameter amountof method
Credit(lines 16–19 of Fig. 4.15), then methodCreditadds that value to the balance
(line 18 of Fig. 4.15). Lines 29–32 (Fig. 4.16) output the balances of bothAccounts again to show thatonlyaccount1’s balance changed.
Line 35 prompts the user to enter a deposit amount foraccount2. Line 36 obtains the input from the user by calling methodConsole.ReadLine, and passing the return value to theConvertclass’sToDecimalmethod. Lines 37–38 display the deposit amount. Line 39 calls objectaccount2’sCreditmethod and suppliesdepositAmountas the method’s argu- ment, then methodCreditadds that value to the balance. Finally, lines 42–43 output the balances of bothAccounts again to show thatonlyaccount2’s balance changed.
setandgetAccessors with Different Access Modifiers
By default, thegetandsetaccessors of a property have thesameaccess as the property—
for example, for apublicproperty, the accessors arepublic. It’s possible to declare the
getandsetaccessors withdifferentaccess modifiers. In this case, one of the accessors must implicitly have thesameaccess as the property and the other must be declared with amore restrictiveaccess modifier than the property. For example, in a publicproperty, theget accessor might bepublicand thesetaccessor might beprivate. We demonstrate this feature in Section 10.7.
UML Class Diagram for ClassAccount
The UML class diagram in Fig. 4.18 models classAccount of Fig. 4.15. The diagram models the Balance property as a UML attribute of type decimal (because the corre- sponding C# property had typedecimal). The diagram models classAccount’s construc- tor with a parameterinitialBalanceof typedecimalin the third compartment of the class. The diagram models operationCreditin the third compartment with anamountpa- rameter of typedecimal(because the corresponding method has anamountparameter of C# typedecimal).
Error-Prevention Tip 4.2
The benefits of data integrity are not automatic simply because instance variables are madeprivate—you must provide appropriate validity checking and report the errors.
Error-Prevention Tip 4.3
setaccessors that set the values ofprivatedata should verify that the intended new val- ues are proper; if they’re not, thesetaccessors should leave the instance variables un- changed and indicate an error. We demonstrate how to indicate errors in Chapter 10.