6

Having a String representation of a number(no decimals), what's the best way to convert it to either one of java.lang.Integer or java.lang.Long or java.math.BigInteger? The only condition is that the converted type should be of minimal datatype required to hold the number.

I've this current implementation that works fine, but I would like to know if there's a better code without exception handling.

package com.stackoverflow.programmer;

import java.math.BigInteger;

public class Test {
    public static void main(String[] args) {

        String number = "-12121111111111111";
        Number numberObject = null;
        try {
            numberObject = Integer.valueOf(number);
        } catch (NumberFormatException nfe) {
            System.out.println("Number will not fit into Integer type. Trying Long...");
            try {
                numberObject = Long.valueOf(number);
            } catch (NumberFormatException nfeb) {
                System.out.println("Number will not fit into Long type. Trying BigInteger...");
                numberObject = new BigInteger(number);
            }
        }
        System.out.println(numberObject.getClass() + " : "
                + numberObject.toString());
    }
}
  • Yeah, BigInteger will work – Scary Wombat Sep 13 '16 at 06:12
  • BigInteger will work? But, I don't need a BigInteger object if the input String can be accommodated within Integer or Long. –  Sep 13 '16 at 06:14
  • You can possibly get the string length and then draw conclusions based on that length. This is certainly not the best approach and I would just use a BigInteger instead. – dsp_user Sep 13 '16 at 06:17
  • I think checking it through handling `NumberFormatException` is the best idea, otherwise if you're searching for any other method, then that would simply increase your code approximately up to 10 times. See [this](http://stackoverflow.com/questions/237159/whats-the-best-way-to-check-to-see-if-a-string-represents-an-integer-in-java) question for more details. – Raman Sahasi Sep 13 '16 at 06:18
  • I know I can use BigInteger, the one with bigger capacity always. Still, if I have to do some String processing, how to implement it? How to consider negative numbers? –  Sep 13 '16 at 06:19
  • 5
    Why not start with it as BigInteger, then convert it to either Long or Integer if it's small enough? – Dawood ibn Kareem Sep 13 '16 at 06:24
  • @DavidWallace - How to check if it's small enough, something like a if condition using constants like Integer.Max? –  Sep 13 '16 at 06:49
  • With the `compareTo` method of `BigInteger`. – Dawood ibn Kareem Sep 13 '16 at 06:51
  • What's the point of storing the value in the minimal possible type ? By storing the value in an `Object` it means you will have further to downcast the variable, cluttering your code with ugly `if(... instanceof ...)`. You could avoid all this pain by storing all the values in the biggest required type (seems to be `BigInteger` in your case). – Spotted Sep 13 '16 at 07:25
  • @Spotted - I've been asked to implement a static method that returns List from List, not sure how the Number objects are going to be used later on. I think I'll go with my implementation itself. The reason why I would like to avoid Exception handling is because there's a code review process that says you have to either log or rethrow any exception, just cannot ignore like I did. –  Sep 13 '16 at 08:04
  • 1
    @DavidWallace - Your suggestion is good, but wouldn't it involve usage of a temporary BigInteger object. Also, I am not very sure how to use compareTo. Isn't that for comparing one BigInteger with another, how would it fit in here? –  Sep 13 '16 at 08:18

3 Answers3

4

From what you said, here is what I would have done:

import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;

public class TestSO09_39463168_StringToMinimalNumber {

    public static void main(String[] args) {
        List<String> strNumbers = Arrays.asList("0", //int
                "123", //int
                "-456", //int
                "2147483700", // Long
                "-2147483700", // Long
                "9223372036854775900", //BigInt
                "-9223372036854775900" //BigInt
                );

        for(String strNumber : strNumbers){
            Number number = stringToMinimalNumber(strNumber);
            System.out.println("The string '"+strNumber+"' is a "+number.getClass());
        }

    }

    public static Number stringToMinimalNumber(String s){
        BigInteger tempNumber = new BigInteger(s);

        if(tempNumber.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0 || tempNumber.compareTo(BigInteger.valueOf(Long.MIN_VALUE)) < 0){
            return tempNumber;
        } else if(tempNumber.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0 || tempNumber.compareTo(BigInteger.valueOf(Integer.MIN_VALUE)) < 0){
            return tempNumber.longValue(); //Autobox to Long
        } else {
            return tempNumber.intValue(); //Autobox to Integer
        }

    }

}

You must use a temporary BigInteger, or else you'll end up with lazarov's solution, which is correct, but you can't really do something like that for reason mentionned in the comments.

Anyway, every BigInteger (the ones that are not returned) will be garbage collected. As for autoboxing, I don't think it's that of a bad thing. You could also make "BigInteger.valueOf(Long.MAX_VALUE))" as a constant. Maybe the compiler or the JVM will do this on its own.

I'm not really sure of how efficient it is, and using only BigInteger might be a good idea (as Spotted did), because I serioulsy doubt it would really improve the rest of your code to use the right size, and it might even be error prone if you try to use these Numbers with each other ... But again, it all depend on what you need. (and yes, using Exception as flow control is a really bad idea, but you can add a try catch on the BigInteger tempNumber = new BigInteger(s); to throw your own exception if s is not a number at all)

For recreational purpose, I have made the solution without using a BigInteger, and only with String parsing (this is still not what I recommand to do, but it was fun :)

public static final String INT_MAX_VALUE = "2147483647";
public static final String LONG_MAX_VALUE = "9223372036854775807";

public static Number stringToMinimalNumberWithoutBigInteger(String numberStr){
    //Removing the minus sign to test the value
    String s = (numberStr.startsWith("-") ? numberStr.substring(1,numberStr.length()) : numberStr);

    if(compareStringNumber(s, LONG_MAX_VALUE) > 0){
        return new BigInteger(numberStr);
    } else if(compareStringNumber(s, INT_MAX_VALUE) > 0){
        return new Long(numberStr);
    } else {
        return new Integer(numberStr);
    }
}

//return postive if a > b, negative if a < b, 0 if equals;
private static int compareStringNumber(String a, String b){
    if(a.length() != b.length()){
        return a.length() - b.length();
    }
    for(int i = 0; i < a.length(); i++){
        if( a.codePointAt(i) != b.codePointAt(i) ){ //Or charAt()
            return a.codePointAt(i) - b.codePointAt(i);
        }
    }
    return 0;
}
Asoub
  • 2,273
  • 1
  • 20
  • 33
  • 1
    Thanks for this post. This code is clean. I'll accept this as answer. –  Sep 13 '16 at 11:00
1

Please don't use exceptions for handling flow control, this is a serious anti-pattern (also here).

As you mentionned in the comments, the real thing you've been asked is to convert a List<String> into a List<Number>. Also, if I understand correctly, you know that:

  • You should encounter only numbers without decimals
  • The biggest value you can encounter is possibly unbound

Based on that, the following method will do the job in a more clever way:

private static List<Number> toNumbers(List<String> strings) {
    return strings.stream()
                  .map(BigInteger::new)
                  .collect(Collectors.toList());
}

Eidt: if you're not very familiar with the stream concept, here's the equivalent code without streams:

private static List<Number> toNumbers(List<String> strings) {
    List<Number> numbers = new ArrayList<>();
    for (String s : strings) {
        numbers.add(new BigInteger(s));
    }
    return numbers;
}
Community
  • 1
  • 1
Spotted
  • 4,021
  • 17
  • 33
  • Am new to this streams concept. This method will always return List? –  Sep 13 '16 at 08:24
  • @Programmer From the outside this method returns a `List` (as you've been asked to). The real type of every element will be `BigInteger` though. I've updated my answer with an equivalent code without using streams (if that can help you). – Spotted Sep 13 '16 at 08:26
  • 1
    I think OP also stated that "the converted type should be of minimal datatype required to hold the number.", so I don't think making BigInteger everytime is a good idea. – Asoub Sep 13 '16 at 08:46
  • BigInteger stores numbers internally as a signum (int: -1,0,1) and magnitude (bytes represented as int[]). This is an efficient way of storing large numbers, and has a minimum overhead of 31 bits per number over int and long (32 bit signum instead of 1 bit signum). – Peter Walser Sep 13 '16 at 08:51
  • @Asoub Indeed but in the comments he also stated that he should convert a `List` into a `List`, that's why I suspect this constraint is irrelevant. – Spotted Sep 13 '16 at 08:52
  • Although I agree that using exceptions for control flow is an anti-pattern, there is, IMHO, **one** exception (sic!) to this rule: Checking whether a string is a valid number. For integral types, you could try to fiddle with a regex, but even this won't cover the case of numbers being too large. And I doubt that a regex exists that exactly matches all `double` numbers. One **has** to do "trial and error" here. – Marco13 Sep 14 '16 at 02:02
0

Well if you want to do it "by hand" try something like this:

We define the max values as strings :

String intMax = "2147483647";
String longMax = "9223372036854775807";

and our number:

String ourNumber = "1234567890"

Now our logic will be simple : We will check lenghts of strings firstly

  1. If our numbers length < int max length : IT IS INT

  2. If our numbers length == int max length : Check is it INT or LONG

  3. If our numbers length > int max length :

    3.1 If our numbers length < long max length : IT IS LONG

    3.2 If our numbers length == long max length : Check is it LONG or BIG INTEGER

    3.3 If our numbers length > long max length : IT IS BIG INTEGER

The code should look something like this (I have not tried to compile it may have syntax or other errors) :

if(ourNumber.lenght() < intMax.length ){
    System.out.println("It is an Integer");
} else if(ourNumber.lenght() == intMax.length){
    // it can be int if the number is between 2000000000 and 2147483647
            char[] ourNumberToCharArray = ourNumber.toCharArray();
            char[] intMaxToCharArray = intMax.toCharArray();
            int diff = 0;
            for(int i = 0; i < ourNumberToCharArray.length; i++) {  
                diff  = Character.getNumericValue(intMaxToCharArray[i]) - Character.getNumericValue(ourNumberToCharArray[i]);
                if(diff > 0) { 
                    System.out.println("It is a Long");
                    break;                  
                } else if(diff < 0) {
                    System.out.println("It is an Integer");
                        break;
                }
            }
            if(diff == 0){
              System.out.println("It is an Integer");  
            }   
} else { 
    if(ourNumber.lenght() < longMax.length()) {
        System.out.println("It is a Long");
    } else if(ourNumber.lenght() == longMax.length()){
            char[] ourNumberToCharArray = ourNumber.toCharArray();
            char[] longMaxToCharArray = longMax.toCharArray();
            int diff = 0;
            for(int i = 0; i < ourNumberToCharArray.length; i++) {  
                diff  = Character.getNumericValue(longMaxToCharArray[i]) - Character.getNumericValue(ourNumberToCharArray[i]);
                if(diff > 0) { 
                    System.out.println("It is a BigInteger");
                    break;                  
                } else if(diff < 0) {
                    System.out.println("It is a Long");
                        break;
                }
            }   
            if(diff == 0){
              System.out.println("It is a Long");  
            }   
    } else { 
        System.out.println("It is a BigInteger");
    }
}

Then logic that checks if the numbers match or not is the same in both cases you can but it in a function for example.

Lazar Lazarov
  • 2,412
  • 4
  • 26
  • 35
  • 2
    I didn't think it was possible to have a more complicated solution than the OP's... – Spotted Sep 13 '16 at 08:42
  • Yeah complicated but cheaper believe it or not. – Lazar Lazarov Sep 13 '16 at 08:51
  • This code will be very expensive to maintain (for what it does), it's unprofessional to let such a code go into production. – Spotted Sep 13 '16 at 08:55
  • Lol who is talking about production here ? If he needs the answer for some university or school project in which they want to find how he thinks not how much of the inner libraries he knows ? Your judge is wrong here pal. – Lazar Lazarov Sep 13 '16 at 10:07
  • I wanted a sample code on how to accomplish this through String processing and this post throws some light on it. It might need some refactoring to improve readability and also account for negative numbers. –  Sep 13 '16 at 10:43
  • 1
    Yeah, replacing the System.out with code logic / creating a function for looping throught each characters / using String.codePointAt(i) instead of creating characters arrays then casting chars to int / removing the minus sign from the input. – Asoub Sep 14 '16 at 13:33