부동 소수점 수학이 깨졌습니까?


2983

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

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

왜 이러한 부정확성이 발생합니까?


127
부동 소수점 변수에는 일반적으로이 동작이 있습니다. 하드웨어에 저장되는 방식 때문입니다. 자세한 내용은 부동 소수점 숫자에 대한 Wikipedia 기사를 확인하십시오 .
벤 S

62
JavaScript는 소수를 부동 소수점 숫자 로 취급하므로 더하기와 같은 연산은 반올림 오류가 발생할 수 있습니다. 이 기사를 살펴보고 싶을 것입니다. 모든 컴퓨터 과학자가 부동 소수점 산술에 대해 알아야 할 사항
matt b

4
참고로 자바 스크립트의 모든 숫자 유형은 IEEE-754 Doubles입니다.
Gary Willoughby

6
JavaScript는 Math에 IEEE 754 표준을 사용하므로 64 비트 부동 숫자를 사용합니다. 이로 인해 10 진수가 10 진수 인 동안 Base 2 에서 작동하는 컴퓨터로 인해 부동 소수점 (10 진수) 계산을 수행 할 때 정밀 오류가 발생합니다 .
Pardeep Jain

답변:


2251

이진 부동 소수점 수학은 다음과 같습니다. 대부분의 프로그래밍 언어에서는 IEEE 754 표준을 기반으로합니다 . 문제의 요점은 숫자가이 형식으로 정수에 2의 거듭 제곱으로 표시된다는 것입니다. 유리수 (예를 들면 0.1, 이는 1/10그 분모가 2의 제곱이 정확하게 표현 될 수 없다).

들어 0.1표준의 binary64형식, 표현이 정확하게과 같이 쓸 수있다

  • 0.1000000000000000055511151231257827021181583404541015625 십진수 또는
  • 0x1.999999999999ap-4에서 C99의 hexfloat 표기 .

대조적으로, 유리수 0.1는 즉 1/10,

  • 0.1 십진수 또는
  • 0x1.99999999999999...p-4C99 hexfloat 표기법과 유사하며, 여기서 ...9는 끝없는 시퀀스를 나타냅니다.

상수 0.20.3프로그램 의 상수도 실제 값과 비슷합니다. 가장 가까운 것을 일 double에이 0.2유리수보다 큰 0.2하지만 가장 가까운 것을 double에이 0.3합리적인 수보다 작다 0.3. 합 0.1과 합은 0.2합리적 수보다 커서 0.3코드의 상수에 동의하지 않습니다.

부동 소수점 산술 문제의 상당히 포괄적 인 처리는 모든 컴퓨터 과학자 가 부동 소수점 산술에 대해 알아야 할 것 입니다. 보다 이해하기 쉬운 설명은 floating-point-gui.de를 참조하십시오 .

참고 : 모든 위치 (base-N) 숫자 시스템은이 문제를 정밀하게 공유합니다.

평범한 오래된 십진수 (기본 10) 숫자는 같은 문제가 있으므로 1/3과 같은 숫자는 0.333333333으로 끝나는 것입니다 ...

십진법으로 표현하기 쉽지만 이진법에 맞지 않는 숫자 (3/10)를 우연히 발견했습니다. 1/16은 십진수 (0.0625)의 못생긴 숫자이지만, 이진수에서는 10,000의 십진수 (0.0001)만큼 깔끔한 것처럼 보입니다. **- 우리의 일상 생활에서 기본 2 수 체계를 사용하는 습관, 당신은 심지어 그 숫자를보고 뭔가를 반으로, 반으로 반복하여 도착할 수 있다는 것을 본능적으로 이해합니다.

** 물론 부동 소수점 숫자가 메모리에 저장되는 방식이 아닙니다 (과학 표기법의 형태를 사용함). 그러나 이진 부동 소수점 정밀도 오류는 일반적으로 우리가 작업하고자하는 "실제 세계"숫자가 10의 거듭 제곱이기 때문에 발생하는 경향을 보여줍니다. 오늘. 그렇기 때문에 우리는 "7/5마다 5"대신 71 %와 같은 것을 말할 것입니다. (71 %는 근사치입니다.

따라서 아니오 : 이진 부동 소수점 숫자는 손상되지 않으며 다른 모든 base-n 숫자 시스템만큼 불완전합니다. :)

측면 참고 : 프로그래밍에서 플로트 작업

실제로이 정밀도 문제는 부동 소수점 숫자를 표시하기 전에 원하는 소수 자릿수로 반올림하기 위해 반올림 함수를 사용해야 함을 의미합니다.

또한 동등성 테스트를 약간의 공차를 허용하는 비교로 대체해야합니다. 이는 다음을 의미합니다.

마십시오 하지if (x == y) { ... }

대신 if (abs(x - y) < myToleranceValue) { ... }.

abs절대 값은 어디에 있습니까 ? myToleranceValue특정 응용 프로그램에 대해 선택해야합니다. 허용 할 "흔들기 방"의 양과 비교할 가장 큰 숫자 (정밀성 문제로 인해)와 관련이 있습니다. ). 선택한 언어에서 "엡실론"스타일 상수를주의하십시오. 이들은 공차 값으로 사용 되지 않습니다 .


181
모든 경우에 사용할 수있는 "엡실론"이 없기 때문에 "일부 오류 상수"가 "엡실론"보다 더 정확하다고 생각합니다. 상황에 따라 다른 엡실론을 사용해야합니다. 그리고 기계 엡실론은 사용하기에 항상 좋은 상수는 아닙니다.
Rotsor

34
모든 부동 소수점 수학이 IEEE [754] 표준을 기반으로한다는 것은 사실이 아닙니다 . 예를 들어, 오래된 IBM 16 진 FP가있는 일부 시스템이 여전히 사용 중이며 IEEE-754 산술을 지원하지 않는 그래픽 카드가 여전히 있습니다. 그러나 합리적으로 근사하는 것은 사실입니다.
Stephen Canon

19
Cray는 IEEE-754 준수 속도를 버렸다. 자바는 최적화로서의 준수도 완화했다.
아트 테일러

28
돈에 대한 계산이 항상 정수 에 대한 고정 소수점 산술로 수행되는 방법에 대해이 답변에 무언가를 추가해야한다고 생각합니다 . 돈은 양자화되기 때문입니다. (작은 센트 또는 가장 작은 통화 단위로 내부 회계 계산을 수행하는 것이 합리적 일 수 있습니다. 이는 "한 달에 $ 29.99"를 일일 요율로 변환 할 때 반올림 오류를 줄이는 데 도움이됩니다. 여전히 고정 소수점 산술입니다.)
zwol

18
흥미로운 사실 ​​:이 이진 부동 소수점으로 정확하게 표현되지 않은 0.1 명이 악명 높은 패트리어트 미사일 소프트웨어 버그 를 일으켜 이라크 전쟁 중 28 명이 사망했습니다.
hdl

602

하드웨어 디자이너의 관점

부동 소수점 하드웨어를 디자인하고 빌드하기 때문에 하드웨어 디자이너의 관점을 추가해야한다고 생각합니다. 오류의 원인을 아는 것은 소프트웨어에서 일어나는 일을 이해하는 데 도움이 될 수 있으며 궁극적으로 이것이 부동 소수점 오류가 발생하고 시간이 지남에 따라 축적되는 이유를 설명하는 데 도움이되기를 바랍니다.

1. 개요

엔지니어링 관점에서 볼 때 대부분의 부동 소수점 연산에는 부동 소수점 계산을 수행하는 하드웨어에 마지막 한 단위의 절반 미만의 오류 만 있으면되므로 일부 오류 요소가 있습니다. 따라서 대부분의 하드웨어는 부동 소수점 분할에서 특히 문제가되는 단일 작업 에 대해 마지막 위치에서 한 단위의 절반 미만의 오류를 생성하는 데 필요한 정밀도로 중지됩니다 . 단일 연산을 구성하는 것은 장치가 수행하는 피연산자 수에 따라 다릅니다. 대부분 2 개이지만 일부 단위에는 3 개 이상의 피연산자가 필요합니다. 이 때문에 시간이 지남에 따라 오류가 더해 지므로 반복 된 작업으로 인해 원하는 오류가 발생한다고 보장 할 수 없습니다.

2. 표준

대부분의 프로세서는 IEEE-754 표준을 따르지만 일부는 비정규 화 된 표준 또는 다른 표준을 사용합니다. 예를 들어 IEEE-754에는 비정규 화 된 모드가있어 정밀도를 희생시키면서 매우 작은 부동 소수점 숫자를 표현할 수 있습니다. 그러나 다음은 일반적인 작동 모드 인 IEEE-754의 정규화 된 모드를 다룹니다.

IEEE-754 표준에서, 하드웨어 설계자들은 마지막 장소에서 한 단위의 절반보다 작은 오류 / 엡실론의 값을 허용하며 결과는 마지막 한 단위의 절반보다 작아야합니다. 하나의 작업 장소. 이것은 반복되는 작업이있을 때 오류가 더해지는 이유를 설명합니다. IEEE-754 배정도의 경우 53 비트는 부동 소수점 숫자 (예 : 5.3e5의 5.3)의 가수 (mantissa)라고하는 숫자 부분 (정규화 된)을 나타내는 데 사용되므로 54 번째 비트입니다. 다음 섹션에서는 다양한 부동 소수점 연산에서 하드웨어 오류의 원인에 대해 자세히 설명합니다.

3. 나눗셈에서 반올림 오류의 원인

부동 소수점 나누기 오류의 주요 원인은 몫을 계산하는 데 사용되는 나누기 알고리즘입니다. 대부분의 컴퓨터 시스템은 주로 Z=X/Y,Z = X * (1/Y). 나누기는 반복적으로 계산됩니다. 즉, 각 사이클은 원하는 정밀도에 도달 할 때까지 몫의 일부 비트를 계산합니다. IEEE-754의 경우 마지막 위치에서 1 단위 미만의 오류가있는 항목입니다. Y (1 / Y)의 역수 표는 느린 나눗셈에서 몫 선택 표 (QST)라고하며 몫 선택 표의 비트 단위 크기는 일반적으로 기수의 폭 또는 비트 수입니다. 각 반복에서 계산 된 몫과 가드 비트. IEEE-754 표준의 배정도 (64 비트)의 경우 디바이더의 기수 크기에 몇 가드 비트 k를 더한 값 k>=2입니다. 예를 들어, 한 번에 몫의 2 비트 (기수 4)를 계산하는 디바이더에 대한 일반적인 몫 선택 표는 2+2= 4비트 (플러스 옵션 비트)가됩니다.

3.1 구간 반올림 오차 : 역수 근사

몫 선택 테이블에있는 왕복 수는 나누기 방법 에 따라 다릅니다. SRT 나누기와 같은 느린 나누기 또는 Goldschmidt 나누기와 같은 빠른 나누기; 각 항목은 가능한 가장 낮은 오류를 생성하기 위해 나누기 알고리즘에 따라 수정됩니다. 어쨌든 모든 역수는 근사치입니다.실제의 역수와 오류 요소를 소개합니다. 느린 나눗셈과 빠른 나눗셈 방법은 몫을 반복적으로 계산합니다. 즉, 몫의 일부 비트가 각 단계에서 계산 된 다음 결과가 피제수에서 빼고 디바이더가 오차가 1의 절반보다 작을 때까지 단계를 반복합니다. 마지막 장소에서 단위. 느린 나눗셈 방법은 각 단계에서 몫의 고정 자릿수를 계산하며 일반적으로 구축 비용이 저렴하고 빠른 나눗셈 방법은 단계 당 가변 자릿수를 계산하며 일반적으로 구축 비용이 더 비쌉니다. 나누기 방법의 가장 중요한 부분은 대부분이 역수 의 근사 로 반복 곱셈에 의존 하므로 오류가 발생하기 쉽다는 것입니다.

4. 다른 작업에서 반올림 오류 : 잘림

모든 작업에서 반올림 오류의 또 다른 원인은 IEEE-754가 허용하는 최종 응답의 다른 절단 모드입니다. 잘림, 0으로 반올림 , 가장 가까운 반올림 (기본값), 반올림 및 반올림이 있습니다. 모든 방법은 단일 작업을 위해 마지막에 한 단위 미만의 오차 요소를 도입합니다. 시간이 지남에 따라 작업을 반복하면 잘림도 결과 오류에 누적됩니다. 이 잘림 오류는 지수에 특히 문제가 있으며, 여기에는 어떤 형태의 반복 곱셈이 포함됩니다.

5. 반복 작업

부동 소수점 계산을 수행하는 하드웨어는 단일 작업의 마지막 위치에서 한 단위의 절반 미만의 오류로 결과를 생성하기 만하면되기 때문에 오류를 보지 않으면 반복 된 작업보다 오류가 커집니다. 이는 경계 오류가 필요한 계산에서 수학자 들이 IEEE-754 의 마지막 위치에서 가장 가까운 자리에서 짝수 자릿수를 사용하는 등의 방법을 사용하는 이유입니다. 아웃 및 간격 산술 의 변화와 함께 IEEE 754 라운딩 모드반올림 오류를 예측하고 수정합니다. IEEE-754의 기본 반올림 모드는 다른 반올림 모드에 비해 상대적 오류가 낮기 때문에 가장 가까운 짝수로 반올림합니다 (마지막).

기본 반올림 모드 인 마지막 자리에서 가장 가까운 자리수까지도 한 번의 작업으로 마지막 자리에서 한 단위의 절반 미만의 오류를 보장합니다. 잘림, 반올림 및 반올림 만 사용하면 마지막 위치에서 한 단위의 절반보다 크지 만 마지막 위치에서 하나보다 적은 오류가 발생할 수 있으므로 이러한 모드는 그렇지 않은 경우 권장되지 않습니다. 간격 산술에 사용됩니다.

6. 요약

요컨대, 부동 소수점 연산에서 오류가 발생하는 근본적인 이유는 하드웨어에서 잘림과 나눗셈에서 역수가 잘림의 조합입니다. IEEE-754 표준은 단일 작업의 마지막 위치에서 한 단위의 절반 미만의 오류 만 필요하므로, 반복 작업에 대한 부동 소수점 오류는 수정하지 않으면 더해집니다.


8
(3) 잘못되었습니다. 나누기의 반올림 오류 는 마지막 위치에서 1 단위 이상이 아니라 마지막 위치에서 최대 절반 이됩니다.
gnasher729

6
잘 했어. 대부분의 기본 작업에는 기본 IEEE 반올림 모드를 사용하는 마지막 위치에서 한 단위의 1/2 미만의 오류가 있습니다. 설명을 편집하고 사용자가 기본 반올림 모드를 재정의하는 경우 오류가 하나의 ulp의 1/2보다 크지 만 1ulp보다 작을 수 있습니다 (특히 임베디드 시스템의 경우).
KernelPanik

39
(1) 부동 소수점 숫자 에는 오류가 없습니다. 모든 부동 소수점 값이 그대로입니다. 대부분의 부동 소수점 연산 은 정확하지 않은 결과를 제공합니다. 예를 들어 정확히 1.0 / 10.0과 같은 이진 부동 소수점 값이 없습니다. 반면에 일부 작업 (예 : 1.0 + 1.0) 정확한 결과를 제공합니다.
Solomon Slow

19
"부동 소수점 나누기 오류의 주요 원인은 몫을 계산하는 데 사용되는 나누기 알고리즘입니다"라는 말 은 매우 잘못된 것입니다. IEEE-754 준수 나누기의 경우 부동 소수점 나누기 오류 의 유일한 원인은 결과를 결과 형식으로 정확하게 표현할 수 없기 때문입니다. 사용 된 알고리즘에 관계없이 동일한 결과가 계산됩니다.
Stephen Canon

6
@Matt 늦게 응답해서 죄송합니다. 기본적으로 리소스 / 시간 문제와 트레이드 오프 때문입니다. 긴 나눗셈 /보다 '정상적인'나눗셈을하는 방법이 있는데, 기수 2를 가진 SRT 나누기라고합니다. 그러나 이것은 반복적으로 피제수에서 제수를 이동 및 빼고 클럭주기 당 몫의 1 비트 만 계산하므로 많은 클럭주기를 사용합니다. 우리는 왕복 테이블을 사용하여 사이클 당 더 많은 몫의 몫을 계산하고 효과적인 성능 / 속도 트레이드 오프를 만들 수 있습니다.
KernelPanik

462

.1 또는 1/10을 기수 2 (이진)로 변환하면 기수 10에서 1/3을 나타내려고하는 것처럼 소수점 다음에 반복되는 패턴이 나타납니다. 일반 부동 소수점 방법을 사용하여 정확한 수학.


133
크고 짧은 대답. ... 0.00011001100110011001100110011001100110011001100110011 같은 패턴의 모양을 반복
콘스탄틴 르노

4
이것은 왜 바이너리로 변환하지 않는 더 나은 알고리즘이 사용되지 않는 이유를 설명하지 않습니다.
Dmitri Zaitsev

12
성능 때문입니다. 바이너리는 머신에 고유하기 때문에 수천 배 빠릅니다.
Joel Coehoorn

7
정확한 10 진수 값을 산출하는 메소드가 있습니다. BCD (이진 코드 10 진수) 또는 다양한 다른 형태의 10 진수입니다. 그러나 이들은 이진 부동 소수점을 사용하는 것보다 속도가 느리고 (LOT 속도가 느림) 더 많은 스토리지를 사용합니다. (예를 들어, 묶음 BCD는 바이트에 2 개의 10 진수를 저장합니다. 실제로 256 개의 가능한 값을 저장할 수있는 100 개의 가능한 값 또는 100/256은 바이트의 가능한 값의 약 60 %를 낭비합니다.)
Duncan C

16
@Jacksonkr 당신은 여전히 ​​base 10에서 생각하고 있습니다. 컴퓨터는 기본 2입니다.
Joel Coehoorn 16:14

306

여기에있는 대부분의 답변은이 질문을 매우 건조하고 기술적 인 용어로 해결합니다. 정상적인 인간이 이해할 수있는 용어로이 문제를 해결하고 싶습니다.

피자를 자르려고한다고 상상해보십시오. 피자 조각을 정확하게 자를 수있는 로봇 피자 커터가 있습니다 반으로 . 피자 전체를 반으로 줄이거 나 기존 슬라이스를 반으로 줄 수 있지만, 어쨌든 반은 항상 정확합니다.

그 피자 커터는 매우 미세한 움직임을 가지고 있으며, 당신이 전체 피자로 시작하는 경우, 그 반감, 그리고 작은 조각 각 시간을 절반으로 계속, 당신은 반감을 할 수있는 53 번 슬라이스도 높은 정밀도의 능력에 대해 너무 작하기 전에 . 이 시점에서 더 이상 해당 슬라이스를 절반으로 줄일 수 없지만 그대로 포함하거나 제외해야합니다.

이제 피자의 1/10 (0.1) 또는 1/5 (0.2)를 추가하는 방식으로 모든 슬라이스를 어떻게 조각 하시겠습니까? 실제로 그것에 대해 생각하고 해결해보십시오. 신화적인 정밀 피자 커터가 있다면 실제 피자를 사용해보십시오. :-)


물론 대부분의 숙련 된 프로그래머는 실제 답을 알고 있습니다. 즉, 슬라이스를 사용하여 피자를 정확히 10 분의 5 또는 5 분의 1로 분할 할 수있는 방법이 없다는 것입니다. 당신은 꽤 좋은 근사를 할 수 있고, 대략 0.2의 근사치 0.1을 더하면 대략 0.3의 근사치를 얻지 만 여전히 근사치입니다.

배정 밀도 숫자 (피자 53 배를 절반으로 줄일 수있는 정밀도)의 경우 0.1보다 작거나 큰 숫자는 0.099999999999999991671672731131132594682276248931884765625 및 0.1000000000000000055511151231257827021181583404541015625입니다. 후자는 전자보다 0.1에 훨씬 더 가깝기 때문에 0.1의 입력이 주어지면 숫자 파서는 후자를 선호합니다.

(두 숫자의 차이는 "가장 작은 슬라이스"입니다. 여기에는 상향 바이어스를 도입하거나 제외를 포함하여 하향 바이어스를 도입하기로 결정해야합니다. 해당 최소 슬라이스에 대한 기술적 용어는 ulp 입니다.)

0.2의 경우 숫자는 모두 동일하며 2 배로 확장되었습니다. 다시, 0.2보다 약간 큰 값을 선호합니다.

두 경우 모두 0.1과 0.2의 근사치에는 약간의 상향 바이어스가 있습니다. 이러한 바이어스를 충분히 추가하면 원하는 수에서 멀어지고 멀어지게됩니다. 실제로 0.1 + 0.2의 경우 바이어스는 결과 숫자가 더 이상 가장 가까운 숫자가 아닐 정도로 충분히 높습니다. ~ 0.3.

특히 0.1 + 0.2는 실제로 0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125 인 반면, 0.3에 가장 가까운 숫자는 실제로 0.299999999999999988897769753748434595763683319091796875입니다.


PS 일부 프로그래밍 언어는 슬라이스를 정확히 10 분할로 분할 할 수있는 피자 절단기를 제공합니다 . 그러한 피자 절단기는 흔하지 않지만, 하나에 접근 할 수 있다면 정확히 10 분의 1 또는 5 분의 1의 슬라이스를 얻을 수있을 때 사용해야합니다.

(원래 Quora에 게시되었습니다.)


3
정확한 수학을 포함하는 일부 언어가 있습니다. 예를 들어, Scheme은 GNU Guile을 통한 예입니다. draketo.de/english/exact-math-to-the-rescue를 참조하십시오 — 이것은 수학을 분수로 유지하고 결국에만 조각화합니다.
Arne Babenhauserheide

5
@FloatingRock 사실, 소수의 주류 프로그래밍 언어에는 합리적인 숫자가 내장되어 있습니다. Arne은 Schemer이므로, 우리가 망쳐 놓은 것들입니다.
Chris Jester-Young

5
@ ArneBabenhauserheide 나는 이것이 합리적인 수로 만 작동한다는 것을 덧붙일 가치가 있다고 생각합니다. 따라서 pi와 같은 비합리적인 숫자로 수학을 수행하는 경우 pi의 배수로 저장해야합니다. 물론 pi와 관련된 계산은 정확한 10 진수로 표현할 수 없습니다.
Aidiakapi

13
@connexo 좋아. 피자 로테이터를 어떻게 36도까지 프로그래밍 할 수 있습니까? 36 도는 무엇입니까? (힌트 : 정확한 방식으로 이것을 정의 할 수 있다면, 정확히 10 분의 1 조각 피자 커터도 있습니다.) 즉, 실제로 1/360 (도) 또는 1 / 이진 부동 소수점 만있는 10 (36도).
Chris Jester-Young

12
@connexo 또한 "모든 바보"는 피자를 정확히 36도 회전시킬 수 없습니다 . 인간은 오류가 발생하기 쉽기 때문에 매우 정밀한 작업을 수행 할 수 없습니다.
Chris Jester-Young

212

부동 소수점 반올림 오류. 소수점 이하 자릿수 5가 누락되어 10을 밑으로하는 것처럼 0.1을 밑이 2로 정확하게 표현할 수 없습니다. 1/3은 소수를 나타 내기 위해 무한한 자릿수를 취하지 만 밑이 3이면 "0.1"입니다. 0.1은 10 진수가 아닌 2 진수의 무한 자릿수를 사용합니다. 그리고 컴퓨터에는 무한한 양의 메모리가 없습니다.


133
컴퓨터는 0.1 + 0.2 = 0.3을 얻기 위해 무한한 메모리를 필요로하지 않습니다
Pacerier

23
@Pacerier 물론 그들은 두 개의 무제한 정밀도 정수를 사용하여 분수를 나타내거나 따옴표 표기법을 사용할 수 있습니다. 이것은 "이진"또는 "소수"라는 특정 개념입니다. 이진 / 소수 자릿수와 기수 어딘가에 있다는 점입니다. 정확한 합리적인 결과를 얻으려면 더 나은 형식이 필요합니다.
Devin Jeanpierre

15
@Pacerier : 2 진 또는 10 진 부동 소수점 모두 1/3 또는 1/13을 정확하게 저장할 수 없습니다. 십진 부동 소수점 유형은 M / 10 ^ E 형식의 값을 정확하게 나타낼 수 있지만 대부분의 다른 분수를 나타내는 경우 비슷한 크기의 이진 부동 소수점 숫자보다 정확도가 떨어 집니다. 많은 응용 분야에서, 몇 가지 "특별한 것"으로 완벽한 정밀도를 갖는 것보다 임의의 분수로 높은 정밀도를 갖는 것이 더 유용합니다.
supercat

13
@Pacerier 그들은 어떻게 그들이 대답의 요점이었다 진 수레로 숫자를 저장하는 경우.
Mark Amery

3
@chux : 이진과 십진 형식의 정밀도 차이는 크지 않지만, 십진 형식의 경우 대 / 소문자의 10 : 1 차이는 이진 형식의 2 : 1 차이보다 훨씬 큽니다. 하드웨어 나 소프트웨어에서 효율적으로 구현할 수없는 것처럼 보이기 때문에 십진법 중 하나에서 효율적으로 작동하기 위해 하드웨어 또는 소프트웨어를 구축했는지 궁금합니다.
supercat

121

다른 정답 외에도 부동 소수점 산술 문제를 피하기 위해 값의 스케일링을 고려할 수 있습니다.

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

var result = 1.0 + 2.0;     // result === 3.0 returns true

... 대신에:

var result = 0.1 + 0.2;     // result === 0.3 returns false

표현식 0.1 + 0.2 === 0.3falseJavaScript로 반환 되지만 다행스럽게도 부동 소수점의 정수 산술은 정확하므로 소수 자릿수 표시 오류를 스케일링으로 피할 수 있습니다.

실제적인 예로, 정확도가 가장 중요한 부동 소수점 문제를 피하려면 1 을 센트 수를 나타내는 정수 ( 달러 2550대신 센트)로 처리 하는 것이 좋습니다 25.50.


1 Douglas Crockford : JavaScript : 좋은 부분 : 부록 A-끔찍한 부분 (105 페이지) .


3
문제는 변환 자체가 정확하지 않다는 것입니다. 16.08 * 100 = 1607.9999999999998. 우리는 숫자를 나누고 별도로 변환해야합니까 (16 * 100 + 08 = 1608과 같이)?
Jason

38
여기서 해결책은 모든 계산을 정수로 수행 한 다음 비율 (이 경우 100)로 나누고 데이터를 제시 할 때만 반올림하는 것입니다. 이렇게하면 계산이 항상 정확 해집니다.
David Granado

15
조금만 nitpick : 정수 산술은 부동 소수점 (정확한 의도)까지만 정확합니다. 숫자가 0x1p53보다 큰 경우 (Java 7의 16 진 부동 소수점 표기법을 사용하려면 = 9007199254740992) 해당 지점에서 ulp는 2이므로 0x1p53 + 1은 0x1p53으로 내림됩니다 (0x1p53 + 3은 0x1p53 +로 올림) 4, 반올림). :-D 그러나 당신의 숫자가 9 조보다 작다면, 당신은 괜찮을 것입니다. :-P
Chris Jester-Young

2
Jason, 당신은 결과를 반올림해야합니다 (int) (16.08 * 100 + 0.5)
Mikhail Semenov

@CodyBugstein " 그렇다면 어떻게 .1 + .2가 .3을 표시하게합니까? "소수점을 원하는 곳에 배치하는 사용자 정의 인쇄 기능을 작성하십시오.
RonJohn

113

내 대답은 꽤 길기 때문에 세 부분으로 나눕니다. 문제는 부동 소수점 수학에 관한 것이므로 기계가 실제로하는 일에 중점을 둡니다. 또한 두 배 (64 비트) 정밀도로 특정화했지만 인수는 모든 부동 소수점 산술에 동일하게 적용됩니다.

전문

IEEE 754 배정도 이진 부동 소수점 포맷 (binary64)는 숫자 형식의 수를 나타낸다

값 = (-1) ^ s * (1.m 51 m 50 ... m 2 m 1 m 0 ) 2 * 2 e-1023

64 비트로 :

  • 첫 번째 비트는 부호 비트입니다 . 1숫자가 음수이면 0그렇지 않으면 1 입니다.
  • 다음 11 비트는 지수 이며 1023 으로 오프셋 됩니다. 즉, 배정 밀도 숫자에서 지수 비트를 읽은 후 2의 거듭 제곱을 얻으려면 1023을 빼야합니다.
  • 나머지 52 비트는 유효 (또는 가수)입니다. 가수에서 '임시적' 1.2 진수 값의 최상위 비트 가이므로 항상 2를 생략합니다 1.

1 - IEEE 754는의 개념을 허용 제로 서명 - +0-0다르게 취급된다 : 1 / (+0)무한대입니다; 1 / (-0)음의 무한대입니다. 0 값의 경우 가수 및 지수 비트는 모두 0입니다. 참고 : 0 값 (+0 및 -0)은 명시 적으로 비정규 2 로 분류되지 않습니다 .

2- 오프셋 지수가 0이고 암시적인 비정규 숫자 의 경우에는 해당되지 않습니다 0.. 비정규 배정 밀도 숫자의 범위는 d min ≤ | x | ≤ d max , 여기서 d min (가장 작은 숫자가 아닌 가장 작은 숫자)는 2-1023-51 (≈ 4.94 * 10-324 )이고 d max (가장 큰 수가 비정규 수이며 가수가 전체적으로 1s로 구성됨 )는 2-1023입니다 + 1 - 2 -1023 - 51 (* 10 ≈ 2.225 -308 ).


배정도 숫자를 이진수로 바꾸기

배정도 부동 소수점 숫자를 이진수로 변환하는 많은 온라인 변환기가 존재 하지만 (예 : binaryconvert.com ) 배정도 숫자에 대한 IEEE 754 표현을 얻기위한 샘플 C # 코드가 있습니다 (세 부분을 콜론 ( :) 으로 구분 ) :

public static string BinaryRepresentation(double value)
{
    long valueInLongType = BitConverter.DoubleToInt64Bits(value);
    string bits = Convert.ToString(valueInLongType, 2);
    string leadingZeros = new string('0', 64 - bits.Length);
    string binaryRepresentation = leadingZeros + bits;

    string sign = binaryRepresentation[0].ToString();
    string exponent = binaryRepresentation.Substring(1, 11);
    string mantissa = binaryRepresentation.Substring(12);

    return string.Format("{0}:{1}:{2}", sign, exponent, mantissa);
}

요점 파악 : 원래 질문

(TL; DR 버전의 하단으로 건너 뛰기)

Cato Johnston (질문자)은 왜 0.1 + 0.2! = 0.3인지 물었습니다.

이진수로 작성 (콜론으로 세 부분을 분리)하면 IEEE 754 값은 다음과 같습니다.

0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010

가수는의 반복 자릿수로 구성 0011됩니다. 이것은 계산에 오류가있는 이유의 핵심입니다 -0.1, 0.2 및 0.3은 1 / 9, 1/3 또는 1/7을 초과 하는 유한 수의 이진 비트로 정확하게 이진수로 표현 될 수 없습니다 . 십진수 .

또한 지수의 거듭 제곱을 52만큼 줄이고 이진 표현의 점을 52 자리만큼 오른쪽으로 옮길 수 있습니다 ( 10-3 * 1.23 == 10-5 * 123 과 유사 ). 그러면 이진 표현을 a * 2 p 형식으로 나타내는 정확한 값으로 표현할 수 있습니다. 여기서 'a'는 정수입니다.

지수를 10 진수로 변환하고 오프셋을 제거하고 암시 적 1(대괄호로 표시)을 다시 추가하면 0.1과 0.2는 다음과 같습니다.

0.1 => 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 => 2^-3 * [1].1001100110011001100110011001100110011001100110011010
or
0.1 => 2^-56 * 7205759403792794 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125

두 개의 숫자를 더하려면 지수는 동일해야합니다.

0.1 => 2^-3 *  0.1100110011001100110011001100110011001100110011001101(0)
0.2 => 2^-3 *  1.1001100110011001100110011001100110011001100110011010
sum =  2^-3 * 10.0110011001100110011001100110011001100110011001100111
or
0.1 => 2^-55 * 3602879701896397  = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794  = 0.200000000000000011102230246251565404236316680908203125
sum =  2^-55 * 10808639105689191 = 0.3000000000000000166533453693773481063544750213623046875

합이 2 n * 1. {bbb} 형식이 아니므로 지수를 1 씩 증가시키고 소수 ( binary ) 점을 이동하여 다음을 얻습니다.

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)
    = 2^-54 * 5404319552844595.5 = 0.3000000000000000166533453693773481063544750213623046875

가수에 53 비트가 있습니다 (53 번째는 위의 꺾쇠 괄호 안에 있음). IEEE 754 의 기본 반올림 모드 는 ' 가장 반올림 '입니다. 즉, 숫자 x 가 두 값 ab 사이에 있으면 최하위 비트가 0 인 값이 선택됩니다.

a = 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
  = 2^-2  * 1.0011001100110011001100110011001100110011001100110011

x = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)

b = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
  = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

참고 와 B가 마지막 비트 다르다; + = . 이 경우 최하위 비트가 0 인 값은 b 이므로 합은 다음과 같습니다....00111...0100

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
    = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

0.3의 이진 표현은 다음과 같습니다.

0.3 => 2^-2  * 1.0011001100110011001100110011001100110011001100110011
    =  2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875

이는 0.1과 0.2의 합을 2-54 의 이진수 표현과 만 다릅니다 .

0.1과 0.2의 이진 표현은 IEEE 754가 허용하는 숫자를 가장 정확하게 표현한 것입니다. 기본 반올림 모드로 인해 이러한 표현을 추가하면 최하위 비트에서만 다른 값이 생성됩니다.

TL; DR

0.1 + 0.2IEEE 754 이진 표현으로 작성 하고 (세 부분을 구분하는 콜론으로) 이것을 (와 비교하면) 0.3구별 된 비트를 대괄호로 묶었습니다.

0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100]
0.3       => 0:01111111101:0011001100110011001100110011001100110011001100110[011]

십진수로 다시 변환하면이 값은 다음과 같습니다.

0.1 + 0.2 => 0.300000000000000044408920985006...
0.3       => 0.299999999999999988897769753748...

차이는 정확히 2 -54 5.5511151231258 × 10 ~ 인 -17 원래 값에 비해 미미한는 (많은 애플리케이션) -.

부동 소수점 숫자의 마지막 몇 비트를 비교하는 것은 본질적으로 " 모든 컴퓨터 과학자가 부동 소수점 산술에 대해 알아야 할 것 "(이 답변의 모든 주요 부분을 다루고 있음) 을 읽는 사람이 알듯이 본질적으로 위험 합니다.

대부분의 계산기는 이 문제를 해결하기 위해 추가 가드 숫자 를 사용합니다. 이 방법 0.1 + 0.20.3마지막 몇 비트가 반올림됩니다.


14
내 답변은 게시 후 바로 투표되었습니다. 그 이후로 많은 변경을했습니다 (원본에서는 생략했던 이진수로 0.1과 0.2를 쓸 때 반복되는 비트를 명시 적으로 지적하는 것을 포함하여). 다운 투표자가 이것을 볼 가능성이있는 경우, 답변을 개선 할 수 있도록 피드백을 제공해 주시겠습니까? IEEE 754의 합계 처리가 다른 답변에서 같은 방식으로 다루지 않기 때문에 내 답변에 새로운 것이 추가된다고 생각합니다. "모든 컴퓨터 과학자가 알아야 할 사항"은 동일한 내용을 다루지 만 제 대답 은 0.1 + 0.2의 경우를 구체적 으로 다룹니다 .
Wai Ha Lee

57

컴퓨터에 저장된 부동 소수점 숫자는 정수와 밑 수가 정수 부분에 곱한 지수와 두 부분으로 구성됩니다.

컴퓨터가 기본 10에서 작업한다면, 0.11 x 10⁻¹, 0.22 x 10⁻¹, 그리고 0.3것이다 3 x 10⁻¹. 정수 수학은 쉽고 정확하므로 추가 0.1 + 0.2하면 분명히 결과가 나타납니다 0.3.

컴퓨터는 일반적으로 기본 10에서 작동하지 않고 기본 2에서 작동합니다. 예를 들어 0.5is 1 x 2⁻¹and 0.25is 1 x 2⁻²등의 일부 값에 대해서는 정확한 결과를 얻을 수 있으며 결과를 3 x 2⁻², 또는 에 추가 할 수 0.75있습니다. 바로 그거죠.

문제는 10 진수로 정확하게 표현할 수 있지만 2 진수로는 표현할 수없는 숫자와 함께 발생합니다.이 숫자는 가장 가까운 숫자로 반올림해야합니다. 매우 일반적인 IEEE 64 비트 부동 소수점 형식을 가정하면 가장 가까운 숫자 0.13602879701896397 x 2⁻⁵⁵ , 그리고 가장 가까운 번호 0.2이다 7205759403792794 x 2⁻⁵⁵; 그것들을 합하면 10808639105689191 x 2⁻⁵⁵, 또는 정확한 10 진수 값이 0.3000000000000000444089209850062616169452667236328125됩니다. 부동 소수점 숫자는 일반적으로 표시를 위해 반올림됩니다.


2
@ Mark이 명확한 설명에 감사하지만 0.1 + 0.4가 정확히 0.5 (파이썬 3에서 가장 작음)를 더하는 이유는 의문입니다. 파이썬 3에서 float를 사용할 때 평등을 확인하는 가장 좋은 방법은 무엇입니까?
pchegoor

2
@ user2417881 IEEE 부동 소수점 연산에는 모든 연산에 대한 반올림 규칙이 있으며 때로는 반올림하여 두 숫자가 조금만 꺼져도 정확한 답을 얻을 수 있습니다. 의견이 너무 길어서 어쨌든 나는 전문가가 아닙니다. 이 답변에서 볼 수 있듯이 0.5는 이진수로 나타낼 수있는 몇 가지 소수 중 하나이지만 우연의 일치입니다. 동등성 테스트는 stackoverflow.com/questions/5595425/…를 참조하십시오 .
Mark Ransom

1
@ user2417881 귀하의 질문에 관심이있어서 전체 질문과 답변으로 바 꾸었습니다 : stackoverflow.com/q/48374522/5987
Mark Ransom

47

부동 소수점 반올림 오류. 에서 무엇 모든 컴퓨터 과학자한다 알고 부동 소수점 연산에 관하여 :

무한히 많은 실수를 유한 한 비트 수로 압축하려면 대략적인 표현이 필요합니다. 정수는 무한히 많지만 대부분의 프로그램에서 정수 계산 결과는 32 비트로 저장 될 수 있습니다. 반대로, 고정 된 수의 비트가 주어지면 실수를 사용한 대부분의 계산은 많은 비트를 사용하여 정확하게 표현할 수없는 수량을 생성합니다. 따라서 부동 소수점 계산 결과를 유한 표현에 맞추려면 종종 반올림해야합니다. 이 반올림 오류는 부동 소수점 계산의 특징입니다.


33

내 해결 방법 :

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

정밀도 는 추가하는 동안 소수점 이후에 유지하려는 자릿수를 나타냅니다.


30

많은 좋은 답변이 게시되었지만 하나 더 추가하고 싶습니다.

모든 숫자를 float / doubles 로 표현할 수있는 것은 아닙니다. 예를 들어 IEEE0.2 float 포인트 표준에서 숫자 "0.2"는 단 정밀도로 "0.200000003"으로 표시됩니다.

후드 아래에 실수를 저장하기위한 모델은 다음과 같이 실수를 나타냅니다.

여기에 이미지 설명을 입력하십시오

당신은 입력 할 수 있지만 0.2쉽게, FLT_RADIX그리고 DBL_RADIX2; "이진 부동 소수점 산술에 대한 IEEE 표준 (ISO / IEEE 표준 754-1985)"을 사용하는 FPU가있는 컴퓨터의 경우 10이 아닙니다.

따라서 그러한 숫자를 정확하게 나타내는 것은 약간 어렵습니다. 중간 계산없이이 변수를 명시 적으로 지정하더라도


28

이 유명한 배정도 문제와 관련된 통계입니다.

0.1의 단계 (0.1에서 100까지)를 사용 하여 모든 값 ( a + b )을 추가 할 때 ~ 15 %의 정확도 오차가 발생 합니다. 오류로 인해 값이 약간 커지거나 작아 질 수 있습니다. 여기 몇 가지 예가 있어요.

0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)

0.1의 단계 (100에서 0.1까지)를 사용하여 모든 값 ( a-b 여기서 a> b )을 뺄 때 ~ 34 %의 정확도 오차가 발생 합니다. 여기 몇 가지 예가 있어요.

0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)

* 15 % 및 34 %는 실제로 매우 크므로 정밀도가 중요 할 때는 항상 BigDecimal을 사용하십시오. 소수점 이하 두 자리수 (단계 0.01)를 사용하면 상황이 조금 더 악화됩니다 (18 % 및 36 %).


28

아니요, 깨지지는 않지만 대부분의 소수는 근사값이어야합니다

요약

부동 소수점 산술 정확하지만 불행히도 일반적인 10 진수 숫자 표현과 잘 맞지 않으므로 종종 우리가 쓴 것과 약간 다른 입력을주는 것으로 나타났습니다.

0.01, 0.02, 0.03, 0.04 ... 0.24와 같은 간단한 숫자조차도 이진 분수로 정확하게 표현할 수는 없습니다. 0.01, .02, .03 ...을 세면 0.25에 도달하지 않을 때까지 밑이 2 인 첫 번째 분수를 얻을 수 있습니다 . FP를 사용하려고 시도하면 0.01이 약간 벗어 났으므로 25를 더 정확하게 0.25에 더하는 유일한 방법은 가드 비트 및 반올림과 관련된 긴 인과 관계 체인이 필요했을 것입니다. 예측하기 어렵 기 때문에 우리는 손을 내밀고 "FP는 정확하지 않습니다 " 라고 말하지만 실제로는 그렇지 않습니다.

우리는 FP 하드웨어에베이스 10에서 단순 해 보이지만베이스 2에서 반복되는 부분을 지속적으로 제공합니다.

어떻게 이런일이 일어 났습니까?

우리가 10 진수로 쓸 때, 모든 분수 (특히, 모든 종료 소수점) 는 형식의 합리적인 수입니다.

           a / ( 2n x 5m )

이진수로 우리는 2 n 항만 얻습니다 .

           a / 2n

그래서 진수로, 우리는 나타낼 수 없습니다 (1) / 3 . 기수 10은 2를 소인수로 포함하므로 이진 분수 로 쓸 수있는 모든 수는 10 진수 로 쓸 수도 있습니다. 그러나 기본 10 분수 로 쓰는 것은 거의 바이너리로 표현할 수 없습니다. 0.01, 0.02, 0.03 ... 0.99 범위에서 FP 형식으로 3 개의 숫자 만 표현할 수 있습니다 : 0.25, 0.50 및 0.75, 1/4, 1/2 및 3/4이므로 모든 숫자 2 n 항만 사용하는 소수 입니다.

기본으로 10 우리는 나타낼 수 없습니다 (1) / 3 . 그러나 이진, 우리는 할 수 없어 (1) / (10) 또는 (1) / 3 .

따라서 모든 이진 분수는 십진수로 쓸 수 있지만 그 반대는 아닙니다. 실제로 대부분의 소수는 이진수로 반복됩니다.

다루기

개발자는 일반적으로 <epsilon 을 수행하도록 지시 받습니다. 비교 받습니다. C 라이브러리에서 정수 값으로 반올림 (round () 및 roundf (), 즉 FP 형식으로 유지)하는 것이 좋습니다. 특정 소수점 이하 자릿수로 반올림하면 출력과 관련된 대부분의 문제가 해결됩니다.

또한 실제 숫자를 차지하는 문제 (초기, 엄청나게 비싼 컴퓨터에서 FP가 발명 한 문제)에서 우주의 물리적 상수와 다른 모든 측정 값은 비교적 적은 수의 중요한 수치로만 알려져 있으므로 전체 문제 공간 어쨌든 "inexact"였습니다. FP "정확도"는 이러한 종류의 응용 프로그램에서 문제가되지 않습니다.

사람들이 콩 계산에 FP를 사용하려고 할 때 전체 문제가 실제로 발생합니다. 그것은 그것을 위해 효과가 있지만, 당신이 적분 값을 고수하는 경우에만 사용합니다. 이것이 우리가 십진 분수 소프트웨어 라이브러리를 모두 가지고있는 이유입니다.

Chris 의 피자 답변이 마음에 들었습니다 . 왜냐하면 "정확하지 않은"부분에 대한 일반적인 손글씨가 아니라 실제 문제를 설명하기 때문입니다. FP가 단순히 "정확하지 않은"경우이를 수정 하여 수십 년 전에 수행했을 수 있습니다. 우리가하지 않은 이유는 FP 형식이 작고 빠르며 많은 숫자를 처리하는 가장 좋은 방법이기 때문입니다. 또한, 이는 우주 시대와 무기 경쟁의 유산이며 소규모 메모리 시스템을 사용하는 매우 느린 컴퓨터의 큰 문제를 해결하려는 초기 시도입니다. (때로는 1 비트 스토리지를위한 개별 자기 코어 이지만 다른 이야기입니다. )

결론

은행에서 Bean을 계산하는 경우, 처음에 10 진 문자열 표시를 사용하는 소프트웨어 솔루션이 완벽하게 작동합니다. 그러나 양자 색채 역학이나 공기 역학을 그렇게 할 수는 없습니다.


가장 가까운 정수로 반올림하는 것이 모든 경우에 비교 문제를 해결하는 안전한 방법은 아닙니다. 0.4999998과 0.500001은 다른 정수로 반올림되므로 모든 반올림 컷 포인트 주위에는 "위험 구역"이 있습니다. (이 십진수 문자열은 아마도 IEEE 이진 수레로 정확하게 표현할 수 없다는 것을 알고 있습니다.)
Peter Cordes

1
또한 부동 소수점이 "레거시"형식이지만 매우 잘 설계되었습니다. 지금 다시 디자인하면 누구나 변경할 내용이 없습니다. 더 많이 배울수록 더 디자인 된 것 같습니다. 예를 들어 바이어스 지수는 연속 이진 부동 소수점이 연속적인 정수 표현을 의미하므로 nextafter()IEEE 부동 소수점의 이진 표현에 대해 정수 증분 또는 감소로 구현할 수 있습니다 . 또한 부동 소수점을 정수로 비교하고 음수가 둘 다일 때를 제외하고 정답을 얻을 수 있습니다 (기호 크기 대 2의 보수 때문에).
Peter Cordes

플로트는 십진수로 저장해야하며 이진이 아니어야하며 모든 문제가 해결되지 않습니다.
Ronen Festinger

" x / (2 ^ n + 5 ^ n) "은 " x / (2 ^ n * 5 ^ n) " 이 아니어야합니까 ?
Wai Ha Lee

@RonenFestinger-1/3은 어떻습니까?
Stephen C

19

덕트 테이프 솔루션을 사용해 보셨습니까?

오류가 발생하는시기를 판별하고 짧은 if 문으로 오류를 수정하십시오. 예쁘지는 않지만 일부 문제점의 경우 유일한 해결책이며이 중 하나입니다.

 if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
                    else { return n * 0.1 + 0.000000000000001 ;}    

나는 C #의 과학 시뮬레이션 프로젝트에서 같은 문제를 겪었습니다. 나비 효과를 무시하면 큰 뚱뚱한 용으로 변하고 a에서


19

최고의 솔루션 을 제공하기 위해 다음 방법을 발견했다고 말할 수 있습니다.

parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3

왜 이것이 최고의 솔루션인지 설명하겠습니다. 위에서 언급 한 다른 사람들이 대답했듯이 Javascript toFixed () 함수를 사용할 준비가되어 문제를 해결하는 것이 좋습니다. 그러나 대부분 문제가 발생할 수 있습니다.

이 같은 두 개의 부동 소수점 숫자를 추가하려고 상상 0.2하고 0.7여기있다 :0.2 + 0.7 = 0.8999999999999999 .

예상 결과는 0.9이 경우 1 자리 정밀도의 결과가 필요하다는 것을 의미했습니다. 그래서 당신은 사용해야 (0.2 + 0.7).tofixed(1) 했지만 toFixed ()에 특정 매개 변수를 줄 수는 없습니다.

`0.22 + 0.7 = 0.9199999999999999`

이 예제에서는 정밀도가 2 자리 여야 toFixed(2)하므로 주어진 모든 부동 수에 맞는 매개 변수는 무엇입니까?

모든 상황에서 10이라고하자 :

(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"

제길! 9 이후에 원치 않는 제로로 무엇을 하시겠습니까? 그것은 당신이 원하는대로 그것을 플로트로 변환 할 때입니다 :

parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9

이제 솔루션을 찾았으므로 다음과 같은 기능으로 제공하는 것이 좋습니다.

function floatify(number){
           return parseFloat((number).toFixed(10));
        }

직접 해보자 :

function floatify(number){
       return parseFloat((number).toFixed(10));
    }
 
function addUp(){
  var number1 = +$("#number1").val();
  var number2 = +$("#number2").val();
  var unexpectedResult = number1 + number2;
  var expectedResult = floatify(number1 + number2);
  $("#unexpectedResult").text(unexpectedResult);
  $("#expectedResult").text(expectedResult);
}
addUp();
input{
  width: 50px;
}
#expectedResult{
color: green;
}
#unexpectedResult{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> +
<input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> =
<p>Expected Result: <span id="expectedResult"></span></p>
<p>Unexpected Result: <span id="unexpectedResult"></span></p>

이 방법으로 사용할 수 있습니다 :

var x = 0.2 + 0.7;
floatify(x);  => Result: 0.9

W3 스쿨 다른 해결책은 당신이 번식 할 수 있으며 분할 위의 문제를 해결하기 위해, 너무가 제안 :

var x = (0.2 * 10 + 0.1 * 10) / 10;       // x will be 0.3

명심 (0.2 + 0.1) * 10 / 10가 같은 보이지만 전혀 작동하지 않습니다! 입력 플로트를 정확한 출력 플로트로 변환하는 함수로 적용 할 수 있기 때문에 첫 번째 솔루션을 선호합니다.


이것은 나를 진짜 두통으로 만들었다. 12 개의 부동 소수점 숫자를 합한 다음 그 숫자 인 경우 합계와 평균을 표시합니다. toFixed ()를 사용하면 두 숫자의 합을 수정할 수 있지만 여러 숫자를 합하면 도약이 중요합니다.
Nuryagdy Mustapayev

@Nuryagdy Mustapayev 12 개의 부동 소수점 수를 합한 다음 결과에 floatify () 함수를 사용하고 원하는 것을 수행하기 전에 테스트 한 결과 의도가 없었습니다.
모하마드 무사 비

나는 약 20 개의 매개 변수와 20 개의 수식이있는 상황에서 각 수식의 결과가 다른 솔루션에 의존하는 경우이 솔루션이 도움이되지 않았다고 말하고 있습니다.
Nuryagdy Mustapayev

16

컴퓨터가 계산 목적으로 이진수 (기본 2) 숫자 시스템을 사용하고 십진수 (기본 10)를 사용하기 때문에 이상한 숫자가 나타납니다.

이진수 나 십진수 또는 둘 다로 정확하게 표현할 수없는 대부분의 소수가 있습니다. 결과-반올림 (정확한) 숫자 결과입니다.


두 번째 단락을 전혀 이해하지 못합니다.
Nae

1
@Nae 저는 두 번째 단락을 "대부분의 분수는 정확히 10 진수 2 진수로 표현할 수 없습니다 . 따라서 대부분의 결과는 반올림됩니다. 여전히 표현에 내재 된 비트 / 숫자 수에 대해서는 정밀하지만 사용 중입니다. "
Steve Summit

15

이 질문의 많은 중복은 대부분 부동 소수점 반올림이 특정 숫자에 미치는 영향에 대해 묻습니다. 실제로, 관심있는 계산의 결과를 읽는 것보다는 정확한 결과를 보아 작동 방식에 대한 느낌을 얻는 것이 더 쉽습니다. 이러한 변환 등 - 일부 언어는 그 일의 방법을 제공 float하거나 double에를 BigDecimal자바로합니다.

이것은 언어에 구애받지 않는 질문이므로 10 진수를 부동 소수점으로 변환 하는 것과 같은 언어에 구애받지 않는 도구가 필요합니다 .

질문의 숫자에 적용하면 복식으로 처리됩니다.

0.1은 0.1000000000000000055511151231257827021181583404541015625로 변환됩니다.

0.2는 0.200000000000000011102230246251565404236316680908203125로 변환합니다.

0.3은 0.299999999999999988897769753748434595763683319091796875로 변환하고

0.30000000000000004는 0.3000000000000000444089209850062616169452667236328125로 변환됩니다.

처음 두 숫자를 수동으로 또는 전체 정밀도 계산기 와 같은 10 진수 계산기에 추가 에 추가하면 실제 입력의 정확한 합계는 0.3000000000000000166533453693773481063544750213623046875입니다.

반올림 오류가 0.3에 해당하는 반올림 인 경우 반올림 오류는 0.0000000000000000277555756156289135105907917022705078125입니다. 0.30000000000000004에 해당하는 반올림도 0.0000000000000000277555756156289135105907917022705078125를 반올림합니다. 둥글고 균일 한 타이 브레이커가 적용됩니다.

부동 소수점 변환기로 돌아 가면 0.30000000000000004의 원시 16 진수는 3fd3333333333334이며 이는 짝수로 끝나므로 올바른 결과입니다.


2
편집 한 사람에게 롤백했습니다. 코드 인용에 적합한 코드 따옴표를 고려합니다. 언어 중립적 인이 답변에는 인용 된 코드가 전혀 포함되어 있지 않습니다. 숫자는 영어 문장으로 사용될 수 있으며 코드로 변환되지 않습니다.
패트리샤 샤나 한

이것은 하지만, 읽기 쉽도록하지 포맷합니다 - 누군가가 코드로 숫자를 포맷 왜 가능성이있다.
Wai Ha Lee

... 또한, 짝수 행 라운드 받는 지칭 이진 표현 하지 진수 표현. 참조 , 예를 들어, 또는 .
Wai Ha Lee

@WaiHaLee 나는 십진수에 홀수 / 짝수 테스트를 적용하지 않았습니다. 16 진 숫자는 2 진 확장의 최하위 비트가 0 인 경우에도 마찬가지입니다.
Patricia Shanahan

14

아무도 이것을 언급하지 않았다면 ...

Python 및 Java와 같은 일부 고급 언어에는 이진 부동 소수점 제한을 극복하기위한 도구가 제공됩니다. 예를 들면 다음과 같습니다.

  • 내부적으로 소수 표기법으로 이진수 표기법이 아닌 내부적으로 숫자를 나타내는 파이썬 decimal모듈 과 Java BigDecimal클래스 . 둘 다 정밀도가 제한되어 있기 때문에 여전히 오류가 발생하기 쉽지만 이진 부동 소수점 산술과 관련된 가장 일반적인 문제를 해결합니다.

    10 센트와 20 센트는 항상 정확히 30 센트입니다.

    >>> 0.1 + 0.2 == 0.3
    False
    >>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
    True
    

    파이썬 decimal모듈은 IEEE 표준 854-1987을 기반으로 합니다.

  • 파이썬 fractions모듈 과 아파치 커먼 BigFraction클래스 . 둘 다 유리수를 (numerator, denominator)쌍 으로 나타내며 십진 부동 소수점 산술보다 더 정확한 결과를 제공 할 수 있습니다.

이러한 솔루션 중 어느 것도 완벽하지는 않지만 (특히 성능을 보거나 매우 높은 정밀도를 요구하는 경우) 이진 부동 소수점 산술과 관련된 많은 문제를 해결합니다.


14

그냥 추가해도됩니까? 사람들은 항상 이것이 컴퓨터 문제라고 가정하지만 손 (베이스 10)으로 계산하면 기본 문제 (1/3+1/3=2/3)=true와 마찬가지로 0.333 ...에서 0.333 ...까지 무한대를 추가하지 않으면 얻을 수 없습니다 (1/10+2/10)!==3/102, 0.333 + 0.333 = 0.666으로 자르고 0.667로 반올림하여 기술적으로 부정확 할 수 있습니다.

삼항으로 계산하고 1/3은 문제가되지 않습니다. 어쩌면 각 손에 15 개의 손가락이있는 인종은 왜 십진법이 깨 졌는지 묻습니다 ...


인간은 십진수를 사용하기 때문에 부동 소수점이 기본적으로 십진수로 표시되지 않는 정확한 이유가 없으므로 정확한 결과를 얻습니다.
Ronen Festinger

인간은 기본 10 (10 진수) 이외의 많은 기본을 사용합니다. 이진은 우리가 컴퓨팅에 가장 많이 사용하는 것입니다. '좋은 이유'는 모든 기본의 모든 분수를 간단히 표현할 수

@RonenFestinger 이진 산술은 숫자로 8 가지 기본 연산 만 필요하기 때문에 컴퓨터에서 쉽게 구현할 수 있습니다. $ a $, $ b $ $ 0,1 $ 만 있으면 $ \ operatorname {xor} (a, b) $ 및 $ \ operatorname {cb} (a, b) $. 여기서 xor는 배타적이며 cb는 "a 캐리 비트"이며 $ a = 1 = b $ 인 경우를 제외하고 모든 경우에 $ 0 $입니다. 하나 (사실 모든 작업의 ​​통역 성은 $ 2 $의 사례를 절약하고 필요한 것은 $ 6 $ 규칙입니다). 10 진수 확장은 $ 10 \ times 11 $ (10 진수 표기법)의 경우 저장해야하며 각 비트에 대해 $ 10 $의 다른 상태가 필요하며 캐리의 저장 공간을 낭비합니다.
Oskar Limka

@RonenFestinger-십진수가 더 정확하지 않습니다. 이것이이 답변의 말입니다. 선택한 기초에 대해 무한 반복되는 숫자 시퀀스를 제공하는 유리수 (분수)가 있습니다. 기록을 위해, 먼저 컴퓨터 중 일부는 사용의 기본을 번호 10 개 표현을하지만, 선구적인 컴퓨터 하드웨어 설계자는 곧 기본이 훨씬 쉽게하고 구현하는 것이 더 효율적이라고 결론을 내렸다.
Stephen C

9

디지털 컴퓨터에서 구현할 수있는 일종의 부동 소수점 수학은 반드시 실제 숫자와 그 근사값의 근사를 사용해야합니다. ( 표준 버전은 50 페이지가 넘는 문서로 구성되며 정오표 및 정교화를 처리 할위원회가 있습니다.)

이 근사값은 서로 다른 종류의 근사값을 혼합 한 것으로, 정확도와의 특정 편차 방식으로 인해 무시되거나 신중하게 설명 될 수 있습니다. 또한 대부분의 사람들이 눈치 채지 못한 채 지나가는 하드웨어 및 소프트웨어 수준에서 명백한 예외적 인 사례가 많이 있습니다.

무한 정밀도가 필요한 경우 (예 : 여러 개의 짧은 스탠드 인 대신 숫자 π 사용) 대신 기호 수학 프로그램을 작성하거나 사용해야합니다.

그러나 부동 소수점 수학의 가치가 불분명하고 논리와 오류가 빠르게 누적 될 수 있다는 아이디어에 만족한다면이를 위해 필요한 요구 사항과 테스트를 작성할 수 있습니다. 당신의 FPU.


9

재미를 위해 표준 C99의 정의에 따라 플로트 표현을 연주했으며 아래 코드를 작성했습니다.

이 코드는 3 개의 분리 된 그룹으로 부동의 이진 표현을 인쇄합니다

SIGN EXPONENT FRACTION

그런 다음 합을 인쇄하여 충분한 정밀도로 합산하면 실제로 하드웨어에 존재하는 값을 표시합니다.

따라서을 쓸 때 float x = 999...컴파일러는 함수에 xx의해 인쇄 된 합계 yy가 주어진 숫자와 같도록 함수에 의해 인쇄 된 비트 표현으로 해당 숫자를 변환합니다 .

실제로이 합계는 근사치 일뿐입니다. 숫자 999,999,999의 경우 컴파일러는 부동 소수점의 비트 표현으로 숫자 1,000,000,000을 삽입합니다.

코드가 끝나면 콘솔 세션을 연결합니다.이 세션에서는 하드웨어에 실제로 존재하는 두 상수 (PI 및 999999999)에 대한 항의 합계를 컴파일러가 삽입합니다.

#include <stdio.h>
#include <limits.h>

void
xx(float *x)
{
    unsigned char i = sizeof(*x)*CHAR_BIT-1;
    do {
        switch (i) {
        case 31:
             printf("sign:");
             break;
        case 30:
             printf("exponent:");
             break;
        case 23:
             printf("fraction:");
             break;

        }
        char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
        printf("%d ", b);
    } while (i--);
    printf("\n");
}

void
yy(float a)
{
    int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
    int fraction = ((1<<23)-1)&(*(int*)&a);
    int exponent = (255&((*(int*)&a)>>23))-127;

    printf(sign?"positive" " ( 1+":"negative" " ( 1+");
    unsigned int i = 1<<22;
    unsigned int j = 1;
    do {
        char b=(fraction&i)!=0;
        b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
    } while (j++, i>>=1);

    printf("*2^%d", exponent);
    printf("\n");
}

void
main()
{
    float x=-3.14;
    float y=999999999;
    printf("%lu\n", sizeof(x));
    xx(&x);
    xx(&y);
    yy(x);
    yy(y);
}

다음은 하드웨어에 존재하는 float의 실제 값을 계산하는 콘솔 세션입니다. bc메인 프로그램에서 출력 한 용어의 합계를 인쇄하는 데 사용 했습니다. 파이썬 repl이나 그와 비슷한 것에 그 합계를 삽입 할 수 있습니다.

-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872

그게 다야. 999999999의 값은 실제로

999999999.999999446351872

bc-3.14도 교란 되어 있음을 확인할 수도 있습니다. 의 scale요소 를 설정하는 것을 잊지 마십시오 bc.

표시된 합계는 하드웨어 내부의 금액입니다. 계산하여 얻은 값은 설정 한 스케일에 따라 다릅니다. 나는 그 scale요소를 15로 설정했습니다 . 수학적으로 무한 정도로 그것은 1,000,000,000 인 것 같습니다.


5

이것을 보는 또 다른 방법 : 숫자를 나타내는 64 비트가 사용됩니다. 결과적으로 2 ** 64 = 18,446,744,073,709,551,616을 초과하는 방법은 없습니다.

그러나 Math는 이미 0과 1 사이의 소수는 무한대라고 말합니다. IEE 754는 훨씬 더 큰 숫자 공간과 NaN 및 +/- 무한대에이 64 비트를 효율적으로 사용하도록 인코딩을 정의하므로 정확하게 표현 된 숫자 사이에 공백이 있습니다. 대략적인 숫자입니다.

불행히도 0.3은 틈새에 있습니다.


4

예를 들어 8 자리의 정확도로 10 진법으로 작업한다고 상상해보십시오. 당신은 여부를 확인

1/3 + 2 / 3 == 1

이것이 반환한다는 것을 배우십시오 false. 왜? 음, 우리가 가진 실제 숫자

1/3 = 0.333 ....2/3 = 0.666 ....

소수점 이하 8 자리에서 자르면

0.33333333 + 0.66666666 = 0.99999999

물론 이것은 1.00000000정확히 다릅니다 0.00000001.


고정 된 비트 수를 가진 이진수의 상황은 정확히 유사합니다. 실수로, 우리는

1/10 = 0.0001100110011001100 ... (기본 2)

1/5 = 0.0011001100110011001 ... (기본 2)

예를 들어 7 비트로 잘라 내면

0.0001100 + 0.0011001 = 0.0100101

반면에

3/10 = 0.01001100110011 ... (기본 2)

이것은 7 비트로 잘리고 있으며, 0.0100110이것들은 정확히 다릅니다 0.0000001.


정확한 숫자는 일반적으로 과학적 표기법으로 저장되므로 약간 더 미묘합니다. 예를 들어 지수와 가수에 얼마나 많은 비트를 할당했는지에 따라 1/10을 저장하는 대신 0.0001100다음과 같이 저장할 수 있습니다 1.10011 * 2^-4. 이것은 계산에 필요한 자릿수에 영향을줍니다.

결론은 이러한 반올림 오류로 인해 부동 소수점 숫자에 ==를 사용하고 싶지 않다는 것입니다. 대신 차이의 절대 값이 고정 된 작은 숫자보다 작은 지 확인할 수 있습니다.


4

Python 3.5부터는math.isclose() 대략적인 동등성을 테스트 하는 함수를 사용할 수 있습니다 .

>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False

3

이 스레드는 현재 부동 소수점 구현에 대한 일반적인 토론으로 약간 분기되었으므로 문제를 해결하는 프로젝트가 있다고 덧붙였습니다.

예를 들어 https://posithub.org/ 를 살펴보십시오. 여기에서는 더 적은 비트로 더 나은 정확도를 제공하는 posit (및 이전 모델 unum)라는 숫자 유형이 나와 있습니다. 내 이해가 정확하면 질문의 종류도 해결합니다. 매우 흥미로운 프로젝트, 그 뒤에있는 사람은 Dr. John Gustafson 수학자 입니다. 모든 것은 오픈 소스이며 C / C ++, Python, Julia 및 C # ( https://hastlayer.com/arithmetics )에 많은 실제 구현이 있습니다.


3

실제로는 매우 간단합니다. 베이스 10 시스템 (예 : 우리 시스템)을 사용하면베이스의 주요 요소를 사용하는 분수 만 표현할 수 있습니다. 10의 소인수는 2와 5입니다. 따라서 분모가 모두 10의 소인수를 사용하므로 1/2, 1/4, 1/5, 1/8 및 1/10은 모두 깨끗하게 표현할 수 있습니다. 대조적으로, 1 / 3, 1/6 및 1/7은 분모가 3 또는 7의 소인수를 사용하기 때문에 모두 반복되는 10 진수입니다. 이진 (또는 밑수 2)에서 유일한 소인수는 2입니다. 소수로 2 만 포함합니다. 이진수로 1/2, 1/4, 1/8은 모두 소수로 깔끔하게 표현됩니다. 반면 1/5 또는 1/10은 소수를 반복합니다. 따라서 기본 10 시스템에서 10 진수를 깨끗하게하는 동안 0.1 및 0.2 (1/10 및 1/5)는 컴퓨터가 작동하는 기본 2 시스템에서 10 진수를 반복합니다.이 반복 소수에 대해 수학을 수행 할 때,

에서 https://0.30000000000000004.com/


3

같은 진수 숫자 0.1, 0.2그리고 0.3정확히 이진 표현 부동 소수점 형식을 인코딩하지 않습니다. 에 대한 근사 0.1와 에 대한 근사의 합은에 사용 된 근사 와 0.2다르 0.3므로 0.1 + 0.2 == 0.3여기에서 더 명확하게 볼 수있는 허위는 다음 과 같습니다.

#include <stdio.h>

int main() {
    printf("0.1 + 0.2 == 0.3 is %s\n", 0.1 + 0.2 == 0.3 ? "true" : "false");
    printf("0.1 is %.23f\n", 0.1);
    printf("0.2 is %.23f\n", 0.2);
    printf("0.1 + 0.2 is %.23f\n", 0.1 + 0.2);
    printf("0.3 is %.23f\n", 0.3);
    printf("0.3 - (0.1 + 0.2) is %g\n", 0.3 - (0.1 + 0.2));
    return 0;
}

산출:

0.1 + 0.2 == 0.3 is false
0.1 is 0.10000000000000000555112
0.2 is 0.20000000000000001110223
0.1 + 0.2 is 0.30000000000000004440892
0.3 is 0.29999999999999998889777
0.3 - (0.1 + 0.2) is -5.55112e-17

이러한 계산을보다 안정적으로 평가하려면 부동 소수점 값에 10 진수 기반 표현을 사용해야합니다. C 표준은 기본적으로 이러한 유형을 지정하지 않지만 기술 보고서에 설명 된 확장으로 지정 합니다.

_Decimal32, _Decimal64_Decimal128유형 시스템에 사용할 수있는 (예를 들어, GCC는 그들을 지원 선택한 대상 하지만 연타는 그들을 지원하지 않는 OS X ).


1

Math.sum (자바 스크립트) .... 종류의 연산자 교체

.1 + .0001 + -.1 --> 0.00010000000000000286
Math.sum(.1 , .0001, -.1) --> 0.0001

Object.defineProperties(Math, {
    sign: {
        value: function (x) {
            return x ? x < 0 ? -1 : 1 : 0;
            }
        },
    precision: {
        value: function (value, precision, type) {
            var v = parseFloat(value), 
                p = Math.max(precision, 0) || 0, 
                t = type || 'round';
            return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
        }
    },
    scientific_to_num: {  // this is from https://gist.github.com/jiggzson
        value: function (num) {
            //if the number is in scientific notation remove it
            if (/e/i.test(num)) {
                var zero = '0',
                        parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
                        e = parts.pop(), //store the exponential part
                        l = Math.abs(e), //get the number of zeros
                        sign = e / l,
                        coeff_array = parts[0].split('.');
                if (sign === -1) {
                    num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
                } else {
                    var dec = coeff_array[1];
                    if (dec)
                        l = l - dec.length;
                    num = coeff_array.join('') + new Array(l + 1).join(zero);
                }
            }
            return num;
         }
     }
    get_precision: {
        value: function (number) {
            var arr = Math.scientific_to_num((number + "")).split(".");
            return arr[1] ? arr[1].length : 0;
        }
    },
    sum: {
        value: function () {
            var prec = 0, sum = 0;
            for (var i = 0; i < arguments.length; i++) {
                prec = this.max(prec, this.get_precision(arguments[i]));
                sum += +arguments[i]; // force float to convert strings to number
            }
            return Math.precision(sum, prec);
        }
    }
});

아이디어는 실수 대신 실수를 피하기 위해 연산자를 사용하는 것입니다

Math.sum은 사용할 정밀도를 자동 감지합니다.

Math.sum은 여러 인수를 허용합니다.


1
그래도 " 왜 이러한 부정확성이 발생합니까? " 라는 질문에 대답하지 못했습니다 .
Wai Ha Lee

당신이 옳았지만이 문제에 관한 자바 스크립트 이상한 행동으로 여기에 왔어요 ... 나는 단지 일종의 해결책을 공유하고 싶습니다
bortunac

당신이있어 여전히 비록 질문에 대답하지.
Wai Ha Lee

0

부동 소수점 주위 에이 흥미로운 문제를 보았습니다.

다음 결과를 고려하십시오.

error = (2**53+1) - int(float(2**53+1))
>>> (2**53+1) - int(float(2**53+1))
1

우리는 2**53+1모든 것이 잘 작동 할 때 중단 점을 분명히 볼 수 있습니다 2**53.

>>> (2**53) - int(float(2**53))
0

여기에 이미지 설명을 입력하십시오

이것은 배정도 바이너리 때문에 발생합니다 : IEEE 754 배정도 바이너리 부동 소수점 형식 : binary64

배정도 부동 소수점 형식에 대한 Wikipedia 페이지에서 :

배정도 이진 부동 소수점은 성능 및 대역폭 비용에도 불구하고 단 정밀도 부동 소수점보다 넓은 범위로 인해 PC에서 일반적으로 사용되는 형식입니다. 단 정밀도 부동 소수점 형식과 마찬가지로 동일한 크기의 정수 형식과 비교할 때 정수의 정밀도가 부족합니다. 일반적으로 간단히 이중이라고합니다. IEEE 754 표준은 binary64를 다음과 같이 지정합니다.

  • 부호 비트 : 1 비트
  • 지수 : 11 비트
  • 중요한 정밀도 : 53 비트 (52 개 명시 적으로 저장 됨)

여기에 이미지 설명을 입력하십시오

주어진 바이어스 지수와 52 비트 비율을 가진 주어진 64 비트 배정도 데이텀으로 가정 한 실제 값은 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

또는

여기에 이미지 설명을 입력하십시오

저에게 지적 해 주신 @a_guest에게 감사드립니다.


-1

다른 질문은이 질문에 대한 중복으로 명명되었습니다.

C ++에서 cout << x디버거가 표시하는 값 과 결과가 다른 이유는 x무엇입니까?

x질문에은입니다 float변수입니다.

한 가지 예는

float x = 9.9F;

디버거에 다음과 같이 표시 됩니다 . 작업 9.89999962의 출력은 cout입니다 9.9.

대답은 cout의 기본 정밀도 float는 6이므로 10 진수로 반올림됩니다.

참조 위해 여기 를 보십시오


1
IMO-여기에 게시하면 잘못된 접근 방식입니다. 답답하지는 않지만 원래 질문에 대한 답변이 필요한 사람들 (지금은 삭제되었습니다!)은 여기에서 찾을 수 없습니다. 작업 내용이 저장 될 가치가 있다고 생각되면 1) 이것이 실제로 응답하는 다른 Q를 찾고 2) 자체 응답 질문을 작성하는 것이 좋습니다.
Stephen C
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.