100%를 한번에 바꾸는건 어려워도 1%를 100번 바꾸는건 쉽다.

생각정리 자세히보기

개발/Node.js (NestJS)

[Node.js] BigInt 처리 방법

dc-choi 2025. 2. 13. 13:58
반응형

최근에 Node.js에서 BigInt값을 처리하게 되면서 오류가 발생하였습니다. 그에 따라 해당 문제가 왜 발생하는지, 어떻게 해결해야 하는지에 대한 트러블 슈팅을 기록하고자 합니다.

왜 발생하였는가?

해당 문제가 발생한 이유는 MySQL의 BigInt와 Node.js상에서의 number값에서 처리할 수 있는 범위가 달라 발생한 문제였습니다.

 

보편적으로 MySQL에서의 PK의 타입은 Int나 BigInt로 저장하여 Auto Increment를 사용하게 됩니다. PK의 경우 대부분 BigInt를 사용합니다. 저는 기존에는 Number 타입을 통해 해당 값을 처리해왔는데요. 현재 시스템 상 BigInt를 처리하기 위해서는 Number 타입을 사용하는 것이 아닌 BigInt를 사용해야 합니다. 

 

 BigInt를 사용해야 하는가에 대해 알아보기 위해서는 우선 Number와 BigInt의 차이점에 대해서 알아보아야 합니다. 우선 Number 타입의 경우 53비트까지만 정수를 안정적으로 처리할 수 있습니다. 이는 Number타입이 IEEE 754 배정밀도 부동소수점을 사용하기 때문입니다.

IEEE 754 배정밀도 부동소수점?

배정밀도 부동소수점은 부동소수점 숫자를 64비트(8바이트)로 저장하는 방식입 니다. 일반적으로 double 타입으로 사용됩니다. 세부 구조는 다음과 같습니다.

S | EEEEEEEEEEE | MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM

S: 1비트(부호: 0=양수, 1=음수)
E: 11비트(지수: 수의 크기를 결정, 2의 거듭제곱)
M: 52비트(가수: 실제 숫자의 유효숫자, 정밀도)

 

실제로 값을 저장하는 부분인 가수의 크기는 총 52비트이지만 첫번째 1이 암묵적으로 존재하기 때문에 최대 53비트까지 처리됩니다. 따라서 2^53 = 9,007,199,254,740,992 까지 안정적으로 처리할 수 있고 그 이상의 값이 들어오는 경우 부동소수점의 오차가 발생하게 됩니다. 이는 다른 프로그래밍 언어에서도 발생하고 있는 문제입니다.

어떻게 해결해야 하는가?

저는 요청시 해당 값을 예외처리하기 위해 다음과 같은 코드를 추가적으로 작성하였습니다.

import {
    ValidationOptions,
    ValidatorConstraint,
    ValidatorConstraintInterface,
    registerDecorator,
} from "class-validator";

@ValidatorConstraint({ async: false })
export class IsBigIntConstraint implements ValidatorConstraintInterface {
    validate(value: any): boolean {
        // 값이 BigInt이거나 BigInt로 변환 가능해야 함
        return typeof value === "bigint" || (!isNaN(value) && BigInt(value).toString() === value.toString());
    }

    defaultMessage(): string {
        return "Value must be a BigInt.";
    }
}

export function IsBigInt(validationOptions?: ValidationOptions) {
    return function (object: object, propertyName: string) {
        registerDecorator({
            target: object.constructor,
            propertyName: propertyName,
            options: validationOptions,
            constraints: [],
            validator: IsBigIntConstraint,
        });
    };
}

class-vaildator를 통해 예외처리를 진행하고 있어서 해당 요청값이 들어오는 경우 예외처리를 하고 있습니다.

 

하지만 /api/v1/order/1 처럼 path param으로 BigInt 값을 처리해야하는 경우는 처리하지 못합니다. 이 경우 다음과 같이 처리합니다.

import { BadRequestException, Injectable, PipeTransform } from "@nestjs/common";

@Injectable()
export class ParseBigIntPipe implements PipeTransform<string, bigint> {
    transform(value: string): bigint {
        try {
            return BigInt(value);
        } catch (error) {
            throw new BadRequestException(`Validation failed. "${value}" is not a valid bigint.`);
        }
    }
}

값을 변환하는 과정에서 오류가 발생하기 때문에 Pipe를 사용하여 값을 변환하도록 하였습니다.

결론

식별자가 2^53 = 9,007,199,254,740,992을 넘어가지 않으면 Number 타입을 써도 상관없습니다. 하지만 데이터 범위가 2^53을 넘는 값을 다루어야 하는 경우 BigInt를 사용하는 것을 고려해야합니다.

반응형