...
BigInteger 자료형
BigInteger는 언제 사용되는가
Type | 범위 |
int | -2,147,483,648 ~ 2,147,483,647 |
long | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 |
자바의 int는 메모리 크기는 4byte로 표현할 수 있는 범위는 -2,147,483,648 ~ 2,147,483,647이고 long은 메모리 크기는 8byte로 표현할 수 있는 범위는 -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 이다.
보기엔 조 단위가 넘는 커다란 숫자이지만, 우주 시뮬레이션과 같은 무한한 정수를 계산하기 위해서는 턱 없이 부족한 공간이다.
만일 이 타입의 범위를 넘어서게 되면 에러가 난다.
따라서 정수형 데이터의 타입을 사용할 때에는 반드시 자신이 사용하고자 하는 데이터의 최소/최대 크기를 고려해야 한다.
만약 해당 타입이 표현할 수 있는 범위를 벗어난 데이터를 저장하게 되면, 오버플로우(overflow)가 발생해 전혀 다른 값이 저장될 수 있기 때문이다.
- 오버플로우 : 해당 타입이 표현할 수 있는 '최대 표현 범위'보다 큰 수를 저장할 때 발생하는 현상
- 언더플로우 : 해당 타입이 표현할 수 있는 '최소 표현 범위'보다 작은 수를 저장할 때 발생하는 현상
byte max = 127;
byte min = -128;
System.out.println(max + 1000); // ERROR
System.out.println(min - 1000); // ERROR
자바에서 byte 타입이 표현할 수 있는 정수 크기 범위는 -128부터 127까지 이다.
하지만 위의 코드 처럼 바이트 타입의 변수에 크기 범위를 넘어서는 연산을 하게 되면 오버플로우, 언더플로우가 발생하여 잘못된 결과가 저장되게 된다.
그래서 프로그램 개발할때 만일 아주 큰 정수를 다룰 경우가 있을 경우, 고정된 타입보다는 자바에서 제공하는 BigInteger이라는 클래스를 활용하는 것이 좋다.
BigInteger 자료형은 거의 무한한 크기의 정수형 숫자를 다룰 수 있는 타입이다. 보안 및 암호화 응용 프로그램에서 널리 사용된다.
BigInteger 사용법
BigInteger 선언
만일 long 타입이 수용할 수 있는 범위보다 더 큰 숫자가 필요하다면 BigInteger 클래스 타입을 이용하면 된다.
먼저 BigInteger 를 사용하기 위해선 가장 먼저 java.math 패키지에서 클래스를 import 해야 된다.
기본적으로 BigInteger을 초기화하기 위해서는 문자열을 인자 값으로 넘겨주어야 한다. 만일 숫자로 초기화하려면 valueOf static 메소드를 이용하면 된다.
import java.math.BigInteger; // BigInteger를 사용하려면 math 클래스를 import 해야 된다.
// 문자열로 생성
BigInteger bigNumber = new BigInteger("10000");
// 숫자로 생성
BigInteger bigInteger = BigInteger.valueOf(8871);
// n진수로 생성
BigInteger bigInteger = new BigInteger("FFFF", 16); // 16진수
BigInteger bigInteger2 = new BigInteger("1011", 2); // 2진수
BigInteger 형 변환
문자열 리터럴 값으로 초기화 할 수도 있지만, 이미 있는 숫자값들을 가져와 BigInterger 타입으로 형 변환도 가능하다.
// int / long -> BigIntger
int num = 100000;
BigInteger bigNumber = BigInteger.valueOf(num);
// BigIntger -> int
int int_bigNum = bigNumber.intValue();
// BigIntger -> long
long long_bigNum = bigNumber.longValue();
// BigIntger -> float
float float_bigNum = bigNumber.floatValue();
// BigIntger -> double
double double_bigNum = bigNumber.doubleValue();
// BigIntger -> String
String String_bigNum = bigNumber.toString();
BigInteger 연산
BIgInteger 자료형은 클래스 구조이며 문자열 이기 때문에 일반적인 +, -, * 기호를 이용한 사칙연산을 직접적으로 할 수 없다.
그래서 BigInteger 타입의 숫자를 연산을 하려면 내장 메서드를 통하여 해야한다.
내장 메서드에는 대표적으로 add , substract , multiply , divide , compareTo 등이 있다
BigInteger bigNumber1 = new BigInteger("100000");
BigInteger bigNumber2 = new BigInteger("10000");
System.out.println("덧셈(+) :" +bigNumber1.add(bigNumber2)); // 덧셈(+) :110000
System.out.println("뺄셈(-) :" +bigNumber1.subtract(bigNumber2)); // 뺄셈(-) :90000
System.out.println("곱셈(*) :" +bigNumber1.multiply(bigNumber2)); // 곱셈(*) :1000000000
System.out.println("나눗셈(/) :" +bigNumber1.divide(bigNumber2)); // 나눗셈(/) :10
System.out.println("나머지(%) :" +bigNumber1.remainder(bigNumber2)); // 나머지(%) :0
메서드 | 설 명 |
BigInteger add(BigInteger val) | 덧셈(this + val) |
BigInteger subtract(BigInteger val) | 뺄셈(this - val) |
BigInteger multiply(BigInteger val) | 곱셈(this * val) |
BigInteger divide(BigInteger val) | 나눗셈(this / val) |
BigInteger remainder(BigInteger val) | 나머지(this % val) |
BigInteger 비트 연산
BigInteger는 큰 숫자를 다루는 대신 성능이 떨어지므로, 성능을 향상시키기 위해 비트단위로 연산하는 메서드들을 제공한다.
BigInteger i = new BigInteger("1018"); // 2진수로 표현하면 : 1111111010(2)
int bitCount = i.bitCount(); // 1의 갯수 : 8
int bitLength = i.bitLength(); // 비트 수 : 10
int getLowestSetBit = i.getLowestSetBit(); // 1
boolean testBit3 = i.testBit(3); // true
BigInteger setBit12 = i.setBit(12); // 우측에서 13번째 비트를 1로 변경 → 1001111111010(2) → 5114
BigInteger flipBit0 = i.flipBit(0); // 1111111011(2) → 1019
BigInteger clearBit3 = i.clearBit(3); // 1111110010(2) → 1010
메서드 | 설 명 |
int bitCount( ) | 2진수로 표현했을 때, 1의 개수(음수는 0의 개수)를 반환 |
int bitLength( ) | 2진수로 표현헀을 때, 값을 표현하는데 필요한 bit수 |
boolean testBit(int n) | 우측에서 n + 1번째 비트가 1이면 true, 0이면 false |
BigInteger setBit(int n) | 우측에서 n + 1번째 비트를 1로 변경 |
BigInteger clearBit(int n) | 우측에서 n + 1번째 비트를 0으로 변경 |
BigInteger flipBit(int n) | 우측에서 n + 1번째 비트를 전환(1 → 0, 0 → 1) |
BigInteger 타입의 숫자가 정수가 짝수인지 홀수 인지 확인하는 방법으론, 제일 오른쪽 비트가 0일 것이므로,testBit(0)으로 마지막 비트를 확인하는 식으로 응용이 가능하다.
또한 비트 끼리 and, or, xor, not 연산이 가능하다.
BigInteger i = new BigInteger("17"); // 2진수 : 10001(2)
BigInteger j = new BigInteger("7"); // 2진수 : 111(2)
BigInteger and = i.and(j); // 10001(2) & 111(2) = 00001(2) → 1(10)
BigInteger or = i.or(j); // 23
BigInteger not = j.not(); // -8
BigInteger xor = i.xor(j); // 22
BigInteger andNot = i.andNot(j); // 16
BigInteger shiftLeft = i.shiftLeft(1); // 34
BigInteger shiftRight = i.shiftRight(1); // 8
BigInteger 숫자 비교
BigInteger의 값을 비교할 때는 compareTo 라는 메서드를 사용한다.
BigInteger bigNumber1 = new BigInteger("100000");
BigInteger bigNumber2 = new BigInteger("1000000");
// 두 수 비교 compareTo 맞으면 0 / 틀리면 -1
int compare = bigNumber1.compareTo(bigNumber2);
System.out.println(compare); // -1
BigInteger 상수
참고로 자주 사용되는 숫자 0,1,2,10은 BigInteger.ZERO, BigInteger.ONE, BigInteger.TWO, BigInteger.TEN으로 클래스 상수로서 제공한다.
그냥 숫자를 쓰면 되지 이런식으로 클래스 상수로 제공되는 이유는, BigInteger 연산에 있어 편리함을 제공하기 위해서 이다.
다음 예제 코드를 보면, 어느 숫자에 10을 계산할때 위에서 배운대로 라면 valueOf 로 BigInteger 타입으로 변환을 해주고 계산을 해야되지만 클래스 상수로 바로바로 계산이 됨을 볼 수 있다.
System.out.println(BigInteger.ZERO); // 0
System.out.println(BigInteger.ONE); // 1
System.out.println(BigInteger.TWO); // 2
System.out.println(BigInteger.TEN); // 10
// 본래라면 숫자 10을 valueOf를 통해 BigInteger 클래스로 초기화하고 연산을 해야되지만,
System.out.println(new BigInteger("20").add(BigInteger.valueOf(10))); // 30
// 자주 사용되는 숫자일 경우 BigInteger 상수로 바로 계산이 가능하다는 장점이 있다
System.out.println(new BigInteger("20").add(BigInteger.TEN)); // 30
BigDecimal 자료형
BigDecimal는 언제 사용되는가
Type | 범위 |
float | 1.4E-45 ~ 3.4028235E38 |
double | 4.9E-324 ~ 1.7976931348623157E308 |
부동 소수점을 이용해 실수 넘버를 저장하는타입인 float과 double은 소수점의 정밀도가 완벽하지 않는 다는 특징을 지니고 있다.
그래서 소수점 이하의 수를 다룰 때 사칙연산 시 정확한 값을 출력하지 않게 된다. 왜냐하면 내부적으로 수를 저장할 때 이진수의 근사치를 저장하기 때문이다.
컴퓨터의 실수와 부동소수점의 특징을 자세히 알아보고 싶다면 다음 포스팅을 참고하길 바란다. (정말 쉽게 설명하였다)
double value1 = 12.23;
double value2 = 34.45;
// 기대값 : 46.68
System.out.println(value1 + value2); // 46.68000000000001
위의 코드에서 12.23와 34.45을 더했으니 결과로 46.68을 예상했겠지만, 실제로는 46.68000000000001가 출력되는 것을 볼 수 있다.
수의 크기로서 보면 아주 작은 근사한 차이겠지만, 금융 관련 프로그램에서는 이 오차가 큰 영향을 미칠 수 있기 때문에 주의해야 한다.
그렇기에 미세한 숫자의 변동도 허용하지 않는 특히 돈과 소수점을 다룬다면 BigDecimal을 사용해야 한다.
BigDecimal은 계산 속도는 double , float을 사용하는 경우보다 조금 느리지만 정밀한 결과를 보장한다.
BigDecimal 사용법
BigDecimal 선언
BigDecimal 클래스는 java.math 패키지 안에 포함되어 있다.
BigDecimal 은 앞서본 BigInteger 와 매우 비슷한 형태임을 볼 수 있다. 차이점은 정수냐 실수냐 이라고 할수 있다.
double, int, long타입으로도 생성이 가능하지만, double의 정밀도의 한계가 있어 사용하는 클래스 이므로 double타입으로 생성할 경우 오차 발생 가능성이 존재한다. 그래서 BigDecimal 역시 BigInteger처럼 보통 문자열로 생성한다.
import java.math.BigDecimal; // BigDecimal 사용하려면 math 클래스를 import 해야 된다.
// 문자열로 생성
BigDecimal bigDecimal = new BigDecimal("123.45678");
// double 타입으로 생성(오차가 발생할 수 있다) - 비추천
BigDecimal bigDecimal2 = new BigDecimal(123.456); // 123.4560000000000030695446184836328029632568359375
// valueOf 생성
BigDecimal bigDecimal3 = BigDecimal.valueOf(123.456); // 123.456
// 소수점 아래자리수 지정
BigDecimal bdFromLong2 = BigDecimal.valueOf(123412345678901L, 2); // 123412345678901 / 10^2 → 1234123456789.01
BigDecimal 형 변환
// double -> BigDecimal
BigDecimal bigDecimal = BigDecimal.valueOf(100000.12345);
// BigDecimal -> int
int int_bigNum = bigDecimal.intValue();
// BigDecimal -> long
long long_bigNum = bigDecimal.longValue();
// BigDecimal -> float
float float_bigNum = bigDecimal.floatValue();
// BigDecimal -> double
double double_bigNum = bigDecimal.doubleValue();
// BigDecimal -> String
String String_bigNum = bigDecimal.toString();
BigDecimal 연산
BigDecimal bigNumber1 = new BigDecimal("100000.12345");
BigDecimal bigNumber2 = new BigDecimal("10000");
System.out.println("덧셈(+) :" +bigNumber1.add(bigNumber2)); // 덧셈(+) :110000.12345
System.out.println("뺄셈(-) :" +bigNumber1.subtract(bigNumber2)); // 뺄셈(-) :90000.12345
System.out.println("곱셈(*) :" +bigNumber1.multiply(bigNumber2)); // 곱셈(*) :1000001234.50000
System.out.println("나눗셈(/) :" +bigNumber1.divide(bigNumber2)); // 나눗셈(/) :10.000012345
System.out.println("나머지(%) :" +bigNumber1.remainder(bigNumber2)); // 나머지(%) :0.12345
메서드 | 설 명 |
BigDecimal add(BigDecimal val) | 덧셈(this + vall) |
BigDecimal subtract(BigDecimal val) | 뺄셈(this - vall) |
BigDecimal multiply(BigDecimal val) | 곱셈(this * vall) |
BigDecimal divide(BigDecimal val) | 나눗셈(this / vall) |
BigDecimal remainder(BigDecimal val) | 나머지(this % vall) |
BigDecimal 숫자 비교
두 수를 비교하는 compareTo 는 마찬가지로 같으면 0 다르면 -1 을 반환한다.
BigDecimal bigNumber1 = new BigDecimal("100000.12345");
BigDecimal bigNumber2 = new BigDecimal("1000000.6789");
int compare = bigNumber1.compareTo(bigNumber2); //-1
BigDecimal 정밀도 값
BigDecimal 는 실수 자료형이기 때문에 정밀도, 스케일, 부호 같은 속성을 추출하는 메소드가 존재한다.
BigDecimal bd = new BigDecimal("-12345.6789");
bd.precision(); // 정밀도 : 9
bd.scale(); // 지수값(스케일) : 4
bd.signum(); // 부호 : -1(음수)
BigDecimal 지수(E) 제거
가끔 커다란 실수값을 다룰떄, 지수(E) 부호가 붙어있는 값을 다룰때가 있다.
예를들면 2000000을 2.0E7로 표현하는데 이를 그대로 연산에 이용할수가 없어서 수동 숫자 변환이 필요하다.
이에 대한 해결법으로는, 우선 Double로 파싱을 한 뒤, new BigDecimal 객체를 생성주면 된다.
String str = "1.0E7"
BigDecimal b = new BigDecimal(str); // 지수 붙음
System.out.println(b); // 1.0E+7
BigDecimal b2 = new BigDecimal(Double.parseDouble(str)); // 이렇게 하면 지수 안붙음
System.out.println(b2); // 10000000
BigDecimal 반올림 정책
java.lang.ArithmeticException
BigDecimal 타입의 실수는 divide 메서드를 사용하면 나눗셈 연산이 가능하다.
하지만 주의해야 할점은, 실수 나눗셈의 결과가 정확하게 나누어 몫이 떨어지지 않는 수의 경우 java.lang.ArithmeticException 예외를 던진다는 점이다.
BigDecimal value1 = new BigDecimal("11");
BigDecimal value2 = BigDecimal.valueOf(3);
// Exception in thread "main" java.lang.ArithmeticException:
// Non-terminating decimal expansion; no exact representable decimal result.
value1.divide(value2); // 3.666666666666666666666...
따라서 BigDecimal 의 나눗셈은 보다 정확한 계산을 위해서는 소수점 몇 번째짜리까지, 어떻게 처리할 것인지 지정을 해줘야 한다.
BigDecimal value1 = new BigDecimal("11");
BigDecimal value2 = BigDecimal.valueOf(3);
// divide()의 2번째 파라미터는 N 번째 자리까지 표현할 것인가를 뜻하고, 3번째 파라미터는 반올림 정책 처리 방식이다.
// 소수점 3번째 자리에서 반올림하여 2번째 자리까지 표기한다.
value1.divide(value2, 2, RoundingMode.HALF_EVEN); // 3.67
BigDecimal - RoundingMode
위의 코드에서 반올림을 어떻게 처리할 것인가에 대해 RoundingMode.HALF_EVEN 라는 상수를 사용했는데, 이것이 반올림 정책이다.
자바에서는 BigDecimal 클래스에 다양한 소수점 처리 방식을 제공하고 있다.
아무래도 부동소수점의 실수를 다루는데 있어 부정확한 실수 연산값을 보완하기 위해 반올림을 명확히 지정할 필요가 있기 때문에 자바 프로그래밍에서 지원하는 것이다.
예를 들어, 미국 연방 세금 환급은 RoundingMode.HALF_UP을 사용하여 전체 달러 금액으로 반올림하도록 지정한다.
상수 | 설 명 |
RoundingMode.CEILING | 올림 |
RoundingMode.FLOOR | 내림 |
RoundingMode.UP | 양수일때는 올림, 음수일 때는 내림 |
RoundingMode.DOWN | UP 과 반대로 양수일 때는 내림, 음수일 때는 올림 |
RoundingMode.HALF_UP | 반올림(5이상 올림, 5미만 버림) |
RoundingMode.HALF_EVEN | 반올림(반올림 자리의 값이 짝수면 HALF_DOWN , 홀수면 HALF_UP ) |
RoundingMode.HALF_DOWN | 반올림(6이상 올림, 6미만 버림) |
RoundingMode.UNNECESSARY | 나눗셈의 결과가 딱 떨어지는 수가 아니면, ArithmeticException 발생 |
사용법은 다음과 같다.
divide() 메소드에 첫번째 인자로는 실수값을, 두번째는 소수점 몇자리 지정, 세번째는 반올림 정책을 지정한다.
반드시 인수 3개를 적을 필요는 없으며 자유롭게 가능하다.
BigDecimal divide(BigDecimal num, int scale(소수점 몇자리), RoundingMode roundingMode)
BigDecimal b10 = new BigDecimal("10");
BigDecimal b3 = new BigDecimal("3");
// 나누기 결과가 무한으로 떨어지면 예외 발생
b10.divide(b3);// 3.3333333333333... java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
// 반올림 정책을 명시하면 예외가 발생하지 않음
b10.divide(b3, RoundingMode.HALF_EVEN); // 3
// 반올림 자리값을 명시
b10.divide(b3, 6, RoundingMode.HALF_EVEN); // 3.333333
b10.divide(b3, 9, RoundingMode.HALF_EVEN); // 3.333333333
혹은 연산값이 아닌 고정된 실수에 대해서도 setScale() 메소드를 통해 반올림 정책을 지정할 수 있다.
BigDecimal setScale(int newScale, RoundingMode mode)
// 소수점 첫 번째까지 표현, 두번째 자리에서 반올림
BigDecimal.valueOf(12.35).setScale(1, RoundingMode.HALF_UP); // 12.4
// 소수점 이하 모두 제거하고 올림
BigDecimal.valueOf(12.34).setScale(0, RoundingMode.CEILING); // 13
// 음수인 경우는 특정 자릿수 이하 제거
BigDecimal.valueOf(-12.34).setScale(1, RoundingMode.CEILING); // -12.3
// 특정 자릿수 이하 버림
new BigDecimal("12.37").setScale(1, RoundingMode.FLOOR); // 12.3
BigDecimal - MathContext
반올림 동작을 제어하는데 RoundingMode 와 같이 MathContext 클래스도 존재한다.
MathContext는 반올림 모드와 정밀도(precision)를 하나로 묶어놓은 것일 뿐인 클래스이다.
상수 | 설 명 |
MathContext.DECIMAL32 | 7자리 정밀도 및 HALF_EVEN의 반올림 모드 |
MathContext.DECIMAL64 | 16자리 정밀도 및 HALF_EVEN의 반올림 모드 |
MathContext.DECIMAL128 | 34자리 정밀도 및 HALF_EVEN의 반올림 모드 |
MathContext.UNLIMITED | 무제한 정밀 산술 |
BigDecimal b10 = new BigDecimal("10");
BigDecimal b3 = new BigDecimal("3");
// 전체 자리수를 7개로 제한하고 HALF_EVEN 반올림을 적용한다.
b10.divide(b3, MathContext.DECIMAL32); // 3.333333
// 전체 자리수를 16개로 제한하고 HALF_EVEN 반올림을 적용한다.
b10.divide(b3, MathContext.DECIMAL64); // 3.333333333333333
// 전체 자리수를 34개로 제한하고 HALF_EVEN 반올림을 적용한다.
b10.divide(b3, MathContext.DECIMAL128); // 3.333333333333333333333333333333333
// 전체 자리수를 제한하지 않는다.
b10.divide(b3, MathContext.UNLIMITED); // java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. 예외가 발생한다.
# 참고자료
https://www.baeldung.com/java-bigdecimal-biginteger
https://jsonobject.tistory.com/466
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.