티스토리 뷰

SW 개발/Java

Java, BigDecimal 사용법 정리

지단로보트 2019.02.12 22:09

BigDecimal?

  • BigDecimalJava 언어에서 숫자를 정밀하게 저장하고 표현할 수 있는 유일한 방법이다.
  • 소수점을 저장할 수 있는 가장 크기가 큰 타입인 double은 소수점의 정밀도에 있어 한계가 있어 값이 유실될 수 있다.
  • Java 언어에서 돈과 소수점을 다룬다면 BigDecimal은 선택이 아니라 필수이다.
  • BigDecimal의 유일한 단점은 느린 속도와 기본 타입보다 조금 불편한 사용법 뿐이다.

double, 무엇이 문제인가?

  • 소수점 이하의 수를 다룰 때 double 타입은 사칙연산시 아래와 같이 우리가 기대한 값과 다른 값을 출력한다. 이유는 double 타입이 내부적으로 수를 저장할 때 이진수의 근사치를 저장하기 때문이다. 저장된 수를 다시 십진수로 표현하면서 아래와 같은 문제가 발생한다. 아래 설명할 BigDecimal 타입은 내부적으로 수를 십진수로 저장하여 아주 작은 수과 큰 수의 연산에 대해 거의 무한한 정밀도를 보장한다. [관련 링크1] [관련 링크2]
double a = 10.0000;
double b = 3.0000;

// 기대값: 13
// 실제값: 13.000001999999999
a + b;

// 기대값: 7
// 실제값: 6.999999999999999
a - b;

// 기대값: 30
// 실제값: 30.000013000000997
a * b;

// 기대값: 3.33333...
// 실제값: 3.333332555555814
a / b;

BigDecimal 기본 용어

  • precision: 숫자를 구성하는 전체 자리수라고 생각하면 편하나, 정확하게 풀이하면 왼쪽부터 0이 아닌 수가 시작하는 위치부터 오른쪽부터 0이 아닌 수로 끝나는 위치까지의 총 자리수이다. unscale과 동의어이다. (ex: 012345.67890의 precision은 11이 아닌 9이다.)
  • scale: 전체 소수점 자리수라고 생각하면 편하나, 정확하게 풀이하면 소수점 첫째 자리부터 오른쪽부터 0이 아닌 수로 끝나는 위치까지의 총 소수점 자리수이다. fraction과 동의어이다. (ex: 012345.67890의 scale은 4이다. 하지만 0.00, 0.0의 scale은 모두 1이다.) BigDecimal32bit의 소수점 크기를 가진다.
  • DECIMAL128: IEEE 754-2008에 의해 표준화된, 부호와 소수점을 수용하며, 최대 34자리까지 표현 가능한 10진수를 저장할 수 있는 형식이다. 2018년 미국 정부의 총 부채액이 15조 7천 500억 달러로 총 14자리 임을 감안하면, 금융권에서 처리되는 대부분의 금액을 수용할 수 있는 크기이다. Java에서는 BigDecimal 타입을 통해 공식적으로 지원한다.

BigDecimal 기본 상수

  • float, double 타입과 다르게 BigDecimal 타입은 초기화가 장황한 편이다. 그래서, 자주 쓰는 0, 1, 100은 쓰기 편하게 미리 상수로 정의되어 있다.
// 흔히 쓰이는 값은 상수로 정의
// 0
BigDecimal.ZERO

// 1
BigDecimal.ONE

// 10
BigDecimal.TEN

BigDecimal 초기화

  • double 타입으로 부터 BigDecimal 타입을 초기화하는 방법으로 가장 안전한 것은 문자열의 형태로 생성자에 전달하여 초기화하는 것이다. double 타입의 값을 그대로 전달할 경우 앞서 사칙연산 결과에서 본 것과 같이 이진수의 근사치를 가지게 되어 예상과 다른 값을 얻을 수 있다. [관련 링크]
// double 타입을 그대로 초기화하면 기대값과 다른 값을 가진다.
// 0.01000000000000000020816681711721685132943093776702880859375
new BigDecimal(0.01);

// 문자열로 초기화하면 정상 인식
// 0.01
new BigDecimal("0.01");

// 위와 동일한 결과, double#toString을 이용하여 문자열로 초기화
// 0.01
BigDecimal.valueOf(0.01);

BigDecimal 비교 연산

  • BigDecimal은 기본 타입이 아닌 오브젝트이기 때문에 특히, 동등 비교 연산을 유의해야 한다. double 타입을 사용하던 습관대로 무의식적으로 == 기호를 사용하면 예기치 않은 연산 결과를 초래할 수 있다.
BigDecimal a = new BigDecimal("2.01");
BigDecimal b = new BigDecimal("2.010");

// 객체의 레퍼런스 주소에 대한 비교 연산자로 무의식적으로 값의 비교를 위해 사용하면 오동작
// false
a == b;

// 값의 비교를 위해 사용, 소수점 맨 끝의 0까지 완전히 값이 동일해야 true 반환
// false
a.equals(b);

// 값의 비교를 위해 사용, 소수점 맨 끝의 0을 무시하고 값이 동일하면 0, 적으면 -1, 많으면 1을 반환
// 0
a.compareTo(b);

BigDecimal 사칙 연산

  • Java에서 BigDecimal 타입의 사칙 연산 방법은 아래와 같다. 보다시피 double 타입보다 장황하고 귀찮은 편이다. (아래 설명할 Kotlin에서는 double 타입을 사용하는 것처럼 매우 간결한 문법을 제공한다.)
BigDecimal a = new BigDecimal("10");
BigDecimal b = new BigDecimal("3");

// 더하기
// 13
a.add(b);

// 빼기
// 7
a.subtract(b);

// 곱하기
// 30
a.multiply(b);

// 나누기
// 3.333333...
// java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
a.divide(b);

// 나누기
// 3.333
a.divide(b, 3, RoundingMode.HALF_EVEN);

// 나누기 후 나머지
// 전체 자리수를 34개로 제한
// 1
a.remainder(b, MathContext.DECIMAL128);

// 절대값
// 3
new BigDecimal("-3").abs();

// 두 수 중 최소값
// 3
a.min(b);

// 두 수 중 최대값
// 10
a.max(b);

BigDecimal 소수점 처리

  • RoundingMode.HALF_EVENJava의 기본 반올림 정책으로 금융권에서 사용하는 Bankers Rounding와 동일한 알고리즘이다. 금융권에서는 시스템 개발시 혼란을 막기 위해 요구사항에 반올림 정책을 명확히 명시하여 개발한다.
// 소수점 이하를 절사한다.
// 1
new BigDecimal("1.1234567890").setScale(0, RoundingMode.FLOOR);

// 소수점 이하를 절사하고 1을 증가시킨다.
// 2
new BigDecimal("1.1234567890").setScale(0, RoundingMode.CEILING);
// 음수에서는 소수점 이하만 절사한다.
// -1
new BigDecimal("-1.1234567890").setScale(0, RoundingMode.CEILING);

// 소수점 자리수에서 오른쪽의 0 부분을 제거한 값을 반환한다.
// 0.9999
new BigDecimal("0.99990").stripTrailingZeros();

// 소수점 자리수를 재정의한다.
// 원래 소수점 자리수보다 작은 자리수의 소수점을 설정하면 예외가 발생한다.
// java.lang.ArithmeticException: Rounding necessary
new BigDecimal("0.1234").setScale(3);

// 반올림 정책을 명시하면 예외가 발생하지 않는다.
// 0.123
new BigDecimal("0.1234").setScale(3, RoundingMode.HALF_EVEN);

// 소수점을 남기지 않고 반올림한다.
// 0
new BigDecimal("0.1234").setScale(0, RoundingMode.HALF_EVEN);
// 1
new BigDecimal("0.9876").setScale(0, RoundingMode.HALF_EVEN);

BigDecimal 나누기 처리

BigDecimal b10 = new BigDecimal("10");
BigDecimal b3 = new BigDecimal("3");

// 나누기 결과가 무한으로 떨어지면 예외 발생
// java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
b10.divide(b3);

// 반올림 정책을 명시하면 예외가 발생하지 않음
// 3
b10.divide(b3, RoundingMode.HALF_EVEN);

// 반올림 자리값을 명시
// 3.333333
b10.divide(b3, 6, RoundingMode.HALF_EVEN);

// 3.333333333
b10.divide(b3, 9, RoundingMode.HALF_EVEN);

// 전체 자리수를 7개로 제한하고 HALF_EVEN 반올림을 적용한다.
// 3.333333
b10.divide(b3, MathContext.DECIMAL32);

// 전체 자리수를 16개로 제한하고 HALF_EVEN 반올림을 적용한다.
// 3.333333333333333
b10.divide(b3, MathContext.DECIMAL64);

// 전체 자리수를 34개로 제한하고 HALF_EVEN 반올림을 적용한다.
// 3.333333333333333333333333333333333
b10.divide(b3, MathContext.DECIMAL128);

// 전체 자리수를 제한하지 않는다.
// java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. 예외가 발생한다.
b10.divide(b3, MathContext.UNLIMITED);

Kotlin에서의 BigDecimal

  • Kotlin에서의 BigDecimal 연산은 놀라울 정도로 간단하다. 두 객체 간의 메써드 연산자 대신 +, -, *, /, %, .. 연산자를 사용하면 된다. 나누기 연산자에 대해서만 유의하면 된다.
val a: BigDecimal = BigDecimal(10)
val b: BigDecimal = BigDecimal(3)
val c: BigDecimal = 3.0.toBigDecimal()

// equalsTo()와 동일하게 소수점 마지막의 0까지 비교
// false
println(b == c)

// compareTo()는 소수점 마지막의 0을 무시
// true
println(b.compareTo(c) == 0)

// 이 경우 == 기호는 equalsTo()가 아닌 compareTo()로 작동
// true
println(b <= c)

// 이 경우 == 기호는 equalsTo()가 아닌 compareTo()로 작동
// true
println(b >= c)

// add()와 동일
// 13.0
println(a + b)

// subtract()와 동일
// 7.0
println(a - b)

// multiply()와 동일
// 30.00
println(a * b)

// divide(other, RoundingMode.HALF_EVEN)와 동일
// 3.3
println(a / b)

// 34자리 정밀도로 나누기 처리
// 3.333333333333333333333333333333333
println(a.divide(b, MathContext.DECIMAL128))

// 연산자 오버로드 선언
// 프로젝트 내에서 동일 연산자 메써드에 대해 1번만 선언 가능
operator fun BigDecimal.div(other: BigDecimal): BigDecimal = this.divide(other, 6, RoundingMode.HALF_EVEN)

// 연산자 오버로드 선언 후 결과 확인
// 3.333333
println(a / b)
  • 위를 응용하면 특정 연산자 오버로드가 선언된 클래스를 아래와 같이 별도로 분리해두는 것이 가독성과 관리 측면에서 좋다.
package com.jsonobject.example

import java.math.BigDecimal
import java.math.RoundingMode

operator fun BigDecimal.div(other: BigDecimal): BigDecimal = this.divide(other, BigDecimalUtils.SCALE_SIX, BigDecimalUtils.BANKERS_ROUNDING_MODE)

class BigDecimalUtils {

    companion object {

        const val SCALE_SIX = 6
        val BANKERS_ROUNDING_MODE = RoundingMode.HALF_EVEN
    }
}
  • 주의할 점은 연산자를 사용하는 클래스 내에서 반드시 오버로드가 선언된 클래스를 import해야 오버로드가 정상적으로 적용된다.
import com.jsonobjet.example.bigdecimal.div

BigDecimal 문자열 변환 출력

  • .setScale()을 사용하여 소수점 자리수를 제한하면 원본의 소수점 값은 상실해 버린다. 문자열로 출력하는 것이 목적이라면 NumberFormat 클래스를 사용하는 것이 적합하다.
NumberFormat format = NumberFormat.getInstance();
format.setMaximumFractionDigits(6);
format.setRoundingMode(RoundingMode.HALF_EVEN);
// 0.123457
format.format(new BigDecimal("0.1234567890"));

MySQL과 BigDecimal

  • MySQL 또한 Java와 동일한 문제를 가지고 있다. FLOAT, DOUBLE 타입에 소수를 가진 수를 저장할 경우 앞서와 동일한 연산의 정확도 문제가 발생한다. 이를 해결하기 위해 MySQLBigDecimal 타입에 대응하는 DECIMAL 타입을 제공한다. 컬럼 선언 방법은 아래와 같다.
foo DECIMAL(5,2) DEFAULT 0.00 NOT NULL
  • DECIMAL 타입 선언시 괄호 안의 숫자의 의미는 PRECISION, SCALE을 의미한다. (5,2)의 경우 전체 자리수는 5, 소수점 이하 자리수는 2로 선언하겠다는 의미이다.(-999.99~999.99) 괄호를 생략하는 것도 가능한데 이 경우 기본값으로 (10,0)이 적용된다. PRECISION의 최대값은 65로 산업 표준인 DECIMAL128을 충분히 수용할 수 있다. SCALE의 최대값은 30으로 PRECISION보다 클 수 없다.
  • 만약, 지정된 소수 자리수보다 많은 값을 저장할 경우, 지정된 소수 자리수 이하는 절사(floor)된다. [관련 링크] 절사보다 반올림을 선호할 경우, 바로 아래 설명한 애플리케이션 레벨에서의 반올림 처리를 하면 된다.

JPA에서의 BigDecimal 처리

  • JDBC에서 MySQL/MariaDBDECIMAL 타입은 ResultSet 인터페이스의 getBigDecimal(), getString() 2개 메써드로 획득이 가능하다. [관련 링크] JPA 또한 별도의 작업 없이 엔티티 필드에 BigDecimal 타입을 사용하여 처리하면 된다.
  • 만약, 데이터베이스 저장시 소수점 이하 자리수와 반올림 방법을 자동으로 처리되게 하고 싶다면 JPA가 제공하는 커스텀 컨버터를 제작하면 된다. 커스텀 컨버터 작성 예는 아래와 같다. Kotlin으로 작성하였다.
// 소수점 이하 6자리에서 Bankers Rounding을 적용하여 BigDecimal 값을 생성한 후 데이터베이스에 저장하는 역할의 컨버터
class BigDecimalScale6WithBankersRoundingConverter : AttributeConverter<BigDecimal, String> {

    companion object {

        const val SCALE_SIX = 6
        val BANKERS_ROUNDING_MODE = RoundingMode.HALF_EVEN
    }

    override fun convertToDatabaseColumn(attribute: BigDecimal?): String? {

        return when (attribute?.scale()) {
            null -> null
            SCALE_SIX -> attribute.toString()
            else -> attribute.setScale(SCALE_SIX, BANKERS_ROUNDING_MODE).toString()
        }
    }

    override fun convertToEntityAttribute(dbData: String?): BigDecimal? {

        return when (dbData) {
            null -> null
            else -> BigDecimal(dbData)
        }
    }
}
  • 작성한 커스텀 컨버터를 엔티티에 적용하는 예는 아래와 같다.
// DECIMAL(21,6) 타입에 대한 맵핑 상세 정의
// 숫자 범위는 -999999999999999.999999 ~ +999999999999999.999999
@Column(name = "foo", nullable = false, precision = 21, scale = 6)
@Digits(integer = 15, fraction = 6)
@Convert(converter = BigDecimalScale6WithBankersRoundingConverter::class)
var foo: BigDecimal? = BigDecimal.ZERO

Spring Data MongoDB에서의 BigDecimal 처리

  • Spring Data MongoDB는 기본적으로 BigDecimal 타입의 필드를 String 타입으로 저장하고 읽어들인다. 문제는 String 타입의 필드는 도큐먼트 간의 정렬 및 연산시 의도하지 않은 결과를 초래할 수 있다. [관련 링크] 이러한 문제를 예방하려면 String이 아닌 Decimal128(2016-11-29 출시된 MongoDB 3.4부터 지원함에 유의) 타입으로 저장하도록 컨버터를 제작해야 한다. MongoDB의 경우 JPA와는 다르게 개별 필드 레벨로는 컨버터 생성이 불가능하고 별도의 통합된 org.springframework.data.mongodb.core.convert.CustomConversions 커스텀 빈을 작성해야 한다.
  • 첫번째 방법은 아래와 같이 개별 컨버터를 따로 만드는 것이다. 저장할 때와 불러올 때의 컨버터를 각각 따로 만들어야 한다.
package com.jsonobject.example;

import java.math.BigDecimal;
import org.bson.types.Decimal128;
import org.springframework.core.convert.converter.Converter;

public class Decimal128ToBigDecimalConverter implements Converter<Decimal128, BigDecimal> {

    @Override
    public BigDecimal convert(Decimal128 source) {

        return source == null ? null : source.bigDecimalValue();
    }
}
package com.jsonobject.example;

import java.math.BigDecimal;
import org.bson.types.Decimal128;
import org.springframework.core.convert.converter.Converter;

public class BigDecimalToDecimal128Converter implements Converter<BigDecimal, Decimal128> {

    @Override
    public Decimal128 convert(BigDecimal source) {

        return source == null ? null : new Decimal128(source);
    }
}
  • 앞서 제작한 컨버트를 적용하여 컨버터 빈을 생성하면 첫번째 방법은 적용이 완료된다.
package com.jsonobject.example;

import java.util.Arrays;
import com.jsonobject.example.BigDecimalToDecimal128Converter;
import com.jsonobject.example.Decimal128ToBigDecimalConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.convert.CustomConversions;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;

@Configuration
public class MongoConfig {

    @Autowired
    private MongoDbFactory mongoFactory;

    @Autowired
    private MongoMappingContext mongoMappingContext;

    @Bean
    public MappingMongoConverter mongoConverter() {

        DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoFactory);
        MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
        mongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
        mongoConverter.setCustomConversions(new CustomConversions(Arrays.asList(
            new BigDecimalToDecimal128Converter(),
            new Decimal128ToBigDecimalConverter()
        )));

        return mongoConverter;
    }
}
  • 두번째 방법은 훨씬 간단하다. CustomConversions 빈만 생성하면 된다. 개별 컨버터를 제작할 필요도 없다.
package com.jsonobject.example;

import java.math.BigDecimal;
import java.util.Arrays;
import org.bson.types.Decimal128;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mongodb.core.convert.CustomConversions;

@Configuration
public class MongoConfig {

    @Bean
    CustomConversions customConversions() {

        Converter<Decimal128, BigDecimal> decimal128ToBigDecimal = new Converter<Decimal128, BigDecimal>() {
            @Override
            public BigDecimal convert(Decimal128 source) {

                return source == null ? null : source.bigDecimalValue();
            }
        };

        Converter<BigDecimal, Decimal128> bigDecimalToDecimal128 = new Converter<BigDecimal, Decimal128>() {
            @Override
            public Decimal128 convert(BigDecimal source) {

                return source == null ? null : new Decimal128(source);
            }
        };

        return new CustomConversions(Arrays.asList(decimal128ToBigDecimal, bigDecimalToDecimal128));
    }
}

BigDecimal과 Java Stream

// POJO 목록에서 BigDecimal 타입을 가진 특정 필드의 합을 반환
BigDecimal sumOfFoo = fooList.stream()
    .map(FooEntity::getFooBigDecimal)
    .filter(foo -> Objects.nonNull(foo))
    .reduce(BigDecimal.ZERO, BigDecimal::add);

// 특정 BigDecimal 필드를 기준으로 오름차순 정렬된 리스트를 반환
foolist.stream()
    .sorted(Comparator.comparing(it -> it.getAmount()))
    .collect(Collectors.toList());

// 위와 동일한 기능, 정렬된 새로운 리스트를 반환하지 않고 원본 리스트를 바로 정렬
foolist.sort(Comparator.comparing(it -> it.getAmount()));

AtomicBigDecimal

  • Google Guava는 서로 다른 쓰레드에서 하나의 double 변수에 접근시 동시성을 보장하는 com.google.common.util.concurrent.AtomicDouble을 제공한다. 현재까지, AtomicBigDecimal은 제공되지 않는데 필요할 경우 java.util.concurrent.atomic.AtomicReference를 이용하여 아래와 같이 구현할 수 있다.
package com.jsonobject.example;

import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicReference;

public class AtomicBigDecimal {

    private final AtomicReference<BigDecimal> valueHolder = new AtomicReference(BigDecimal.ZERO);

    public AtomicBigDecimal() {
    }

    public AtomicBigDecimal(BigDecimal initialValue) {

        valueHolder.set(initialValue);
    }

    public AtomicBigDecimal(double initialValue) {

        this(BigDecimal.valueOf(initialValue));
    }

    public BigDecimal get() {

        return valueHolder.get();
    }

    public void set(BigDecimal initialValue) {

        valueHolder.set(initialValue);
    }

    public BigDecimal addAndGet(BigDecimal delta) {

        return valueHolder.updateAndGet(x -> x.add(delta));
    }

    public BigDecimal addAndGet(double delta) {

        return this.addAndGet(BigDecimal.valueOf(delta));
    }

    public BigDecimal getAndAdd(BigDecimal delta) {

        return valueHolder.getAndUpdate(x -> x.add(delta));
    }

    public BigDecimal getAndAdd(double delta) {

        return this.getAndAdd(BigDecimal.valueOf(delta));
    }

    public double doubleValue() {

        return this.get().doubleValue();
    }

    public String toString() {

        return this.get().toString();
    }
}

BigDecimal 타입의 JSON 문자열 변환

  • 지금까지 알아본 BigDecimal의 애플리케이션 내부 연산과 저장소 말고도 신경 써야 할 것이 있다. 바로, 외부 서비스 간의 API 요청-응답 처리이다. JSON 스펙에서는 BigDecimal 타입의 표현 방법에 대해 명확히 규정하고 있지 않다. 그래서 API 응답을 표현할 때 혹시 모를 소수점 이하에서의 데이터 유실을 확실하게 예방하려면 BigDecimal을 숫자가 아닌 문자열로 응답해야 한다. [관련 링크] 아래는 Jackson 라이브러리를 사용하여 POJO-JSON 변환시 소수점 이하 6자리에서 Bankers Rounding을 적용하여 응답하는 커스텀 JsonSerializer를 제작한 예이다.
class BigDecimalScale6WithBankersRoundingSerializer : JsonSerializer<BigDecimal>() {

    companion object {

        const val SCALE_SIX = 6
        val BANKERS_ROUNDING_MODE = RoundingMode.HALF_EVEN
    }

    override fun serialize(value: BigDecimal?, gen: JsonGenerator?, serializers: SerializerProvider?) {

        gen?.writeString(value?.setScale(SCALE_SIX, BANKERS_ROUNDING_MODE).toString())
    }
}
  • 앞서 제작한 JsonSerializer를 아래와 같이 POJO에 명시하면 의도한대로 JSON 응답 처리를 할 수 있다.
@JsonSerialize(using = BigDecimalScale6WithBankersRoundingSerializer::class)
var fooDecimal: BigDecimal? = BigDecimal.ZERO,

BigDecimal 유닛 테스트

  • BigDecimalJUnit에서 Assertion 기능을 제공하지 않아 유닛 테스트가 불편하다. AssertJ 라이브러리를 이용하면 아래와 같이 네이티브하게 BigDecimal에 대한 유닛 테스트를 수행할 수 있다. [라이브러리 링크]
BigDecimal a = BigDecimal.valueOf(0.1);
BigDecimal b = BigDecimal.valueOf(0.10);
BigDecimal c = BigDecimal.valueOf(0.101);
BigDecimal d = BigDecimal.valueOf(0.001);

// equals()와 동일하기 때문에 소수점 마지막 0까지 동일해야 true
// false
assertThat(a).isEqualTo(b));

// compareTo()와 동일하기 때문에 소수점 마지막 0이 달라도 true
// true
assertThat(a).isEqualByComparingTo(b);

// 두 수가 주어진 오차 범위를 만족하면 true
// true
assertThat(a).isCloseTo(c, within(d));

BigDecimal 관련 라이브러리

  • big-math 라이브러리는 java.lang.Math 클래스의 BigDecimal 버전이라고 할 수 있다. BigDecimal 기반 연산과 관련된 여러 유용한 기능을 제공한다. [라이브러리 링크]

BigDecimal과 통화

  • BigDecimal은 돈을 다루는데 있어 가장 확실하고 안전한 타입이다. 하지만, 여러 국가의 통화를 표현하기에는 부족함이 있다. 이러한 통화를 다루기 위한 Java 표준으로 JSR 354가 존재한다. 그리고 이를 구현한 구현체 라이브러리로 Moneta가 존재한다. 여러 국가의 통화를 직접 처리하는 것보다 이러한 정식 구현체를 적용하는 것이 효율적이다. [라이브러리 링크]
  • 아래는 build.gradle에 해당 라이브러리의 종속성을 추가하는 방법이다.
compile group: 'org.javamoney', name: 'moneta', version: '1.3', ext: 'pom' // JavaMoney
compile group: 'org.javamoney.lib', name: 'javamoney-lib', version: '1.0', ext: 'pom' // JavaMoney
compile group: 'org.javamoney.lib', name: 'javamoney-calc', version: '1.0' // JavaMoney Caculations

참고 글

댓글
댓글쓰기 폼