(R) 큰 자릿수의 정수를 csv 포맷으로 제대로 출력하기

by Minho Lee — on

cover-image

회사에서 종종 특정 사용자들의 id값을 추출하는 일을 맡게 된다. 그러다보면 종종 나와야하는 값과 실제로 csv 파일에 추출된 결과물이 다를 때가 있다. 난 분명히 아무것도 건드린적이 없는데, 테이블에 있는 값과 추출된 값이 뭔가 다른 것 같다. 어떻게 된 일일까?

큰 자릿수의 정수를 csv 포맷으로 제대로 뽑아보자

문제가 발생한 상황

DB의 특정 테이블에 id 목록이 있다. R에서 DB로 쿼리를 날렸고, 결과물을 받아서 readr::write_csv() 함수를 통해 csv로 출력을 한다. 결과물이 아마 다음과 같이 나올 것이라고 생각한다.

...
10259972703
10259992203
10260001169
10260004683
10260033149
10260081123
...

하지만 출력된 결과물은 다음과 같다. csv 파일로 떨군 결과물을 터미널에서 head 명령어로 출력한 결과다.

...
10259972703
10259992203
1.026e+10
1.026e+10
10260033149
10260081123
...

오잉? 이게 무슨 일일까…? 절대로 겹쳐서는 안되는게 id값인데 어찌된 영문인지 정보가 유실되어 하나의 id인 것 처럼 변환되었다.

원인이 무엇일까?

자세한 내용은 잘 모르지만 아무래도 2^31 보다 큰 정수가 들어오다보니 처리하는 과정에서 문제가 생겼을 것이라 내심 짐작할 수 있었다. 문제는 저 케이스만 추출해서 결과를 확인해보면 문제가 재현되지 않는다는 것 ㅜㅜ

library('tidyverse')
data_frame(id = c(10259972703, 10259992203, 10260001169, 10260004683, 10260033149, 10260081123) %>% 
  format_csv %>% 
  cat

# id
# 10259972703
# 10259992203
# 10260001169
# 10260004683
# 10260033149
# 10260081123

3백만건의 id를 추출했는데 저런 케이스가 딱 1건 존재했다. 그밖에도 0이 많은 숫자들은 지수 표기로 자꾸 변환되는데 이것도 은근히 귀찮…

그래서 짐작가는 원인을 발견했는데도 불구하고 동일한 현상을 재현하는 것은 실패. 일단은 퇴근을 해야하니 문제를 해결할 방법 부터 찾아보기로 했다.

어떻게 해결할까?

실제로 사용한 방법은 (머리가 쓰기 싫어서) 가장 단순한 방법으로 처리했다. 애초에 DB에서 데이터를 받아올 때 문자열 포맷으로 변환되도록하여 표기가 변하지 않도록 고정시켜버렸다.

검색해보니 readr 패키지 제작자들은 이러한 자동 포맷 변환이 이슈가 아니라고 생각하는 것 같다. 링크 따라서 라이브러리 옵션 조정으로 해결하는 것은 일단 패쓰.

R의 기본 integer 자료형은 32비트이기 때문에 64비트 integer를 제공하는 라이브러리를 찾아보기로 했다. (나중에 알고보니 위 이슈 링크에도 언급되어 있더라) 그러다가 발견한 것이 bit64 라이브러리!

다음과 같이 bit64::as.integer64 함수를 통해 64비트 정수로 변환할 수 있다.

10110000001
# [1] 1.011e+10

bit64::as.integer64(10110000001)
# integer64
# [1] 10110000001

덧. 위 예제만 써놓으니 콘솔에서 표기 문제처럼 보일까봐. 여기서 발생한 문제는 csv 파일로 출력했을 때 발생하는 문제입니다. 파일로 뽑기는 귀찮아서 write.csv() 함수는 write.csv(df, file = '') 옵션을 통해 콘솔에 결과를 바로 출력하도록 하고, readr::write_csv() 함수는 df %>% readr::format_csv() %>% cat() 을 통해 파일 대신 콘솔에 결과를 출력하도록 했습니다.

#### 수정전 ####
data.frame(
  id = c(1, 1000, 10000000000, 10000100000)
) %>% 
  write.csv('', row.names = FALSE)
# "id"
# 1
# 1000
# 1e+10
# 10000100000

data.frame(
  id = c(1, 1000, 10000000000, 10000100000)
) %>% 
  format_csv() %>% cat()
# id
# 1
# 1e3
# 1e10
# 100001e5

#### 수정후 ####
data.frame(
  id = bit64::as.integer64(c(1, 1000, 10000000000, 10000100000))
) %>% 
  write.csv('', row.names = FALSE)
# "id"
# 1
# 1000
# 10000000000
# 10000100000

data.frame(
  id = bit64::as.integer64(c(1, 1000, 10000000000, 10000100000))
) %>% 
  format_csv() %>% cat()
# id
# 1
# 1000
# 10000000000
# 10000100000

급마무리

bit64::as.integer64 로 수치를 변환하는 경우에도 readr::write_csv 함수가 잘 동작하는 것을 확인하였다. 다음번에는 이렇게 요상한데서 시간 잡아먹지는 않았으면.. 그리고 이런 low 레벨의 문제들은 너무 어려운 것 같다….. 퇴근해야지

Comments