문제

다음 c# 코드를 고려하십시오.

double result1 = 1.0 + 1.1 + 1.2;
double result2 = 1.2 + 1.0 + 1.1;

if (result1 == result2)
{
    ...
}

결과 1은 항상 결과 2와 동일해야합니까? 문제는 그렇지 않습니다. 결과 1은 3.3이고 결과 2는 3.3000000000000003입니다. 유일한 차이점은 상수의 순서입니다.

반올림 문제가 발생할 수있는 방식으로 복식이 구현된다는 것을 알고 있습니다. 절대 정밀도가 필요한 경우 대신 소수를 사용할 수 있다는 것을 알고 있습니다. 또는 if 문에서 math.round ()를 사용할 수 있습니다. 나는 C# 컴파일러가 무엇을하고 있는지 이해하고 싶어하는 괴상한 일입니다. 누구든지 나에게 말할 수 있습니까?

편집하다:

지금까지 Floating Point 산술을 읽거나 CPU가 복식을 처리하는 방법의 고유 한 부정확성에 대해 이야기 한 모든 분들께 감사드립니다. 그러나 나는 내 질문의 주요 추력이 여전히 답이 없다고 생각합니다. 그것은 올바르게 표현하지 않은 것에 대한 내 잘못입니다. 이렇게 넣어 드리겠습니다.

위의 코드를 세분화하면 다음 작업이 발생할 것으로 예상됩니다.

double r1 = 1.1 + 1.2;
double r2 = 1.0 + r1
double r3 = 1.0 + 1.1
double r4 = 1.2 + r3

위의 각 추가 각각에 반올림 오류가 있다고 가정 해 봅시다 (번호가 매겨진 E1..E4). 따라서 R1은 반올림 오류 E1을 포함하고 R2에는 반올림 오류 E1 + E2, R3에는 E3 + E4가 포함되어 있습니다.

이제 반올림 오류가 어떻게 발생하는지 정확히 알지 못하지만 E1+E2가 E3+E4와 동일 할 것으로 예상했을 것입니다. 분명히 그렇지는 않지만, 그것은 나에게 어떻게 든 잘못된 것 같습니다. 또 다른 것은 위의 코드를 실행할 때 반올림 오류가 발생하지 않는다는 것입니다. 그것이 CPU보다는 이상한 일을하는 C# 컴파일러라고 생각하게 만듭니다.

나는 내가 많은 것을 요구한다는 것을 알고있다. 그리고 누군가가 줄 수있는 가장 좋은 대답은 CPU 디자인에서 가서 박사 학위를하는 것입니다. 그러나 나는 단지 물어볼 것이라고 생각했습니다.

편집 2

원래 코드 샘플에서 IL을 살펴보면이 작업을 수행하는 CPU가 아닌 컴파일러라는 것은 분명합니다.

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
    .maxstack 1
    .locals init (
        [0] float64 result1,
        [1] float64 result2)
    L_0000: nop 
    L_0001: ldc.r8 3.3
    L_000a: stloc.0 
    L_000b: ldc.r8 3.3000000000000003
    L_0014: stloc.1 
    L_0015: ret 
}

컴파일러가 나를 위해 숫자를 추가하고 있습니다!

도움이 되었습니까?

해결책

E1+E2가 E3+E4와 동일 할 것으로 예상했을 것입니다.

그것은 전적으로 기대하는 것과는 다릅니다

 floor( 5/3 ) + floor( 2/3 + 1 )

동일하게

 floor( 5/3 + 2/3 ) + floor( 1 )

바닥을 타기 전에 2^53을 곱하는 것을 제외하고.

12 비트 정밀 플로팅 포인트를 사용하고 값과 함께 자르기 :

1.0            =  1.00000000000
1.1            =  1.00011001100
1.2            =  1.00110011001

1.0 + 1.1      = 10.00011001100 // extended during sum
r1 = 1.0 + 1.1 = 10.0001100110  // truncated to 12 bit
r1  + 1.2      = 11.01001100101 // extended during sum
r2 = r1  + 1.2 = 11.0100110010  // truncated to 12 bit

1.1 + 1.2      = 10.01001100110 // extended during sum
r3 = 1.1 + 1.2 = 10.0100110011  // truncated to 12 bit
r3 + 1.0       = 11.01001100110 // extended during sum
r4 = r3  + 1.0 = 11.0100110011  // truncated to 12 bit

따라서 작업 순서/절단 순서를 변경하면 오류가 변경되고 R4! = R2가 변경됩니다. 이 시스템에서 1.1과 1.2를 추가하면 마지막 비트가 튀어 나와서 잘리지 않아야합니다. 1.0에서 1.1을 추가하면 마지막 비트 1.1이 손실되므로 결과는 동일하지 않습니다.

한 번의 순서로, 반올림 (자르기)은 후행을 제거합니다. 1.

다른 순서에서, 반올림은 후행을 제거합니다. 0 두 번.

하나는 0과 같지 않습니다. 따라서 오류는 동일하지 않습니다.

복식에는 정밀도가 더 많아지고 C#은 자르기보다는 반올림을 사용하지만이 간단한 모델은 동일한 값의 다른 순서로 다른 오류가 발생할 수 있기를 바랍니다.

FP와 수학의 차이점은 +가 추가하기보다는 '추가 된 둥근'의 속기라는 것입니다.

다른 팁

C# 컴파일러는 아무것도하지 않습니다. CPU는입니다.

CPU 레지스터에 A가 있고 B를 추가하는 경우 해당 레지스터에 저장된 결과는 A+B이며 사용 된 부동 정밀도에 근접합니다.

C를 추가하면 오류가 추가됩니다. 이 오류 첨가는 전이 작업이 아니므로 최종 차이입니다.

보다 클래식 용지 (모든 컴퓨터 과학자가 부동 소수점 산술에 대해 알아야 할 것) 주제에. 이런 종류의 물건은 부동 소수점 산술에서 일어나는 일입니다. 컴퓨터 과학자는 1/3+1/3+1/3 그렇지 않아 1과 같다 ...

부동 소수점 작동 순서가 중요합니다. 질문에 직접 대답하지는 않지만 항상 부동 소수점 번호를 비교해야합니다. 관용을 포함시키는 것이 일반적입니다.

double epsilon = 0.0000001;
if (abs(result1 - result2) <= epsilon)
{
    ...
}

이것은 관심이있을 수 있습니다. 모든 컴퓨터 과학자가 부동 소수점 산술에 대해 알아야 할 것

결과 1은 항상 결과 2와 동일해야합니까?

잘못된. 그것은 수학에서도 마찬가지입니다 부동 소수점 산술.

당신은 몇 가지를 읽어야합니다 수치 분석 프라이머.

순서에 따라 오류가 동일하지 않은 이유는 다른 예제로 설명 할 수 있습니다.

10 미만의 숫자의 경우 모든 숫자를 저장할 수 있으므로 1, 2, 3 등을 최대 10 개로 저장할 수 있지만 10 후에는 내부 손실로 인해 매번 두 번째 숫자 만 저장할 수 있습니다. 다시 말해서, 그것은 10, 12, 14 등만 저장할 수 있습니다.

이제이 예를 들어 다음이 다른 결과를 낳는 이유를 알 수 있습니다.

1 + 1 + 1 + 10 = 12 (or 14, depending on rounding)
10 + 1 + 1 + 1 = 10

부동 소수점 번호의 문제는 정확하게 표현할 수 없으며 오류가 항상 같은 방식으로 진행되는 것은 아니므로 순서가 중요하다는 것입니다.

예를 들어, 3.00000000003 + 3.00000000003은 6.00000000005 (끝에 6은 아님)가 될 수 있지만 3.000000003 + 2.99999999997은 6.00000000001 일 수 있습니다.

step 1: 3.00000000003 + 3.00000000003 = 6.00000000005
step 2: 6.00000000005 + 2.99999999997 = 9.00000000002

그러나 순서를 변경하십시오.

step 1: 3.00000000003 + 2.99999999997 = 6.00000000001
step 2: 6.00000000001 + 3.00000000003 = 9.00000000004

그래서 그것은 중요합니다.

물론, 당신은 위의 예가 서로 균형을 잡는다는 점에서 운이 좋을 것입니다. 첫 번째는 .xxx1에 의해, 다른 하나는 .xxx1에 의해 아래로 줄어들므로 둘 다 .xxx3을 제공하지만 보장은 없습니다.

중간 결과가 다르기 때문에 실제로 동일한 값을 사용하지 않습니다.

double result1 = 2.1 + 1.2;
double result2 = 2.2 + 1.1;

복식은 10 진수 값을 정확하게 표현할 수 없기 때문에 다른 결과를 얻습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top