๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Java

MariaDB decimal ํƒ€์ž…๊ณผ Java BigDecimal ๋™์ผํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ

by ํ‘์‹œ๋ฐ” 2023. 8. 5.

๐Ÿ˜‚ ๋ฐฐ๊ฒฝ

์ •ํ™•ํ•œ ์‹ค์ˆ˜ ๊ฐ’ ๊ณ„์‚ฐ์ด ํ•„์š”ํ•ด Java์˜ BigDecimal์„ ์‚ฌ์šฉํ•ด ๊ตฌํ˜„ํ•œ ํŠน์ • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์žˆ์—ˆ๋‹ค.

 

ํ•ด๋‹น ๋กœ์ง์€ ์กฐํšŒ ๊ธฐ๋Šฅ์ด ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ๋Š”๋ฐ, ํ•˜๋‚˜๋Š” DB ๊ฐ’์„ ์กฐํšŒํ•˜๋Š” ํ˜„์žฌ ์กฐํšŒ, ๋‹ค๋ฅธ ํ•˜๋‚˜๋Š” ๋ฏธ๋ž˜ ๋‚ ์งœ๋ฅผ ์„ ํƒํ•˜๋ฉด ์˜ˆ์ƒ๋œ ๊ฐ’์„ ๊ณ„์‚ฐํ•ด์„œ ๋ณด์—ฌ์ฃผ๋Š” ๋ฏธ๋ž˜ ์กฐํšŒ๊ฐ€ ์žˆ๋‹ค.

 

์–ด๋Š ๋‚  ๊ธฐํš์ž๊ฐ€ ํ•ด๋‹น ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ๊ด€๋ จ๋œ ํ™”๋ฉด์„ ์บก์ฒ˜ํ•ด์„œ ๋ณด์—ฌ์ฃผ๋ฉฐ, ์กฐํšŒ์™€ ๊ณ„์‚ฐ๋œ ๊ฐ’์ด ๋งž์ง€ ์•Š๋Š”๋‹ค๊ณ  ํ•˜๋ฉฐ ์˜๋ฌธ์„ ์ œ๊ธฐํ–ˆ๋‹ค.

 

ํ•ด๋‹น ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ  ๋‚˜์„œ ์•Œ๊ฒŒ ๋œ ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ ๊ณต์œ ํ•˜๊ณ ์ž ํ•œ๋‹ค.

๐Ÿ”Ž ๋ฌธ์ œ

ํ˜„์žฌ ์กฐํšŒ(DB ์กฐํšŒ)๋ฅผ ํ•˜๋ฉด 100.0 ๊ฐ’์ด ์ถœ๋ ฅ๋˜๊ณ , ๋ฏธ๋ž˜ ๋‚ ์งœ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ๊ณ„์‚ฐํ•ด์„œ ์กฐํšŒํ•˜๋ฉด 99.9๊ฐ€ ๋œจ๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

 

ํ•ด๋‹น ๋ฌธ์ œ๋Š” 100์„ 3์œผ๋กœ ๋‚˜๋ˆ„๋Š” ๊ฒฝ์šฐ ๋ฐœ์ƒํ–ˆ๋Š”๋ฐ, 100์„ 3์œผ๋กœ ๋‚˜๋ˆ„๋ฉด 33.333333333...์œผ๋กœ ๋‚˜์˜ค๊ฒŒ ๋˜๊ณ , 3๊ฐœ๋ฅผ ๋”ํ•˜๋ฉด 99.999999999... ๊ฐ€ ๋œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ DB ์กฐํšŒ๋Š” 100.0์ด๊ณ  ์™œ ๊ณ„์‚ฐ ์กฐํšŒ๋Š” 99.9์ธ ๊ฒƒ์ผ๊นŒ?

๐Ÿ“Œ ์›์ธ

MariaDB์˜ decimal(M, D)์€ D๋ฅผ ์ดˆ๊ณผํ•œ ์ฒซ ๋ฒˆ์งธ ์ž๋ฆฟ์ˆ˜์—์„œ ๋ฐ˜์˜ฌ๋ฆผ ์ฒ˜๋ฆฌํ•˜์ง€๋งŒ Java์˜ BigDecimal์€ ๋ณ„๋„๋กœ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์•˜๋˜ ๊ฒƒ์ด ๋ฌธ์ œ์˜€๋‹ค. (decimal(M,D)๋Š” M์€ ์ „์ฒด ์ž๋ฆฟ์ˆ˜, D๋Š” ์†Œ์ˆ˜์  ์ดํ•˜ ์ž๋ฆฟ์ˆ˜๋ฅผ ๊ฐ€์ง„ ์ˆซ์žํ˜•์„ ์˜๋ฏธํ•œ๋‹ค.)

 

์˜ˆ๋ฅผ ๋“ค์–ด decimal(16,10)์ธ ๊ฒฝ์šฐ ์†Œ์ˆ˜์ ์ด 10์ž๋ฆฌ๋ฅผ ๋„˜์–ด๊ฐ€๋Š” ์ฒซ ๋ฒˆ์งธ ์ž๋ฆฌ ์ฆ‰, 11๋ฒˆ์งธ ์ž๋ฆฌ ๊ฐ’์—์„œ ๋ฐ˜์˜ฌ๋ฆผ(5 ์ด์ƒ ์˜ฌ๋ฆผ)์ด ๋œ๋‹ค.

 

ํ•ด๋‹น ์ƒํ™ฉ์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ…Œ์ŠคํŠธ๋ฅผ ๊ตฌ์„ฑํ•ด์„œ ์žฌ๊ตฌ์„ฑํ•ด๋ดค๋‹ค. 

 

@Entity
@Table(name = "task")
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Task {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(precision = 16, scale = 10)
    private BigDecimal value;
}

@SpringBootTest
public class TaskServiceTest {

    @Autowired
    TaskRepository taskRepository;

    @Autowired
    EntityManager entityManager;

    @Test
    @Transactional
    void decimalIssue() {
        Task task = Task.builder()
                .value(new BigDecimal("99.99999999999"))
                .build();

        Task savedTask = taskRepository.save(task);
        entityManager.clear();

        Task result = taskRepository.findById(savedTask.getId()).get();
        System.out.println(result);
    }
}

 

์†Œ์ˆ˜๊ฐ€ 10 ์ž๋ฆฟ์ˆ˜๋ฅผ ๋„˜์–ด๊ฐ€๋Š” ๊ฐ’์„ BigDecimal๋กœ ์ €์žฅํ•˜๊ณ , ์กฐํšŒํ•ด์„œ ํ™•์ธํ•ด ๋ณธ๋‹ค.

 

ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋Š” 100์œผ๋กœ ๋‚˜์˜ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ, DB์—๋„ 100์œผ๋กœ ์ €์žฅ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

ํ•˜์ง€๋งŒ Java BigDecimal์€ ๋ณ„๋„๋กœ Scale ๋ฐ RoundingMode๋ฅผ ์„ค์ •ํ•ด์ฃผ์ง€ ์•Š์œผ๋ฉด ์—ฌ์ „ํžˆ 99.999999..๋กœ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ฒŒ ๋œ๋‹ค. ๋งŒ์•ฝ DB์— ์„ค์ •๋œ decimal๊ณผ ๋‹ค๋ฅธ ์ž๋ฆฟ์ˆ˜์—์„œ ๋ฐ˜์˜ฌ๋ฆผ ๋˜๋Š” ์˜ฌ๋ฆผ, ๋‚ด๋ฆผ ๋“ฑ์˜ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด DB์— ์ €์žฅ๋œ ๊ฐ’๊ณผ ๋‹ค๋ฅธ ๊ฐ’์ด ์กฐํšŒ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

๐Ÿ“• ํ•ด๊ฒฐ

DB ์ •์ฑ…๊ณผ ๋™์ผํ•˜๊ฒŒ ๋งž์ถ”์–ด ๊ณ„์‚ฐํ•˜๋Š” ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.  BigDecimal์€ ์ž๋ฆฟ์ˆ˜๋ฅผ ๋„˜์–ด๊ฐ€๋ฉด ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ์ง€ ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” setScale(int newScale, RoundingMode roundingMode) ๋ฉ”์„œ๋“œ๋ฅผ ์ง€์›ํ•œ๋‹ค.

(RoundingMode๋Š” ๊ฐ’์ด scale ๊ฐ’์„ ๋„˜์–ด๊ฐˆ ๊ฒฝ์šฐ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ์ง€ ์ •ํ•˜๋Š” enum)

 

new BigDecimal("99.99999999999").setScale(10, RoundingMode.HALF_UP);

 

REFERENCE

https://mariadb.com/kb/en/decimal/

https://docs.oracle.com/javase/8/docs/api/java/math/RoundingMode.html

๋Œ“๊ธ€