Hi! In today's lesson, we'll talk about large numbers. No, I mean REALLY BIG. How to use BigDecimal in Java - 1 We have previously repeatedly encountered the table of value ranges for primitive data types. It looks like this:
Primitive type Size in memory Value range
byte 8 bit -128 to 127
short 16 bit -32768 to 32767
char 16 bit 0 to 65536
int 32 bits -2147483648 to 2147483647
long 64 bit -9223372036854775808 to 9223372036854775807
float 32 bits (2 to the power of -149) to ((2 to the power of -23) * 2 to the power of 127)
double 64 bit (-2 to the power of 63) to ((2 to the power of 63) - 1)
boolean 8 (when used in arrays), 32 (when not used in arrays) true or false
The roomiest integer data type is the long. When it comes to floating-point numbers, it's the double. But what if the number we need is so large that it does not even fit into a long? The Long data type has a quite large range of possible values, but it is still limited to 64 bits. What do we need to come up with if our Very Large Number requires 100 bits? Fortunately, we don't need to invent anything. For cases such as this, Java has two special classes: BigInteger (for integers) and BigDecimal (for floating-point numbers). What makes them special? First of all, in theory, they have no maximum size. We say "in theory", because there are no computers with infinite memory. And if your program creates a number larger than the amount of available memory, then, the program will not work, of course. But such cases are unlikely. As a result, we can say that BigInteger and BigDecimal can represent numbers of virtually unlimited size. What are these classes used for? First of all, for calculations with extremely rigorous accuracy requirements. For example, human life may depend on the accuracy of calculations in some programs (e.g. software that controls airplanes, rockets, or medical equipment). So if the 150th decimal place is important, then BigDecimal is the best choice. In addition, objects of this class are often used in the world of finance, where accurate calculation of even the smallest values is also extremely important. How do you work with BigInteger and BigDecimal objects and do you need to know about them? Objects of these classes are created like this:
public class Main {

   public static void main(String[] args) {

       BigInteger integer = new BigInteger("11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
       System.out.println(integer);

       BigDecimal decimal = new BigDecimal("123.444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444");
       System.out.println(decimal);
   }
}
Passing a string to the constructor is just one possible option. Here we use strings, because our numbers exceed the maximum values for long and double, and we do need some way to explain to the compiler which number we want to create :) Simply passing the number 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 to the constructor won't work: Java will try to cram the passed number into one of the primitive data types, but it won't fit into any of them. That's why using a string to pass the desired number is a good option. Both classes can automatically extract numerical values from the passed strings. Another important point to remember when working with big-number classes is that their objects are immutable (Immutable). You're already familiar with immutability thanks to your experience with the String class and the wrapper classes for primitive types (Integer, Long, etc.).
import java.math.BigInteger;

public class Main {

   public static void main(String[] args) {

       BigInteger integer = new BigInteger("11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
       System.out.println(integer);

       integer.add(BigInteger.valueOf(33333333));
       System.out.println(integer);

   }
}
Console output:
11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
As you would expect, our number has not changed. To perform the addition operation, you must create a new object to receive the result of the operation.
import java.math.BigInteger;

public class Main {

   public static void main(String[] args) {

       BigInteger integer = new BigInteger("11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
       System.out.println(integer);

       BigInteger result = integer.add(BigInteger.valueOf(33333333));
       System.out.println(result);

   }
}
Console output:
11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111144444444
See, now everything works as it should :) By the way, did you notice how unusual the addition operation looks?
BigInteger result = integer.add(BigInteger.valueOf(33333333));
This is another important point. Big-number classes don't use the + - * / operators. Instead, they provide a set of methods. Let's get acquainted with the main ones (as always, you can find a complete list of methods in the Oracle documentation: here and here).
  1. methods for arithmetic operations: add(), subtract(), multiply(), divide(). These methods are used to perform addition, subtraction, multiplication and division, respectively.

  2. doubleValue(), intValue(), floatValue(), longValue(), etc. are used to convert a big number to one of Java's primitive types. Be careful when using these methods. Don't forget about the differences in bit size!

    import java.math.BigInteger;
    
    public class Main {
    
       public static void main(String[] args) {
    
           BigInteger integer = new BigInteger("11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
    
           long result = integer.longValue();
           System.out.println(result);
    
       }
    }

    Console output:

    8198552921648689607
  3. min() and max() let you find the minimum and maximum value of two big numbers.
    Note that these methods are not static!

    import java.math.BigInteger;
    
    public class Main {
    
       public static void main(String[] args) {
    
           BigInteger integer = new BigInteger("11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
           BigInteger integer2 = new BigInteger("222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222");
    
           System.out.println(integer.max(integer2));
    
       }
    }

    Console output:

    222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222

BigDecimal rounding behavior

This topic has its own separate section, since rounding big numbers and configuring rounding behavior are not so simple. You can use the setScale() method to set the number of decimal places for a BigDecimal. For example, suppose we want the number 111.5555555555 to have three digits after the decimal point. However, we can't achieve what we want by passing the number 3 as an argument to the setScale() method. As mentioned above, BigDecimal is for representing numbers with strict requirements on computational precision. In its current form, our number has 10 digits after the decimal point. We want to drop 7 of them and keep only 3. Accordingly, in addition to the number 3, we must pass the rounding mode. BigDecimal has a total of 8 rounding modes. That's a lot! But if you really need to fine tune the precision of your calculations, you'll have everything you need. So, here are the 8 rounding modes offered by BigDecimal:
  1. ROUND_CEILING — rounds up

    111.5555555555 -> setScale(3, ROUND_CEILING) -> 111.556
  2. ROUND_DOWN — rounds towards zero

    111.5555555555 -> setScale(3, ROUND_DOWN) -> 111.555
  3. ROUND_FLOOR — rounds down

    111.5555555555 -> setScale(3, ROUND_FLOOR) -> 111.555

  4. ROUND_HALF_UP — rounds up if the number after the decimal point >= 0.5

    0.55 -> setScale(1, ROUND_HALF_UP) -> 0.6
    0.54 -> setScale(1, ROUND_HALF_UP) -> 0.5
  5. ROUND_HALF_DOWN — rounds up if the number after the decimal point > 0.5

    0.55 -> setScale(1, ROUND_HALF_DOWN) -> 0.5
    0.56 -> setScale(1, ROUND_HALF_DOWN) -> 0.6
  6. ROUND_HALF_EVEN — rounding depends on the number to the left of the decimal point. If the number to the left is even, rounding will be down. If the number to the left of the decimal point is odd, then rounding will be up.

    2.5 -> setScale(0, ROUND_HALF_EVEN) -> 2

    The number to the left of the decimal place is 2 (even). The number is rounded down. We want 0 decimal places, so the result is 2.

    3.5 -> setScale(0, ROUND_HALF_EVEN) -> 4

    The number to the left of the decimal point is 3 (odd). The number is rounded up. We want 0 decimal places, so the result is 4.

  7. ROUND_UNNECCESSARY — This mode is used when you must pass a rounding mode to a method, but the number does not need to be rounded. If you try to round a number with the ROUND_UNNECCESSARY mode set, an ArithmeticException is thrown.

    3.51 -> setScale(1, ROUND_UNNECCESSARY) -> ArithmeticException
  8. ROUND_UP — rounds away from zero.

    111.5551 -> setScale(3, ROUND_UP) -> 111.556

Comparing big numbers

This is also important. You will recall that we use the equals() method is compare objects in Java. The implementation is either provided by the language itself (for standard Java classes) or overriden by the programmer. But in the case of BigDecimal objects, using the equals() method for comparisons is not recommended. This is because the BigDecimal.equals() method returns true only if the 2 numbers have the same value and scale: Let's compare the behavior of the equals() method for the Double and BigDecimal classes:
import java.math.BigDecimal;

public class Main {

   public static void main(String[] args) {

       Double a = 1.5;
       Double b = 1.50;

       System.out.println(a.equals(b));

       BigDecimal x = new BigDecimal("1.5");
       BigDecimal y = new BigDecimal("1.50");

       System.out.println(x.equals(y));

   }
}
Console output:
true
false
As you can see, for BigDecimal, the numbers 1.5 and 1.50 turned out to be unequal! This was precisely because of the specifics of the implementation of the equals() method in the BigDecimal class. For a more accurate comparison of two BigDecimal objects, it is better to use the compareTo() method:
import java.math.BigDecimal;

public class Main {

   public static void main(String[] args) {

       BigDecimal x = new BigDecimal("1.5");
       BigDecimal y = new BigDecimal("1.50");

       System.out.println(x.compareTo(y));

   }
}
Console output:
0
The compareTo() method returned 0, which means that 1.5 and 1.50 are equal. And this is the result we expected! :) That concludes our lesson today. Now it's time to get back to the tasks! :)