[javascript] 합계 순서를 변경하면 다른 결과가 반환되는 이유는 무엇입니까?

합계 순서를 변경하면 다른 결과가 반환되는 이유는 무엇입니까?

23.53 + 5.88 + 17.64 = 47.05

23.53 + 17.64 + 5.88 = 47.050000000000004

JavaJavaScript는 모두 동일한 결과를 반환합니다.

부동 소수점 숫자가 이진수로 표시되는 방식으로 인해 일부 합리적인 숫자 ( 예 : 1/3-0.333333 … )를 정확하게 표현할 수 없음을 이해합니다.

요소의 순서를 변경하면 결과에 영향을주는 이유는 무엇입니까?



답변

어쩌면이 질문은 어리석은 일이지만 왜 요소의 순서를 변경하면 결과에 영향을 미칩니 까?

크기에 따라 값이 반올림되는 지점이 변경됩니다. 의 예를 들어 어떤 우린 보는이의 대신 이진 부동 소수점, 우리는 각 추가가 “무한”정밀도로 수행 한 후 반올림 4 개 유효 숫자와 소수점 형 부동 소수점을 사용하고 척하자 그 일의 가장 가까운 표현 가능한 숫자 다음은 두 가지 합계입니다.

1/3 + 2/3 + 2/3 = (0.3333 + 0.6667) + 0.6667
                = 1.000 + 0.6667 (no rounding needed!)
                = 1.667 (where 1.6667 is rounded to 1.667)

2/3 + 2/3 + 1/3 = (0.6667 + 0.6667) + 0.3333
                = 1.333 + 0.3333 (where 1.3334 is rounded to 1.333)
                = 1.666 (where 1.6663 is rounded to 1.666)

우리는 이것이 문제가되기 위해 비 정수가 필요하지 않습니다.

10000 + 1 - 10000 = (10000 + 1) - 10000
                  = 10000 - 10000 (where 10001 is rounded to 10000)
                  = 0

10000 - 10000 + 1 = (10000 - 10000) + 1
                  = 0 + 1
                  = 1

이것은 중요한 부분이 소수 자릿수가 아닌 유효 숫자 가 제한적이라는 것을보다 명확하게 보여줍니다 . 항상 같은 수의 소수점 이하 자릿수를 유지할 수 있다면 적어도 덧셈과 뺄셈을 사용하면 값이 오버플로되지 않는 한 괜찮을 것입니다. 문제는 더 큰 숫자에 도달하면 더 작은 정보가 손실된다는 것입니다.이 경우 10001은 10000으로 반올림됩니다. (이것은 Eric Lippert가 그의 답변에서 지적한 문제의 예입니다 .)

오른쪽 첫 번째 줄의 값이 모든 경우에 동일하다는 점에 유의해야합니다. 따라서 10 진수 (23.53, 5.88, 17.64)가 정확히 double값 으로 표시되지 않는다는 것을 이해해야합니다. 위에 표시된 문제로 인해 문제가 발생했습니다.


답변

바이너리로 진행되는 일이 있습니다. 우리가 알다시피, 일부 부동 소수점 값은 정확히 10 진수로 표현 될 수 있다고하더라도 바이너리로 정확하게 표현 될 수 없습니다. 이 세 숫자는 그 사실의 예일뿐입니다.

이 프로그램으로 각 숫자의 16 진수 표현과 각 덧셈의 결과를 출력했습니다.

public class Main{
   public static void main(String args[]) {
      double x = 23.53;   // Inexact representation
      double y = 5.88;    // Inexact representation
      double z = 17.64;   // Inexact representation
      double s = 47.05;   // What math tells us the sum should be; still inexact

      printValueAndInHex(x);
      printValueAndInHex(y);
      printValueAndInHex(z);
      printValueAndInHex(s);

      System.out.println("--------");

      double t1 = x + y;
      printValueAndInHex(t1);
      t1 = t1 + z;
      printValueAndInHex(t1);

      System.out.println("--------");

      double t2 = x + z;
      printValueAndInHex(t2);
      t2 = t2 + y;
      printValueAndInHex(t2);
   }

   private static void printValueAndInHex(double d)
   {
      System.out.println(Long.toHexString(Double.doubleToLongBits(d)) + ": " + d);
   }
}

printValueAndInHex방법은 16 진수 프린터 도우미입니다.

출력은 다음과 같습니다.

403787ae147ae148: 23.53
4017851eb851eb85: 5.88
4031a3d70a3d70a4: 17.64
4047866666666666: 47.05
--------
403d68f5c28f5c29: 29.41
4047866666666666: 47.05
--------
404495c28f5c28f6: 41.17
4047866666666667: 47.050000000000004

처음 4 개 번호는 x, y, z, 및 s의 진수 표현. IEEE 부동 소수점 표현에서 비트 2-12는 이진 지수 즉 숫자의 스케일을 나타냅니다 . (첫 번째 비트는 부호 비트이고, 가수에 대한 나머지 비트입니다 .) 표현 된 지수는 실제로 이진수에서 1023을 뺀 것입니다.

처음 4 개의 지수가 추출됩니다.

    sign|exponent
403 => 0|100 0000 0011| => 1027 - 1023 = 4
401 => 0|100 0000 0001| => 1025 - 1023 = 2
403 => 0|100 0000 0011| => 1027 - 1023 = 4
404 => 0|100 0000 0100| => 1028 - 1023 = 5

추가의 첫 세트

두 번째 숫자 ( y)는 더 작은 크기입니다. 를 얻기 위해이 두 숫자를 더할 때 두 x + y번째 숫자 ( 01) 의 마지막 2 비트 가 범위를 벗어나 계산에 포함되지 않습니다.

제 첨가 추가 x + yz같은 규모의 두 숫자를 추가한다.

추가의 두 번째 세트

여기서 x + z먼저 발생합니다. 그것들은 같은 규모이지만, 규모가 더 높은 숫자를 산출합니다.

404 => 0|100 0000 0100| => 1028 - 1023 = 5

제 첨가를 추가 x + z하고 y, 현재 3 비트로부터 삭제되는 y숫자를 추가 ( 101). 결과는 다음 부동 소수점 수이기 때문에 위쪽으로 반올림되어야합니다 4047866666666666. 첫 번째 덧셈 4047866666666667세트와 두 번째 덧셈 세트의 결과입니다. 이 오류는 총계의 출력물에 표시하기에 충분히 중요합니다.

결론적으로 IEEE 숫자에 대해 수학 연산을 수행 할 때주의하십시오. 일부 표현은 정확하지 않으며 스케일이 다르면 훨씬 더 정확하지 않습니다. 가능하면 비슷한 규모의 숫자를 더하고 빼십시오.


답변

존의 대답은 물론 맞습니다. 귀하의 경우 오류는 간단한 부동 소수점 연산을 수행하여 누적되는 오류보다 크지 않습니다. 어떤 경우에는 제로 오류가 발생하고 다른 경우에는 작은 오류가 발생하는 시나리오가 있습니다. 실제로 흥미로운 시나리오는 아닙니다. 좋은 질문은 계산 순서 변경이 작은 오류에서 (상대적으로) 큰 오류로 진행되는 시나리오가 있습니까? 대답은 분명합니다.

예를 들면 다음과 같습니다.

x1 = (a - b) + (c - d) + (e - f) + (g - h);

vs

x2 = (a + c + e + g) - (b + d + f + h);

vs

x3 = a - b + c - d + e - f + g - h;

분명히 정확한 산술에서 그들은 동일 할 것입니다. x1 및 x2 및 x3의 값이 많은 양이되도록 a, b, c, d, e, f, g, h에 대한 값을 찾으려고 시도하는 것이 재미있다. 그렇게 할 수 있는지보십시오!


답변

이것은 실제로 Java 및 Javascript 이상을 다루며 float 또는 double을 사용하는 모든 프로그래밍 언어에 영향을 줄 수 있습니다.

메모리에서 부동 소수점은 IEEE 754 라인을 따라 특수 형식을 사용합니다 (변환기는 가능한 것보다 훨씬 더 나은 설명을 제공합니다).

어쨌든, 여기에 플로트 변환기가 있습니다.

http://www.h-schmidt.net/FloatConverter/

작업 순서에 관한 것은 작업의 “정확도”입니다.

첫 번째 줄은 처음 두 값에서 29.41을 산출하여 2 ^ 4를 지수로 만듭니다.

두 번째 줄은 41.17을 산출하여 2 ^ 5를 지수로 만듭니다.

우리는 지수를 증가시켜 중요한 수치를 잃어 가고 있으며 결과는 바뀔 수 있습니다.

41.17에 대해 맨 오른쪽에있는 마지막 비트를 켜고 끄십시오. 지수의 1 / 2 ^ 23과 같이 “미미한”것이이 부동 소수점 차이를 유발하기에 충분하다는 것을 알 수 있습니다.

편집 : 중요한 인물을 기억하는 사람들에게는 이것이 해당 범주에 속합니다. 10 ^ 4 + 4999의 유효 숫자 1은 10 ^ 4가됩니다. 이 경우 유효 숫자는 훨씬 작지만 .00000000004가 첨부 된 결과를 볼 수 있습니다.


답변

부동 소수점 숫자는 IEEE 754 형식을 사용하여 표현되며 가수에 대한 특정 크기의 비트를 제공합니다. 불행히도 이것은 특정 수의 ‘분수 빌딩 블록’을 제공하며 특정 분수 값을 정확하게 표현할 수는 없습니다.

귀하의 경우에 일어나는 것은 두 번째 경우에는 추가가 평가되는 순서로 인해 추가가 약간의 정밀한 문제에 처할 수 있다는 것입니다. 값을 계산하지는 않았지만 23.53 + 17.64는 정확하게 표현할 수 없지만 23.53 + 5.88은 정확하게 표현할 수 없습니다.

불행히도 그것은 당신이 처리해야 할 알려진 문제입니다.


답변

나는 그것이 증발 순서와 관련이 있다고 생각합니다. 수학 세계에서는 합이 자연스럽게 동일하지만 A + B + C = D 대신 이진 세계에서는 동일합니다.

A + B = E
E + C = D(1)

부동 소수점 숫자가 나올 수있는 2 차 단계가 있습니다.

주문을 변경하면

A + C = F
F + B = D(2)


답변