HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaModerate

Conversion Between Units

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
conversionbetweenunits

Problem

I have created a java program that should take a list of conversions and be able to convert one unit to another. My main goals are maintainability and readability, but speed would be nice as well.

Example input.in

1 meters = 100 centimeters
10 millimeters = 1 centimeters
1 meters = 0.001 kilometers
1 kilometers = 1000000000000 nanometers
10 dekameters = 1 hectometers


Example input to the program:

Enter the first unit: meters
Enter the amount of the first unit: 12
Enter the unit to convert to: nanometers


Example output: 12000000000.0 or 1.2e10

ConversionsMain.java

```
package conversions;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import conversions.ConversionExceptions.*;

public class ConversionsMain {
private static BufferedReader inputReader;
private static Scanner userInput;
public static final String INPUT_FILE_NAME = "input.in";
// Used to validate numbers
// Should match: 1, 20, 57, 88.6, 88.60, 0.75
// Shouldn't match: not_number, .75, 9., 088
private static final String numberRegex = "([1-9][0-9](?:\\.[0-9])?|0\\.[0-9]*)",
// Validates a unit of measurement
unitRegex = "([a-zA-Z]+)";
// An entire line consists of number unit = number unit e.g. 100centimeters=1meter
private static final Pattern tokenPattern = Pattern.compile(
"^" + numberRegex + unitRegex + "=" + numberRegex + unitRegex + "$"
);
private ConversionMap conversionMap = new ConversionMap();
public static void main(String[] args) {
ConversionsMain main = new ConversionsMain();
try {
inputReader = new BufferedReader(new InputStreamReader(new FileInputStream(INPUT_FILE_NAME)));
userInput = new Scanner(System.in);
main.start();
} catch (IOException

Solution

Don't use double. Binary floating-point cannot exactly represent most decimal numbers, so you would be implicitly rounding a lot of your numbers before you even start. Read What Every Computer Scientist Should Know About Floating-Point Arithmetic for a more in-depth explanation.

A slightly better choice would be BigDecimal, which can represent any arbitrary decimal number. However, it can't represent fractions like 1/3 exactly, so while the input numbers could be represented exactly, you'd still have to round after every division.

For this program, the best numeric representation would be rational numbers. You could use Apache Commons' BigFraction or roll your own. This way you can retain exact results until you need to print output, at which point you can decide on an output format or let the user choose one.

In ConversionsMain , the static members inputReader and userInput and the member variable conversionMap can be made local within main(). This makes it easier to understand each method on ConversionsMain, since each method operates only on its arguments and doesn't refer to any external state. It also makes refactoring easier.

start() does two things. It should be split into two methods. The code for reading the list of conversions can be moved into a different method or even into a factory method on ConversionMap. There should also be separate try/catch blocks for the two methods so you can print relevant error information on exceptions like IOException.

You can eliminate the file-closing boilerplate by using try-with-resources if you're on Java 7 or above.

Here's a version of ConversionsMain incorporating the changes I suggested (except for BigFraction):

```
public class ConversionsMain {
public static final String INPUT_FILE_NAME = "input.in";
// Used to validate numbers
// Should match: 1, 20, 57, 88.6, 88.60, 0.75
// Shouldn't match: not_number, .75, 9., 088
public static final String numberRegex = "([1-9][0-9](?:\\.[0-9])?|0\\.[0-9]*)";
public static final String // Validates a unit of measurement
unitRegex = "([a-zA-Z]+)";
// An entire line consists of number unit = number unit e.g. 100centimeters=1meter
private static final Pattern tokenPattern = Pattern.compile(
"^" + ConversionsMain.numberRegex + ConversionsMain.unitRegex + "=" + ConversionsMain.numberRegex + ConversionsMain.unitRegex + "$"
);

public static void main(String[] args) {
ConversionMap conversionMap;
try {
conversionMap = readConversionMapFromFile(INPUT_FILE_NAME);
} catch (IOException e) {
System.out.println("An error occured with the file.\nException details:");
e.printStackTrace();
return;
} catch (MalformedLineException | NumberFormatException e) {
System.out.println("The line does not conform to the standard.\nException details:");
e.printStackTrace();
return;
}

try(final Scanner userInput = new Scanner(System.in)) {
start(conversionMap, userInput);
} catch (IOException e) {
System.out.println("An error occured with the console.\nException details:");
e.printStackTrace();
} catch (MalformedLineException | NumberFormatException e) {
System.out.println("The line does not conform to the standard.\nException details:");
e.printStackTrace();
} catch (NoSuchUnitException e) {
System.out.println("The input is not valid because one more more units was missing.\nDetails:");
e.printStackTrace();
} catch (NoPathBetweenUnits e) {
System.out.println("It is not possible to convert between the two units.");
e.printStackTrace();
}
}

public static void start(ConversionMap conversionMap, Scanner userInput) throws MalformedLineException, IOException, NumberFormatException, NoSuchUnitException, NoPathBetweenUnits {
// Convert
System.out.println("Enter the first unit");
String unit1 = userInput.next(unitRegex);
System.out.println("Enter the amount of the first unit");
double unit1Amount = userInput.nextDouble();
System.out.println("Enter the unit to convert to");
String unit2 = userInput.next(unitRegex);
System.out.println(conversionMap.convert(unit1Amount, unit1, unit2));
}

public static ConversionMap readConversionMapFromFile(final String filename) throws FileNotFoundException, IOException, NumberFormatException, MalformedLineException {
try(final BufferedReader inputReader = new BufferedReader(new InputStreamReader(new FileInputStream(filename)))) {
return readConversionMapFromBufferedReader(inputReader);
}
}

public static ConversionMap readConversionMapFromBufferedReader(BufferedReader inputReader) throws NumberFormatException, MalformedLineException, IOException {

Code Snippets

public class ConversionsMain {
    public static final String INPUT_FILE_NAME = "input.in";
    // Used to validate numbers
    // Should match: 1, 20, 57, 88.6, 88.60, 0.75
    // Shouldn't match: not_number, .75, 9., 088
    public static final String numberRegex = "([1-9][0-9]*(?:\\.[0-9]*)?|0\\.[0-9]*)";
    public static final String // Validates a unit of measurement
     unitRegex = "([a-zA-Z]+)";
    // An entire line consists of number unit = number unit e.g. 100centimeters=1meter
    private static final Pattern tokenPattern = Pattern.compile(
            "^" + ConversionsMain.numberRegex + ConversionsMain.unitRegex + "=" + ConversionsMain.numberRegex + ConversionsMain.unitRegex + "$"
        );

    public static void main(String[] args) {
        ConversionMap conversionMap;
        try {
            conversionMap = readConversionMapFromFile(INPUT_FILE_NAME);
        } catch (IOException e) {
            System.out.println("An error occured with the file.\nException details:");
            e.printStackTrace();
            return;
        } catch (MalformedLineException | NumberFormatException e) {
            System.out.println("The line does not conform to the standard.\nException details:");
            e.printStackTrace();
            return;
        }

        try(final Scanner userInput = new Scanner(System.in)) {
            start(conversionMap, userInput);
        } catch (IOException e) {
            System.out.println("An error occured with the console.\nException details:");
            e.printStackTrace();
        } catch (MalformedLineException | NumberFormatException e) {
            System.out.println("The line does not conform to the standard.\nException details:");
            e.printStackTrace();
        } catch (NoSuchUnitException e) {
            System.out.println("The input is not valid because one more more units was missing.\nDetails:");
            e.printStackTrace();
        } catch (NoPathBetweenUnits e) {
            System.out.println("It is not possible to convert between the two units.");
            e.printStackTrace();
        }
    }

    public static void start(ConversionMap conversionMap, Scanner userInput) throws MalformedLineException, IOException, NumberFormatException, NoSuchUnitException, NoPathBetweenUnits {
        // Convert
        System.out.println("Enter the first unit");
        String unit1 = userInput.next(unitRegex);
        System.out.println("Enter the amount of the first unit");
        double unit1Amount = userInput.nextDouble();
        System.out.println("Enter the unit to convert to");
        String unit2 = userInput.next(unitRegex);
        System.out.println(conversionMap.convert(unit1Amount, unit1, unit2));
    }

    public static ConversionMap readConversionMapFromFile(final String filename) throws FileNotFoundException, IOException, NumberFormatException, MalformedLineException {
        try(final BufferedReader inputReader = new BufferedReader(new InputStreamReader(n

Context

StackExchange Code Review Q#63463, answer score: 10

Revisions (0)

No revisions yet.