2
\$\begingroup\$

I'm trying to parse Double from String in Java. The string input is not necessarily a number, any non number input should be treated as 0.

Given below is a solution using try-catch, where a Array of string is converted into a Array of Double:

public static void main(String[] args) {
    // Array of strings to be parsed as double. 
    // In case of NaN I have to assign it a value 0.
    ArrayList<String> dirtyList = new ArrayList();
    dirtyList.add("Null");
    dirtyList.add("1.24");
    dirtyList.add("0");
    dirtyList.add("0asd");
    ArrayList<Double> doubleList = new ArrayList();
    for ( String item : dirtyList) {
        doubleList.add(parseDouble(item));
    }
    System.out.println(doublelist);
}

// Function to clean data
static Double parseDouble(String x) {
    Double a;
    try {
        a = Double.parseDouble(x);
    } catch (Exception ParseException) {
        a = (double) 0;
    }
    return a;
}

Output:

[0.0, 1.24, 0.0, 0.0]

Is it okay to exploit try-catch in situations like this? Is there a better approach?

\$\endgroup\$
2
  • 1
    \$\begingroup\$ I think that it is Java way because primitive types cannot be null or Option whereas parse may end up with error you have no other option than to handle exception. \$\endgroup\$
    – Val
    Aug 4, 2015 at 6:43
  • \$\begingroup\$ The edit you have made to the code in this question has invalidated the answer you currently have. The code edit thus needs to be rolled back. I have done that (just reverted the changes to parseDouble) \$\endgroup\$
    – rolfl
    Aug 7, 2015 at 11:58

2 Answers 2

3
\$\begingroup\$

In Python, the best practice is to use exceptions for many more situations than is best practice in Java. In Python, you shoot first, ask questions later .... you seek forgiveness, not permission.... you ... {add cheesy analogy here....}

In Java, Exceptions are expensive. When an exception happens, the virtual machine needs to backtrack the call stack, often synchronizing on critical aspects of the runtime, perhaps bringing the entire application in to a locked state, while the exception is built. The Java compiled code is often inlined, and optimized, and the call stack for the actual optimized code has to be translated back to the source code, etc.

If you expect your input data to have invalid double input, then it is not an exceptional case to have invalid data, is it. In Java, you seek permission before having to seek forgiveness... so, you validate the data before doing the hard work.

The documentation for Double.valueOf(String) even goes so far as to show you exactly how to validate an input String before parsing it... using a regular expression, and shows how to use it too.

So, let's compare two ways of parsing the values.... the forgiveness way:

private static final double parseForgiveness(String value) {
    try {
        return Double.parseDouble(value);
    } catch (NumberFormatException nfe) {
        return 0.0;
    }
}

(Note, there's something important in there, I use double and not Double. Using primitives in Java when you can is almost always a better idea for performance than using the full Object version, like Double).

So, the above system begs forgiveness when the data is broken.... what about prevalidating the number...? The code is basically copied from the Javadoc:

private static final double parsePermission(String value) {
    if (DOUBLE_RE.matcher(value).matches()) {
        return Double.parseDouble(value);
    }
    return 0.0;
}

where the DOUBLE_RE is defined as:

private static final String Digits     = "(\\p{Digit}+)";
private static final String HexDigits  = "(\\p{XDigit}+)";
// an exponent is 'e' or 'E' followed by an optionally
// signed decimal integer.
private static final String Exp        = "[eE][+-]?"+Digits;
private static final String fpRegex    =
    ("[\\x00-\\x20]*"+  // Optional leading "whitespace"
     "[+-]?(" + // Optional sign character
     "NaN|" +           // "NaN" string
     "Infinity|" +      // "Infinity" string

     // A decimal floating-point string representing a finite positive
     // number without a leading sign has at most five basic pieces:
     // Digits . Digits ExponentPart FloatTypeSuffix
     //
     // Since this method allows integer-only strings as input
     // in addition to strings of floating-point literals, the
     // two sub-patterns below are simplifications of the grammar
     // productions from section 3.10.2 of
     // The Java Language Specification.

     // Digits ._opt Digits_opt ExponentPart_opt FloatTypeSuffix_opt
     "((("+Digits+"(\\.)?("+Digits+"?)("+Exp+")?)|"+

     // . Digits ExponentPart_opt FloatTypeSuffix_opt
     "(\\.("+Digits+")("+Exp+")?)|"+

     // Hexadecimal strings
     "((" +
      // 0[xX] HexDigits ._opt BinaryExponent FloatTypeSuffix_opt
      "(0[xX]" + HexDigits + "(\\.)?)|" +

      // 0[xX] HexDigits_opt . HexDigits BinaryExponent FloatTypeSuffix_opt
      "(0[xX]" + HexDigits + "?(\\.)" + HexDigits + ")" +

      ")[pP][+-]?" + Digits + "))" +
     "[fFdD]?))" +
     "[\\x00-\\x20]*");// Optional trailing "whitespace"

private static final Pattern DOUBLE_RE = Pattern.compile(fpRegex);

Note that a Pattern is compiled as a single static, and then used to match the String before actually parsing it. That seems like a lot of work, but consider a situation when about half the values are invalid in a String sequence.... how does it affect the performance?

I wrote a UBench tool a little while ago to test performance of these sorts of exercises. The first thing I do is write a function to generate an input array of 50% valid values, and 50% invalid. Here's that code:

private static final char[] alpha = " `1234567890-=~!@#$%^&*()_+qwertyuiop[]\\QWERTYUIOP{}|asdfghjkl;'ASDFGHJKL:\"zxcvbnm,./ZXCVBNM<>?".toCharArray();

private static final String wordRand(Random rand) {
    int len = rand.nextInt(10) + 3;
    char[] chars = new char[len];
    IntStream.range(0, len).forEach(i -> chars[i] = alpha[rand.nextInt(alpha.length)]);
    return new String(chars);
}

private static String[] buildInput(int size) {
    List<String> values = new ArrayList<>();
    Random rand = new Random(size);
    for (int i = 0; i < size; i++) {
        values.add(Double.toString(rand.nextDouble() * i));
        values.add(wordRand(rand));
    }
    return values.toArray(new String[values.size()]);
}

That buildInput method will return an array of Strings where half the values are guaranteed valid float Strings, and the other half are almost certainly not valid (thought it's possible they may be). Then, I run it through a comparative benchmark using the following code:

private static final double[] parseAll(String[] values, ToDoubleFunction<String> parser) {
    return Stream.of(values).mapToDouble(parser).toArray();
}

public static void main(String[] args) {
    UUtils.setStandaloneLogging(Level.INFO);
    String[] values = buildInput(1000);
    UBench bench = new UBench("Parsing");
    bench.addTask("Permission", () -> parseAll(values, DoubleParser::parsePermission));
    bench.addTask("Forgiveness", () -> parseAll(values, DoubleParser::parseForgiveness));
    bench.press(1000).report();
}

That's complicated, I guess, but really it sets up two tests, one which parses 2000 input values using the forgiveness method, the other test uses the permission method. It runs each test 1000 times, and then takes some measurements of each run, and reports on it. The report, on my computer, looks like:

INFO   2015-08-07 12:13:03.058 net.tuis.ubench.UBench(<init>): Creating UBench for suite Parsing
INFO   2015-08-07 12:13:06.860 net.tuis.ubench.UBench(press): UBench suite Parsing: completed benchmarking all tasks using mode INTERLEAVED in 3785.000ms
Task Parsing -> Permission: (Unit: MILLISECONDS)
  Count    :     1000      Average  :   1.1980
  Fastest  :   1.0339      Slowest  :  26.8094
  95Pctile :   1.3693      99Pctile :   2.9096
  TimeBlock : 1.702 1.265 1.143 1.176 1.073 1.086 1.168 1.194 1.076 1.096
  Histogram :   977    17     5     0     1

Task Parsing -> Forgiveness: (Unit: MILLISECONDS)
  Count    :     1000      Average  :   2.5568
  Fastest  :   2.3117      Slowest  :  12.5496
  95Pctile :   2.8740      99Pctile :   5.5604
  TimeBlock : 3.054 2.539 2.522 2.645 2.411 2.471 2.470 2.591 2.419 2.446
  Histogram :   978    20     2

What that means, is that the average time to parse 2000 values using the regex is 1.20 milliseconds, and using the exception system is 2.56 milliseconds - more than twice as slow.

Additionally the fastest parse is 1.03 milliseconds, vs. 2.31 - again more than twice as slow.

Bottom line is that in Java (unlike Python), you should not use exceptions to handle data validation when you expect data to regularly be invalid....

\$\endgroup\$
0
-1
\$\begingroup\$

This approach is totally fine. :) Just a pointer for parseDouble(): you should return the results immediately, instead of a single return at the end. The benefits of this are more compact code and short-circuiting of the logic.

private static Double parseDouble(String x) {
    try {
        // use valueOf to return a wrapper object instead
        return Double.valueOf(x);
    } catch (NumberFormatException e) {
        return Double.valueOf(0);
    }
}

You should be catching the stricter NumberFormatException rather than the general Exception, and use camelCase for the variable name (furthermore, ParseException sounds like the text-formatting-related exception class name).

If you are on Java 8, then you can alternatively treat this as a mapping of Stream<String> to DoubleStream using the same method as the ToDoubleFunction lambda implementation, e.g.:

double[] result = Stream.of("Null", "1.24", "0", "0asd")
                        .mapToDouble(YourClass::parseDouble).toArray();
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.