--- title: SumBigDecimalHex weight: 6 --- # Task - Input data fits into [BigDecimal](https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html) data type - There are decimal and hexadecimal numbers in input - Hexadecimal numbers has `0x` prefix, so for example `0xbsc` equals `11·10⁻¹²` (mantissa and exponent are integers) - Input is case insensitive - Class should be named `SumBigDecimalHex` ## Solution This is the hardest (and last) modification for this homework. Let's modify our `SumDoubleHex` `parseNumber` method. This is what it looks now ```java static double parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); if ( numberString.startsWith("0x") || numberString.startsWith("0X") ) { if (numberString.contains(".")) { return Double.parseDouble(numberString); } return Long.parseLong(numberString.substring(2), 16); } else { return Double.parseDouble(numberString); } } else { return 0; } } ``` Let's make some small changes: Firstly, let's change the output data type to `BigDecimal`. So we will change this line ```java static double parseNumber(StringBuilder number) { ``` to this ```java static BigDecimal parseNumber(StringBuilder number) { ``` Also let's change this line from the parsing of a decimal number option ```java return Double.parseDouble(numberString); ``` to ```java return new BigDecimal(numberString); ``` We will change this line ```java return 0; ``` to ```java return BigDecimal.ZERO; ``` as we did almost the same thing for `SumBigIntegerOctal` modification. Also let's change our `numberString` to lowercase letters just for convenience ``` String numberString = number.toString().toLowerCase(); ``` > [!NOTE] > Here we are using [`String.toLowerCase()`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#toLowerCase--) method that converts all the characters to lower case. So our code will currently look like this ```java static BigDecimal parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString().toLowerCase(); // toLowerCase() added if (numberString.startsWith("0x")) { numberString = numberString.substring(2); if ( /* numberString has "s" in it */ ) { /* somehow parse number */ } /* somehow parse number */ } else { return new BigDecimal(numberString); } } else { return BigDecimal.ZERO; } } ``` > [!NOTE] > Notice, that we no longer need to check for `numberString` to begin with `"0X"`. Since there can't be an upper case letter in the `numberString` after converting all of its letters to lower case. > [!NOTE] > Also in this line > ```java > numberString = numberString.substring(2); > ``` > we strip off first two symbols (`0x`) in order for correct parsing later. Let's solve our "comments" one-by-one. The easiest is to understand if there is an `"s"` in `numberString`. We can do so using [`String.contains(CharSequence s)`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#contains-java.lang.CharSequence-) that returns true if and only if this string contains the specified sequence of char values. In our case the sequence of values will be `"s"`. Let's apply this logic to our method ```java static BigDecimal parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString().toLowerCase(); if (numberString.startsWith("0x")) { numberString = numberString.substring(2); if (numberString.contains("s")) { // <-- /* somehow parse number */ } /* somehow parse number */ } else { return new BigDecimal(numberString); } } else { return BigDecimal.ZERO; } } ``` > [!NOTE] > Again, we are not checking for upper case `"S"`, since there are no upper case letters in `numberString` because we used `.toLowerCase()`. Okay, now let's parse our number if it doesn't contain `"s"`. It's actually very simple. First we need to convert it to `BigInteger` and then pass the result to `BigDecimal`. It will look like this ```java // using 16 to convert from hexadecimal to decimal // and only then passing to BigDecimal return new BigDecimal(new BigInteger(numberString, 16)); ``` > [!IMPORTANT] > Here, we don't assign `BigInteger` object to any variable so it is used only for conversion from hexadecimal to decimal. After it's value is passed to `BigDecimal` a garbage collector can come and destroy it, since there is no way to access it anymore. We have only one "unsolved comment" left, but it's going to require a bit of theory. So basically `s` splits our number in two parts: everything on the left is called a mantissa and on the right is exponent. So the form is basically the following $$ 0x\{mantissa\}s\{exponent\} $$ In order to get the number we will first need to convert each part from hexadecimal to decimal. Exponent represents decimal places. For example `0xABs02`: `AB` in hex equals 171 in decimal, `02` means 2 decimal places, so we shift point (`.`) 2 places left and get 1.71. Mathematically we can shift a point (`.`) by multiplying 10 with some power (in our case exponent). But to shift left we need to first negate the exponent so the point (`.`) shifts left (not right). So the formula is $$ mantissa \cdot 10^{-exponent} $$ Okay, we are done with theory, but how can we implement it in our code. First of all we need somehow to split our `numberString` by `"s"`. To do that we need to get the index at which `"s"` in placed. To do that we can use [`String.indexOf(char ch)`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#indexOf-int-) method that returns the index of the first occurrence of the specified character (in our case `"s"`). Okay, then using [`String.substring()`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#substring-int-) we can split into two parts. Here's what it's gonna look like ```java int indexOfS = numberString.indexOf('s'); // all characters before 's', without including 's' itself String mantissaHexString = numberString.substring(0, indexOfS); // all characters after 's', without including 's' itself String exponentHexString = numberString.substring(indexOfS + 1); ``` > [!NOTE] > Note, that we use `'s'` (not `"s"`) in `indexOf` method. That's because it uses [`char`](https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html) data type as its argument, but `"s"` is a string. So we need single quotes in order to represent `char`. > [!NOTE] > When extracting exponent we can use only one argument (`indexOfS + 1`), because it will automatically go to the end of the string itself. Okay, now that we have `mantissaHexString` and `exponentHexString` we just need to convert them into decimal. That we know how to do using `BigInteger` with base specifier. ```java BigInteger mantissa = new BigInteger(mantissaHexString, 16); BigInteger exponent = new BigInteger(exponentHexString, 16); ``` Okay, one thing left is to use the formula that we got $$ mantissa \cdot 10^{-exponent} $$ I will write the solution first and explain it right after ```java return new BigDecimal(mantissa).scaleByPowerOfTen(exponent.negate().intValueExact()); ``` Here we are using [`BigDecimal.scaleByPowerOfTen(int n)`](https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html#scaleByPowerOfTen-int-) method to basically multiply by \(10^{-exponent}\). Then we use [`BigInteger.negate()`](https://docs.oracle.com/javase/8/docs/api/java/math/BigInteger.html#negate--) to basically multiply by -1. And finally we use [`BigInteger.intValueExact()`](https://docs.oracle.com/javase/8/docs/api/java/math/BigInteger.html#intValueExact--) to get the integer value. We do that because `scaleByPowerOfTen` method recieves integer argument (not `BigInteger`). So our method will look like this ```java static BigDecimal parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString().toLowerCase(); if (numberString.startsWith("0x")) { numberString = numberString.substring(2); if (numberString.contains("s")) { int indexOfS = numberString.indexOf('s'); String mantissaHexString = numberString.substring(0, indexOfS); String exponentHexString = numberString.substring(indexOfS + 1); BigInteger mantissa = new BigInteger(mantissaHexString, 16); BigInteger exponent = new BigInteger(exponentHexString, 16); return new BigDecimal(mantissa).scaleByPowerOfTen(exponent.negate().intValueExact()); } return new BigDecimal(new BigInteger(numberString, 16)); } else { return new BigDecimal(numberString); } } else { return BigDecimal.ZERO; } } ``` Okay, now let's change `main` method accordingly. Let's take `main` method from `SumBigIntegerOctal` modification. Here's what it looks like now ```java public static void main(String[] args) { BigInteger sum = new BigInteger("0"); for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum = sum.add(parseNumber(number)); number = new StringBuilder(); } } sum = sum.add(parseNumber(number)); } System.out.println(sum); } ``` All we need to do is to replace the sum type from `BigInteger` to `BigDecimal`. So full code will look like this ```java // SumBigDecimalHex.java import java.math.BigDecimal; import java.math.BigInteger; public class SumBigDecimalHex { public static void main(String[] args) { BigDecimal sum = new BigDecimal("0"); for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum = sum.add(parseNumber(number)); number = new StringBuilder(); } } sum = sum.add(parseNumber(number)); } System.out.println(sum); } static BigDecimal parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString().toLowerCase(); if (numberString.startsWith("0x")) { numberString = numberString.substring(2); if (numberString.contains("s")) { int indexOfS = numberString.indexOf('s'); String mantissaHexString = numberString.substring(0, indexOfS); String exponentHexString = numberString.substring(indexOfS + 1); BigInteger mantissa = new BigInteger(mantissaHexString, 16); BigInteger exponent = new BigInteger(exponentHexString, 16); return new BigDecimal(mantissa).scaleByPowerOfTen(exponent.negate().intValueExact()); } return new BigDecimal(new BigInteger(numberString, 16)); } else { return new BigDecimal(numberString); } } else { return BigDecimal.ZERO; } } } ``` This code works correctly and passes all the tests!