SumBigDecimalHex

Task

  • Input data fits into BigDecimal 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

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

static double parseNumber(StringBuilder number) {

to this

static BigDecimal parseNumber(StringBuilder number) {

Also let’s change this line from the parsing of a decimal number option

return Double.parseDouble(numberString);

to

return new BigDecimal(numberString);

We will change this line

return 0;

to

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() method that converts all the characters to lower case.

So our code will currently look like this

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

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) 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

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

// 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} 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

mantissa10exponent 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) method that returns the index of the first occurrence of the specified character (in our case "s"). Okay, then using String.substring() we can split into two parts. Here’s what it’s gonna look like

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 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.

BigInteger mantissa = new BigInteger(mantissaHexString, 16);
BigInteger exponent = new BigInteger(exponentHexString, 16);

Okay, one thing left is to use the formula that we got

mantissa10exponent mantissa \cdot 10^{-exponent}

I will write the solution first and explain it right after

return new BigDecimal(mantissa).scaleByPowerOfTen(exponent.negate().intValueExact());

Here we are using BigDecimal.scaleByPowerOfTen(int n) method to basically multiply by 10exponent10^{-exponent}. Then we use BigInteger.negate() to basically multiply by -1. And finally we use BigInteger.intValueExact() to get the integer value. We do that because scaleByPowerOfTen method recieves integer argument (not BigInteger).

So our method will look like this

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

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

// 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!