Programing

JavaScript에서 큰 숫자가 잘못 반올림 됨

lottogame 2021. 1. 6. 07:42
반응형

JavaScript에서 큰 숫자가 잘못 반올림 됨


이 코드를 참조하십시오.

<html>
  <head> 
    <script src="http://www.json.org/json2.js" type="text/javascript"></script>
    <script type="text/javascript">

      var jsonString = '{"id":714341252076979033,"type":"FUZZY"}';
      var jsonParsed = JSON.parse(jsonString);
      console.log(jsonString, jsonParsed);

    </script>
  </head>
  <body>
  </body>
</html>

Firefox 3.5에서 콘솔을 볼 때 jsonParsed의 값은 다음과 같습니다.

Object id=714341252076979100 type=FUZZY

즉, 숫자는 반올림됩니다. 다른 값, 동일한 결과를 시도했습니다 (숫자 반올림).

반올림 규칙도 얻지 못합니다. 714341252076979136은 714341252076979200으로 반올림되는 반면 714341252076979135는 714341252076979100으로 반올림됩니다.

편집 : 아래의 첫 번째 주석을 참조하십시오. 분명히 이것은 JSON에 관한 것이 아니라 JavaScript 번호 처리에 관한 것입니다. 그러나 문제는 남아 있습니다.

왜 이런 일이 발생합니까?


여기서보고있는 것은 실제로 두 개의 반올림의 효과입니다. ECMAScript의 숫자는 내부적으로 배정 밀도 부동 소수점으로 표시됩니다. id714341252076979033( 0x9e9d9958274c35916 진수) 로 설정 되면 실제로 가장 가까운 표현 가능한 배정 밀도 값인 714341252076979072( 0x9e9d9958274c380) 이 할당됩니다 . 값을 인쇄 할 때 15 개의 유효 십진수로 반올림되어 14341252076979100.


자바 스크립트 숫자 유형의 용량을 초과하고 있습니다 . 자세한 내용 은 사양의 §8.5를 참조 하세요. 이러한 ID는 문자열이어야합니다.

IEEE-754 배정 밀도 부동 소수점 (JavaScript에서 사용하는 숫자의 종류)은 모든 숫자를 정확하게 나타낼 수 없습니다 (물론). 유명한 0.1 + 0.2 == 0.3것은 거짓입니다. 그것은 분수에 영향을 미치는 것처럼 정수에 영향을 미칠 수 있습니다. 9,007,199,254,740,991 ( Number.MAX_SAFE_INTEGER) 이상이되면 시작됩니다 .

저쪽 Number.MAX_SAFE_INTEGER + 1( 9007199254740992)는 IEEE-754 부동 소수점 형식은 더 이상 모든 연속적인 정수를 나타낼 수 있습니다. 9007199254740991 + 1이다 9007199254740992, 그러나 9007199254740992 + 1입니다 9007199254740992 있기 때문에 9007199254740993형식으로 표현 될 수 없습니다. 다음이 될 수 있습니다 9007199254740994. 그럼 9007199254740995그럴 9007199254740996없지만 할 수 있습니다.

그 이유는 비트가 부족하여 더 이상 1s 비트가 없기 때문입니다. 최하위 비트는 이제 2의 배수를 나타냅니다. 결국 계속 진행하면 해당 비트를 잃고 4의 배수로 만 작업합니다.

값이 해당 임계 값보다 훨씬 높으므로 가장 가까운 표현 가능한 값으로 반올림됩니다.


비트에 대해 궁금하다면 다음과 같은 일이 발생합니다. IEEE-754 이진 배정 밀도 부동 소수점 숫자는 부호 비트, 11 비트 지수 (숫자의 전체 스케일을 2의 거듭 제곱으로 정의 함)를가집니다. 이진 형식이기 때문에]), 52 비트의 유의성 (하지만 형식이 너무 영리해서 52 비트 중 53 비트의 정밀도를 얻습니다). 지수가 사용되는 방법은 복잡 하지만 ( 여기에 설명 되어 있음) 매우 모호한 용어로 지수에 1을 더하면 지수가 2의 거듭 제곱에 사용되기 때문에 유효 숫자의 값이 두 배가됩니다 (다시 한 번주의해야합니다. 직접적이지 않고 영리함이 있습니다).

따라서 값 9007199254740991(일명, Number.MAX_SAFE_INTEGER)을 살펴 보겠습니다 .

   + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− −−−−−−−−−−−−−− 부호 비트
  / + −−−−−−− + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− −−−−−−−−−−−−−− 지수
 / / | + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− + − 의미
/ / | / |
0 10000110011 1111111111111111111111111111111111111111111111111111
                = 9007199254740991 (Number.MAX_SAFE_INTEGER)

그 지수 값 10000110011은 우리가 유효 에 1을 더할 때마다 표시되는 숫자가 1 씩 증가한다는 것을 의미합니다 (정수 1은 훨씬 더 일찍 분수를 표시하는 능력을 잃었습니다).

그러나 이제 그 의미는 가득 차 있습니다. 그 숫자를 지나가려면 지수를 늘려야합니다. 즉, 유효 숫자에 1을 더하면 표시된 숫자의 값이 1이 아니라 2만큼 올라갑니다 (지수가 2에 적용되기 때문입니다. 이진 부동 소수점 수) :

   + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− −−−−−−−−−−−−−− 부호 비트
  / + −−−−−−− + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− −−−−−−−−−−−−−− 지수
 / / | + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− + − 의미
/ / | / |
0 10000110100 0000000000000000000000000000000000000000000000000000
                = 9007199254740992 (Number.MAX_SAFE_INTEGER + 1)

때문에 글쎄, 그건, 괜찮아 9007199254740991 + 1입니다 9007199254740992어쨌든. 그러나! 우리는 9007199254740993. 비트가 부족합니다. 유효 숫자에 1 만 더하면 값에 2가 더해집니다.

   + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− −−−−−−−−−−−−−− 부호 비트
  / + −−−−−−− + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− −−−−−−−−−−−−−− 지수
 / / | + −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− + − 의미
/ / | / |
0 10000110100 0000000000000000000000000000000000000000000000000001
                = 9007199254740994 (Number.MAX_SAFE_INTEGER + 3)

The format just cannot represent odd numbers anymore as we increase the value, the exponent is too big.

Eventually, we run out of significand bits again and have to increase the exponent, so we end up only being able to represent multiples of 4. Then multiples of 8. Then multiples of 16. And so on.


It is not caused by this json parser. Just try to enter 714341252076979033 to fbug's console. You'll see the same 714341252076979100.

See this blog post for details: http://www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too


JavaScript uses double precision floating point values, ie a total precision of 53 bits, but you need

ceil(lb 714341252076979033) = 60

bits to exactly represent the value.

The nearest exactly representable number is 714341252076979072 (write the original number in binary, replace the last 7 digits with 0 and round up because the highest replaced digit was 1).

You'll get 714341252076979100 instead of this number because ToString() as described by ECMA-262, §9.8.1 works with powers of ten and in 53 bit precision all these numbers are equal.


The problem is that your number requires a greater precision than JavaScript has.

Can you send the number as a string? Separated in two parts?


JavaScript can only handle exact whole numbers up to about 9000 million million (that's 9 with 15 zeros). Higher than that and you get garbage. Work around this by using strings to hold the numbers. If you need to do math with these numbers, write your own functions or see if you can find a library for them: I suggest the former as I don't like the libraries I've seen. To get you started, see two of my functions at another answer.

ReferenceURL : https://stackoverflow.com/questions/1379934/large-numbers-erroneously-rounded-in-javascript

반응형