← 문제로

52. 대량 경험치 일괄 지급과 레벨업 계산 (C#)

난이도 중
내 리뷰 · C#
해설 · C#

해설 — 대량 경험치 일괄 지급과 레벨업 계산 (C#)

난이도: 중

요약

단일 처리 경로의 순수 계산 결함이다(동시성 아님). (A) 경험치를 32비트 int 로 누적해 대량 일괄 지급 시 오버플로가 나서 값이 음수로 손상되고, (B) 레벨업 루프가 최대 레벨을 클램프하지 않아 만렙을 넘어 성장하며 보상을 과다 지급한다(또는 오버플로와 맞물려 루프가 비정상 동작한다).

문제점

  • (A) 정수 오버플로 (int 누적): Exp 와 ExpToNext 반환형이 모두 int 다. 오프라인 누적 보상처럼 큰 amount (예: 20억 근처, 혹은 여러 번 누적)를 더하면 c.Exp += amount 가 int.MaxValue (약 21.4억)를 넘어 음수로 래핑된다. 그러면 c.Exp >= ExpToNext(Level) 가 거짓이 되어 거대한 경험치를 받고도 레벨이 전혀 오르지 않는다(또는 손상된 값으로 엉뚱하게 오른다). C# 은 기본 unchecked 라 예외도 없이 조용히 래핑된다.
    • 재현: 오프라인 14일 보상으로 2,100,000,000 exp 일괄 지급.
    • 근본 원인: 도메인 값의 범위를 넘는 타입 선택(누적/임계 계산에 int).
  • (B) 최대 레벨 클램프 누락: 루프가 Level < MaxLevel 을 확인하지 않아 만렙을 넘어 계속 오른다. 요구사항(초과분 폐기)을 위반해 스탯이 무한 인플레된다. 나아가 레벨이 커지면 ExpToNext=100*level*level 도 int 로 오버플로(예: level≈4632 이상에서 2^31 초과)해 임계가 음수가 되면 while 조건이 항상 참이 되어 폭주할 수 있다.
    • 근본 원인: "만렙에서 멈춘다"는 경계 규칙이 코드에 없음.

수정안

누적/임계 계산을 넉넉한 정수형(long)으로 올리고, 최대 레벨을 클램프하며 초과분을 폐기한다.

public sealed class Character
{
    public int Level = 1;
    public long Exp;                       // 넉넉한 폭
    public int StatPoints;
    public long ExpToNext(int level) => 100L * level * level;  // long 산술
}

public void GrantExp(Character c, long amount)
{
    if (amount <= 0) return;
    c.Exp += amount;                        // long 누적(현실 범위에서 오버플로 없음)
    while (c.Level < MaxLevel && c.Exp >= c.ExpToNext(c.Level))
    {
        c.Exp -= c.ExpToNext(c.Level);
        c.Level++;
        c.StatPoints += 5;
    }
    if (c.Level >= MaxLevel) c.Exp = 0;     // 초과분 폐기
}

극단적으로 큰 입력까지 방어하려면 checked { c.Exp += amount; } 로 오버플로를 예외화하거나 상한으로 포화(saturate)시키는 정책을 둔다.

더 나은 설계

  • 값 범위 명세: 경험치/재화 같은 누적값은 예상 최대치를 계산해 타입을 정하고, 필요하면 checked/포화 연산으로 경계를 강제.
  • 레벨/보상 테이블화: 레벨별 필요 경험치·보상을 데이터 테이블로 두고 만렙 캡을 상수로.
  • 경계 테스트: 만렙 도달, 만렙 근처 대량 지급, int 경계 근처 입력을 유닛 테스트로 고정.

면접 포인트

  • "조용한 정수 오버플로"(C# 기본 unchecked)가 만드는 논리 붕괴와 checked/long 대응.
  • 누적값의 타입은 도메인 최대치로 정한다 — int 는 21억에서 끝난다.
  • 만렙 클램프 같은 경계 규칙을 코드로 강제하고 경계 테스트로 고정.