--- title: SumHex weight: 6 --- # Task - The input is in decimal and hexadecimal numbers - Hexademical numbers has the prefix `0x` - The input is case insensitive - Class should be named `SumHex` ## Solution This modification doesn't seem too complicated either. Let's modify our initial `Sum.java` and apply all our improvements from a previous modification. (A good exercise would be to try doing that yourself and then comeback to check) ```java // SumHex.java public class SumHex { public static void main(String[] args) { int sum = 0; for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum += parseNumber(number); number = new StringBuilder(); } } sum += parseNumber(number); } System.out.println(sum); } static int parseNumber(StringBuilder number) { return number.isEmpty() ? 0 : Integer.parseInt(number.toString()); } } ``` > [!NOTE] > The importance of these changes, is that in all further modifications we will only be changing our parseNumber method and leaving `main` method almost unchanged. The only modifications to `main` would be changing the data type of `sum`. Now `parseNumber` thinks that every number is decimal, but we don't know what to do with hexadecimal numbers. Let's try that ```bash $ java SumHex 0x1 0x2 0x3 Exception in thread "main" java.lang.NumberFormatException: For input string: "0x1" at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) at java.base/java.lang.Integer.parseInt(Integer.java:662) at java.base/java.lang.Integer.parseInt(Integer.java:778) at SumHex.parseNumber(SumHex.java:25) at SumHex.main(SumHex.java:18) ``` So we need to split decimal and hexadecimal processing inside `parseNumber`. Our code would look something like this. ```java static int parseNumber(StringBuilder number) { if (!number.isEmpty()) { if (/* number is hexadecimal */) { return /* parse hexadecimal */; } else { // if the number if decimal return Integer.parseInt(number.toString()); } } return 0; } ``` Here we have 2 major questions: - How to check if the number is hexadecimal? - How to parse the hexadecimal number? We can answer the 1st question, because we know that hexadecimal number starts with `"0x"`. And we know that the input is not case sensitive, so `"0X"` is also a valid option. So we can change our *pseude-code* like this ```java static int parseNumber(StringBuilder number) { if (!number.isEmpty()) { if (/* number starts with "0x" or "0X" */) { return /* parse hexadecimal */; } else { // if the number is decimal return Integer.parseInt(number.toString()); } } return 0; } ``` But how to check if the string starts with some prefix? There is a [`String.startsWith(String prefix)`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#startsWith-java.lang.String-) that returns `true` if the string starts with `prefix` and `false` otherwise. So we can change our code using this information. ```java static int parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); if ( numberString.startsWith("0x") || numberString.startsWith("0X") ) { return /* parse hexadecimal */; } else { // if the number is decimal return Integer.parseInt(number.toString()); } } return 0; } ``` > [!NOTE] > `||` here is a logical ***or***. We still have one problem. How to parse the hexadecimal number? Using [`Integer.parseInt()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#parseInt-java.lang.String-int-) method. It turnes out, that it has the second argument `radix` where we can specify the base of the numerical system (in our case it's `16`). So our code will look like this. ```java static int parseNumber(StringBuilder number) { if (number.isEmpty()) { String numberString = number.toString(); if ( numberString.startsWith("0x") || numberString.startsWith("0X") ) { return Integer.parseInt(numberString.substring(2), 16); } else { return Integer.parseInt(numberString); } } return 0; } ``` > [!NOTE] > Here we used [`String.substring(int beginIndex)`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#substring-int-) method to cut off first two symbols (`"0x"`/`"0X"`) in order for `parseInt` to parse number correctly and not give us exceptions. In our case `beginIndex = 2` because it's the first index of the substring that we want to get. Remember that ***indexing starts with 0*** (not 1). > [!IMPORTANT] > It's important to know that we can't store all hexadecimal numbers in `int` data type. Let's look at this example > ```bash > $ java SumHex 0x80000000 > Exception in thread "main" java.lang.NumberFormatException: For input string: "80000000" under radix 16 > at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) > at java.base/java.lang.Integer.parseInt(Integer.java:662) > at SumHex.parseNumber(SumHex.java:30) > at SumHex.main(SumHex.java:18) > ``` > That's why we will need to use `long` data type for storing hexademical values. Here's the updated code ```java static long parseNumber(StringBuilder number) { // int -> long if (!number.isEmpty()) { String numberString = number.toString(); if (numberString.startsWith("0x") || numberString.startsWith("0X")) { // Integer.parseInt -> Long.parseLong return Long.parseLong(numberString.substring(2), 16); } else { return Integer.parseInt(numberString); } } else { return 0; } } ``` Let's present the full solution ```java // SumHex.java public class SumHex { public static void main(String[] args) { int sum = 0; for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum += parseNumber(number); number = new StringBuilder(); } } sum += parseNumber(number); } System.out.println(sum); } static long parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); if (numberString.startsWith("0x") || numberString.startsWith("0X")) { return Long.parseLong(numberString.substring(2), 16); } else { return Integer.parseInt(numberString); } } else { return 0; } } } ``` > [!IMPORTANT] > Notice, that we didn't change `main` method at all, because we have a separate function handling parsing. That's a good sign because our `main` method doesn't know what number parsing algorithm is used and therefore not dependent on it.