Skip to main content
Engineering LibreTexts

5.5: Numeric Processing Examples

  • Page ID
    15088
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    In this section we consider several numeric programming examples. They are carefully chosen to illustrate different issues and concepts associated with processing numeric data.

    Example: Rounding to Two Decimal Places

    As an example of how to use Math class methods, let’s consider the problem of rounding numbers. When dealing with applications that involve monetary values—dollars and cents—it is often necessary to round a calculated result to two decimal places. For example, suppose a program computes the value of a certificate of deposit (CD) to be 75.19999. Before we output this result, we would want to round it to two decimal places—to 75.20. The following algorithm can be used to accomplish this:

    1. Multiply the number by 100, giving 7519.9999.
    2. Add 0.5 to the number giving 7520.4999.
    3. Drop the fractional part giving 7520
    4. Divide the result by 100, giving 75.20

    Step 3 of this algorithm can be done using the Math.floor(R) method, which rounds its real argument, R, to the largest integer not less than R (from Table 5.11). If the number to be rounded is stored in the double variable R, then the following expression will round R to two decimal places:

    R = Math.floor(R * 100.0 + 0.5) / 100.0;

    Alternatively, we could use the Math.round() method (Table 5.11). This method rounds a floating-point value to the nearest integer. For example, Math.round(65.3333) rounds to 65 and Math.round(65.6666) rounds to 66. The following expression uses it to round to two decimal places:

    R = Math.round(100.0 * R) / 100.0;

    Note that it is important here to divide by \(100.0\) and not by \(100\). Otherwise, the division will give an integer result and we’ll lose the two decimal places.

    Example: Converting Fahrenheit to Celsius

    To illustrate some of the issues that arise in using numeric data, let’s design a program that performs temperature conversions from Fahrenheit to Celsius and vice versa.

    Problem Decomposition

    This problem requires two classes, a Temperature class and a class. The Temperature class will perform the temperature conversions, and TemperatureUI will serve as the user interface (Fig. [fig-convapplobjs]).

    Class Design: Temperature

    The purpose of the Temperature class is to perform the temperature conversions. To convert a Celsius temperature to Fahrenheit or vice versa, it is not necessary to store the temperature value. Rather, a conversion method could take the Celsius (or Fahrenheit) temperature as a parameter, perform the conversion, and return the result. Therefore, the Temperature class does not need any instance variables. Note that in this respect the Temperature class resembles the Math class. Unlike OneRowNim, which stores the game’s state—the number of sticks remaining and whose turn it is—the Math and Temperature classes are stateless.

    Thus, following the design of the Math class, the Temperature class will have two public static methods: one to convert from Fahrenheit to Celsius and one to convert from Celsius to Fahrenheit. Recall that static methods are associated with the class rather than with its instances. Therefore, we needn’t instantiate a Temperature object to use these methods. Instead, we can invoke the methods through the class itself.

    The methods will use the standard conversion formulas: \(F = \frac{9}{5}C + 32\) and \(C = \frac{5}{9}(F - 32)\). Each of these methods should have a single parameter to store the temperature value that is being converted.

    Because we want to be able to handle temperatures such as 98.6, we should use real-number data for the methods’ parameters. Generally speaking, because Java represents real literals such as 98.6 as doubles, the double type is more widely used than float. Because doubles are more widely used in Java, using double wherever a floating point value is needed will cut down on the number of implicit data conversions that a program would have to perform. Therefore, each of our conversion methods should take a double parameter and return a double result. These considerations lead

    Implementation: Temperature

    The implementation of the Temperature class is shown in Figure [fig-temperature]. Note that because celsToFahr() uses the double value temp in its calculation, it uses floating-point literals (9.0, 5.0, and 32.0) in its conversion expression. This helps to reduce the reliance on Java’s built-in promotion rules, which can lead to subtle errors. For example, to the design shown in Figure [fig-p242f2], suppose we had written what looks like an equivalent expression using integer literals:

    return(9 / 5 * temp + 32); // Error: equals (temp + 32)
    public class Temperature
    {   
        public Temperature() {}
    
        public static double fahrToCels(double temp)
        {   
            return (5.0 * (temp - 32.0) / 9.0);
        }
        public static double celsToFahr(double temp)
        {   
            return (9.0 * temp / 5.0 + 32.0);
        }
    } // Temperature

    Because 9 divided by 5 gives the integer result 1, this expression is always equivalent to temp + 32, which is not the correct conversion formula. This kind of subtle semantic error can be avoided if you avoid mixing types wherever possible.

    Testing and Debugging

    The next question to be addressed is how should this program be tested? As always, you should test the program in a stepwise fashion. As each method is coded, you should test it both in isolation and in combination with the other methods, if

    Also, you should develop appropriate test data. It is not enough to just plug in any values. The values you use should test for certain potential problems. For this program, the following tests are appropriate:

    Test converting 0 degrees C to 32 degrees F.

    Test converting 100 degrees C to 212 degrees F.

    Test converting 212 degrees F to 100 degrees C.

    Test converting 32 degrees F to 0 degrees C.

    The first two tests use the celsToFahr() method to test the freezing point and boiling point temperatures, two boundary values for this problem. A boundary value is a value at the beginning or end of the range of values that a variable or calculation is meant to represent. The second pair of tests performs similar checks with the fahrToCels() method. One advantage of using these particular values is that we know what results the methods should return.

    The TemperatureUI Class

    The purpose of the TemperatureUI class is to serve as a user interface—that is, as an interface between the user and a Temperature object. It will accept a Fahrenheit or Celsius temperature from the user, pass it to one of the public methods of the Temperature object for conversion, and display the result that is returned.

    As we discussed in Chapter 4, the user interface can take various forms, ranging from a command-line interface to a graphical interface. Figure [fig-tempcmdline] shows a design for the user interface based on the command-line interface developed in Chapter 4. The TemperatureUI uses a KeyboardReader to handle interaction with the user and uses static methods in the Temperature class to perform the temperature conversions.

    Following the design in Figure [fig-tempcmdline], implement the TemperatureUI class and use it to test the methods in Temperature class. The run() method should use an input-process-output algorithm: Prompt the user for input, perform the necessary processing, and output the result. Note that because Temperature’s conversion methods are class methods, you do not need to instantiate a Temperature object in this project. You can invoke the conversion methods directly through the Temperature class:

    double fahr = Temperature.celsToFahr(98.6);

    Following the design for the GUI developed in Chapter 4, implement a GUI to use for testing the Temperature class. The GUI should have the layout shown in Figure [fig-convapplgui].

    Example: Using Class Constants

    As we noted in Chapter [chapter-intro], in addition to instance variables, which are associated with instances (objects) of a class, Java also allows class variables, which are associated with the class itself. One of the most common uses of such variables is to define named constants to replace literal values. A named constant is a variable that cannot be changed once it has been given an initial value. In this section, we use our running example, OneRowNim, to illustrate using class constants.

    Recall that methods and variables that are associated with a class must be declared with the static modifier. If a variable is declared static, there is exactly one copy of that variable created no matter how many times its class is instantiated. To turn a variable into a constant, it must be declared with the final modifier. Thus, the following would be examples of a class constants, constant values that are associated with the class rather than with its instances:

    public static final int PLAYER_ONE = 1;
    public static final int PLAYER_TWO = 2;
    public static final int MAX_PICKUP = 3;
    public static final int MAX_STICKS = 7;

    The final modifier indicates that the value of a variable cannot be changed. When final is used in a variable declaration, the variable must be assigned an initial value. After a final variable is properly declared, it is a syntax error to attempt to try to change its value. For example, given the preceding declarations, the following assignment statement would cause a compiler error:

    PLAYER_ONE = 5;// Syntax error; PLAYER_ONE is a constant

    Note how we use uppercase letters and underscore characters (_) in the names of constants. This is a convention that professional Java programmers follow, and its purpose is to make it easy to distinguish the constants from the variables in a program. This makes the program easier to read and understand.

    Another way that named constants improve the readability of a program is by replacing the reliance on literal values. For example, for the OneRowNim class, compare the following two if conditions:

    if (num < 1 || num > 3 || num > nSticks) ...
    if (num < 1 || num > MAX_PICKUP || num > nSticks) ...

    Clearly, the second condition is easier to read and understand. In the first condition, we have no good idea what the literal value 3 represents. In the second, we know that MAX_PICKUP represents the most sticks a player can pick up.

    Thus, to make OneRowNim more readable, we should replace all occurrences of the literal value 3 with the constant MAX_PICKUP. This same principle would apply to some of the other literal values in the program. Thus, instead of using 1 and 2 to represent the two players, we could use PLAYER_ONE and PLAYER_TWO to make methods such as the following easier to read and understand:

    public int getPlayer()
    {   if (onePlaysNext) 
             return PLAYER_ONE;
        else return PLAYER_TWO;
    } // getPlayer()

    Another advantage of named constants (over literals) is that their use makes the program easier to modify and maintain. For example, suppose that we decide to change OneRowNim so that the maximum number of sticks that can be picked up is 4 instead of 3. If we used literal values, we would have to change all occurrences of 4 that were used to represent the maximum pick up. If we used a named constant, we need only change its declaration to:

    public static final int MAX_PICKUP = 4;

    So far, all of the examples we have presented show why named constants (but not necessarily class constants) are useful. Not all constants are class constants. That is, not all constants are declared static. However, the idea of associating constants with a class makes good sense. In addition to saving memory resources, by creating just a single copy of the constant, constants such as MAX_STICKS and PLAYER_ONE make more conceptual sense to associate with the class itself rather than with any particular OneRowNim instance.

    Class constants are used extensively in the Java class library. For example, as we saw in Chapter 2, Java’s various built-in colors are represented as constants of the java.awt.Color class— Color.blue and Color.red. Similarly, java.awt.Label uses int constants to specify how a label’s text should be aligned: Label.CENTER.

    Another advantage of class constants is that they can be used before instances of the class exist. For example, a class constant (as opposed to an instance constant) may be used during object instantiation:

    OneRowNim game = new OneRowNim(OneRowNim.MAX_STICKS);

    Note how we use the name of the class to refer to the class constant. Of course, MAX_STICKS has to be a public variable in order to be accessible outside the class. To use MAX_STICKS as a constructor argument it has to be a class constant because at this point in the program there are no instances of OneRowNim. A new version of OneRowNim that uses class constants is shown in Figure [fig-constnim].

    It is important to note that Java also allows class constants to be referenced through an instance of the class. Thus, once we have instantiated game, we can refer to MAX_STICKS with either OneRowNim.MAX_STICKS or game.MAX_STICKS.

    public class OneRowNim
    {   public static final int PLAYER_ONE = 1;
        public static final int PLAYER_TWO = 2;
        public static final int MAX_PICKUP = 3;
        public static final int MAX_STICKS = 11;
        public static final boolean GAME_OVER = false;
    
         private int nSticks = MAX_STICKS;
         private boolean onePlaysNext = true;
    
         public OneRowNim()
         {
         } //OneRowNim() constructor1
         public OneRowNim(int sticks)
         {   nSticks = sticks;
         }  // OneRowNim() constructor2
         public OneRowNim(int sticks, int starter)
         {   nSticks = sticks;
             onePlaysNext = (starter == PLAYER_ONE);
         }  // OneRowNim() constructor3
         public boolean takeSticks(int num)
         {   if (num < 1 || num > MAX_PICKUP || num > nSticks) 
                 return false;                // Error
             else                             // Valid move
             {   nSticks = nSticks - num;
                 onePlaysNext = !onePlaysNext;
                 return true;
             } //else
         }//takeSticks()
         public int getSticks()
         {   return nSticks;
         } //getSticks()
         public int getPlayer()
         {   if (onePlaysNext) 
                 return PLAYER_ONE;
             else return PLAYER_TWO;
         } //getPlayer()
         public boolean gameOver()
         {   return (nSticks <= 0);
         } // gameOver()
         public int getWinner()
         {   if (nSticks < 1) 
                 return getPlayer();
             else return 0;         // Game is not over
         } // getWinner()
         public String report()
         {   return ("Number of sticks left: " + getSticks()
              + "\nNext turn by player " + getPlayer() + "\n");
         }   // report()
    } // OneRowNim class

    Implement a command-line interface class named KBTestOneRowNim, that uses our new version of OneRowNim. Make use of the MAX_STICKS and MAX_PICKUP in the user interface.


    This page titled 5.5: Numeric Processing Examples is shared under a CC BY 4.0 license and was authored, remixed, and/or curated by Ralph Morelli & Ralph Wade via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.