---
title: "부동소수점 reduction: 같은 세 숫자의 합이 1과 0으로 갈리는 이유"
author: "브레이브브라우저쓰자"
published_at: "2026-10-13T06:08:01.813Z"
updated_at: "2026-10-06T00:23:56.871Z"
tags: ["부동소수점 reduction"]
canonical_url: "https://ariser.io/@writer_a9817d5a8de0/%EB%B6%80%EB%8F%99%EC%86%8C%EC%88%98%EC%A0%90-reduction-%EA%B0%99%EC%9D%80-%EC%84%B8-%EC%88%AB%EC%9E%90%EC%9D%98-%ED%95%A9%EC%9D%B4-1%EA%B3%BC-0%EC%9C%BC%EB%A1%9C-%EA%B0%88%EB%A6%AC%EB%8A%94-%EC%9D%B4%EC%9C%A0"
ai_assisted: true
---

# 부동소수점 reduction: 같은 세 숫자의 합이 1과 0으로 갈리는 이유

이 글은 AI가 활용됐습니다.

[원문](https://ariser.io/@writer_a9817d5a8de0/%EB%B6%80%EB%8F%99%EC%86%8C%EC%88%98%EC%A0%90-reduction-%EA%B0%99%EC%9D%80-%EC%84%B8-%EC%88%AB%EC%9E%90%EC%9D%98-%ED%95%A9%EC%9D%B4-1%EA%B3%BC-0%EC%9C%BC%EB%A1%9C-%EA%B0%88%EB%A6%AC%EB%8A%94-%EC%9D%B4%EC%9C%A0)

동일한 입력 배열을 CPU에서 더하면 1이 나오고 가속기에서 더하면 0이 나왔다. 입력을 잃어버리지 않았고, 각각의 덧셈기도 올바르게 동작했다. 이런 일이 가능한 이유는 여러 항을 하나로 줄이는 부동소수점 reduction이 입력뿐 아니라 합을 묶는 순서에도 영향을 받기 때문이다.

병렬 계산은 덧셈을 트리로 펼치기 쉽다. 그 구조가 직렬 누산과 다른 위치에서 중간 결과를 반올림한다. 그래서 결과가 다르다는 관측만으로 어느 쪽의 회로가 잘못되었다고 결론낼 수 없다. 먼저 어떤 합산 순서를 약속했는지 알아야 한다.

## 세 숫자로 드러나는 순서의 효과

binary32, nearest-even 반올림을 가정하고 a=2^25=33,554,432, b=−2^25, c=1을 놓자. 세 입력은 모두 정확하게 표현된다. 무한 정밀도 정수로 더한 최종 합은 1이다.

먼저 a와 b를 더하면 정확히 상쇄되어 0이 된다. 이어 c를 더하면 (a+b)+c=1이다. 이 경로의 중간 결과와 마지막 결과는 모두 정확하다.

반대로 b와 c부터 더하면 정확한 중간 값은 −33,554,431이다. 하지만 이 크기에서 binary32의 인접한 후보 −33,554,432와 −33,554,430 사이 간격은 2다. 정확한 중간 값은 가운데이고, nearest-even은 유효숫자 끝 비트가 짝수인 −33,554,432를 고른다. 여기에 a를 더하면 0이다.

따라서 매 덧셈 뒤에 binary32로 반올림한 a+(b+c)는 0이 된다. 1이라는 작은 항은 첫 중간 반올림에서 사라졌다. 마지막 덧셈기가 정확하게 상쇄할수록 그 사라진 정보는 돌아오지 않는다. 이 예는 가상의 특정 칩 측정이 아니라 같은 형식의 반올림 규칙으로 직접 계산한 결과다.

NVIDIA의 floating-point 안내서도 기본 연산이 표준대로 동작하더라도 괄호를 바꾼 합이 달라질 수 있음을 설명한다. 직렬 합, FMA를 사용하는 계산, 트리로 묶는 병렬 계산을 구별한다. 위 세 숫자는 그 원리를 작은 입력으로 확인하기 위한 별도의 예다. [NVIDIA Floating Point 안내서 §2.2·§3](https://docs.nvidia.com/cuda/floating-point/index.html)

## 트리가 바꾸는 것은 기다리는 시간만이 아니다

8개 항의 직렬 누산은 앞의 합에 다음 항을 계속 더한다. 균형 잡힌 트리는 먼저 네 쌍을 더하고, 그 결과를 두 쌍으로 줄인 뒤 마지막 합을 만든다. 후자는 앞 결과를 기다리는 덧셈 단계가 세 층이라 병렬 회로를 구성하기 좋다. 그러나 두 구조의 중간 값은 다르며 반올림 오차가 생기는 위치도 다르다.

트리의 높이가 낮다는 이유만으로 모든 입력에서 더 정확하다고 말할 수는 없다. 비슷한 크기의 항끼리 먼저 묶으면 작은 항이 큰 항에 곧바로 흡수되는 일을 줄일 수 있다. 반면 어떤 항들이 상쇄하는지, 양수와 음수가 어디에서 만나는지에 따라 다른 묶음이 더 나을 수 있다. 앞의 세 항에서도 정확한 두 큰 수를 먼저 상쇄시킨 경로가 작은 항을 보존했다.

넓은 내부 누산은 또 다른 선택이다. 이 세 입력을 binary64로 먼저 합하면 두 괄호 모두 1이 된다. 그 폭에서는 b+1도 정확히 표현되기 때문이다. 그렇다고 유한한 넓은 형식에서 모든 길이의 배열과 모든 크기의 항에 결합법칙이 생기는 것은 아니다. 중간 정밀도를 늘리는 것과 정확한 합을 무한히 보존하는 것은 다르다.

회로에서는 넓은 누산기를 쓰는 면적과 지연, 부분 합을 저장하거나 이동하는 비용도 발생한다. 처리량 목표를 정한 뒤 실제 입력 분포와 정밀도 계약에서 비교해야 한다. 단순히 트리를 선택했다는 사실이나 큰 데이터셋에서 평균 오차가 작았다는 사실만으로 다른 조건을 대신할 수 없다.

## 같은 답의 의미를 먼저 정한다

부동소수점 reduction에 요구하는 계약은 몇 가지로 나눠 생각할 수 있다. 특정 직렬 순서를 그대로 재현해야 한다면 그 중간 반올림까지 맞춰야 한다. 같은 트리와 형식으로 매번 동일한 결과를 내는 것이 목표라면 트리의 묶음과 각 단계의 정밀도를 고정해야 한다. 수학적인 합에 허용오차 이내로 가까우면 되는 경우에는 기준과 오차 판정 방식을 정의해야 한다.

이 계약들은 자동으로 같아지지 않는다. 동일한 결과를 반복해서 낸다는 성질이 가장 정확한 결과를 뜻하지 않고, 작은 평균 오차가 매번 동일한 비트열을 뜻하지도 않는다. 입력을 여러 작업 단위로 나눌 때 부분 합의 구간이나 합치는 순서까지 바뀐다면, 무엇을 고정해야 하는지 명확히 해야 한다.

오차 기준에서는 0 근처도 따로 생각해야 한다. 큰 양수와 음수가 상쇄되어 정확한 합이 아주 작아지면, 작은 절대 차이도 상대오차로는 커진다. 흔히 절대오차 한도와 기준값 크기에 비례하는 한도를 함께 쓰는 이유다. 어느 한도를 선택할지는 결과의 용도와 허용 범위에 달려 있으며, 임의의 상수 하나가 모든 계산을 검증해 주지는 않는다.

처음의 1과 0 차이는 순서에 따른 올바른 반올림으로도 발생한다. 오류 여부는 그 차이가 약속된 합산 순서 또는 정밀도와 어긋났는지로 판단해야 한다. reduction을 설계하고 검증할 때 입력과 출력 형식 사이에 ‘어떤 중간 합을 만들었는가’라는 항목을 넣으면, 병렬화가 수치 결과를 바꾸는 지점을 설명할 수 있다.

