다음 코드를 고려하십시오.
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
왜 이러한 부정확성이 발생합니까?
다음 코드를 고려하십시오.
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
왜 이러한 부정확성이 발생합니까?
답변:
이진 부동 소수점 수학은 다음과 같습니다. 대부분의 프로그래밍 언어에서는 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-4
C99 hexfloat 표기법과 유사하며, 여기서 ...
9는 끝없는 시퀀스를 나타냅니다.상수 0.2
와 0.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
특정 응용 프로그램에 대해 선택해야합니다. 허용 할 "흔들기 방"의 양과 비교할 가장 큰 숫자 (정밀성 문제로 인해)와 관련이 있습니다. ). 선택한 언어에서 "엡실론"스타일 상수를주의하십시오. 이들은 공차 값으로 사용 되지 않습니다 .
부동 소수점 하드웨어를 디자인하고 빌드하기 때문에 하드웨어 디자이너의 관점을 추가해야한다고 생각합니다. 오류의 원인을 아는 것은 소프트웨어에서 일어나는 일을 이해하는 데 도움이 될 수 있으며 궁극적으로 이것이 부동 소수점 오류가 발생하고 시간이 지남에 따라 축적되는 이유를 설명하는 데 도움이되기를 바랍니다.
엔지니어링 관점에서 볼 때 대부분의 부동 소수점 연산에는 부동 소수점 계산을 수행하는 하드웨어에 마지막 한 단위의 절반 미만의 오류 만 있으면되므로 일부 오류 요소가 있습니다. 따라서 대부분의 하드웨어는 부동 소수점 분할에서 특히 문제가되는 단일 작업 에 대해 마지막 위치에서 한 단위의 절반 미만의 오류를 생성하는 데 필요한 정밀도로 중지됩니다 . 단일 연산을 구성하는 것은 장치가 수행하는 피연산자 수에 따라 다릅니다. 대부분 2 개이지만 일부 단위에는 3 개 이상의 피연산자가 필요합니다. 이 때문에 시간이 지남에 따라 오류가 더해 지므로 반복 된 작업으로 인해 원하는 오류가 발생한다고 보장 할 수 없습니다.
대부분의 프로세서는 IEEE-754 표준을 따르지만 일부는 비정규 화 된 표준 또는 다른 표준을 사용합니다. 예를 들어 IEEE-754에는 비정규 화 된 모드가있어 정밀도를 희생시키면서 매우 작은 부동 소수점 숫자를 표현할 수 있습니다. 그러나 다음은 일반적인 작동 모드 인 IEEE-754의 정규화 된 모드를 다룹니다.
IEEE-754 표준에서, 하드웨어 설계자들은 마지막 장소에서 한 단위의 절반보다 작은 오류 / 엡실론의 값을 허용하며 결과는 마지막 한 단위의 절반보다 작아야합니다. 하나의 작업 장소. 이것은 반복되는 작업이있을 때 오류가 더해지는 이유를 설명합니다. IEEE-754 배정도의 경우 53 비트는 부동 소수점 숫자 (예 : 5.3e5의 5.3)의 가수 (mantissa)라고하는 숫자 부분 (정규화 된)을 나타내는 데 사용되므로 54 번째 비트입니다. 다음 섹션에서는 다양한 부동 소수점 연산에서 하드웨어 오류의 원인에 대해 자세히 설명합니다.
부동 소수점 나누기 오류의 주요 원인은 몫을 계산하는 데 사용되는 나누기 알고리즘입니다. 대부분의 컴퓨터 시스템은 주로 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의 절반보다 작을 때까지 단계를 반복합니다. 마지막 장소에서 단위. 느린 나눗셈 방법은 각 단계에서 몫의 고정 자릿수를 계산하며 일반적으로 구축 비용이 저렴하고 빠른 나눗셈 방법은 단계 당 가변 자릿수를 계산하며 일반적으로 구축 비용이 더 비쌉니다. 나누기 방법의 가장 중요한 부분은 대부분이 역수 의 근사 로 반복 곱셈에 의존 하므로 오류가 발생하기 쉽다는 것입니다.
모든 작업에서 반올림 오류의 또 다른 원인은 IEEE-754가 허용하는 최종 응답의 다른 절단 모드입니다. 잘림, 0으로 반올림 , 가장 가까운 반올림 (기본값), 반올림 및 반올림이 있습니다. 모든 방법은 단일 작업을 위해 마지막에 한 단위 미만의 오차 요소를 도입합니다. 시간이 지남에 따라 작업을 반복하면 잘림도 결과 오류에 누적됩니다. 이 잘림 오류는 지수에 특히 문제가 있으며, 여기에는 어떤 형태의 반복 곱셈이 포함됩니다.
부동 소수점 계산을 수행하는 하드웨어는 단일 작업의 마지막 위치에서 한 단위의 절반 미만의 오류로 결과를 생성하기 만하면되기 때문에 오류를 보지 않으면 반복 된 작업보다 오류가 커집니다. 이는 경계 오류가 필요한 계산에서 수학자 들이 IEEE-754 의 마지막 위치에서 가장 가까운 자리에서 짝수 자릿수를 사용하는 등의 방법을 사용하는 이유입니다. 아웃 및 간격 산술 의 변화와 함께 IEEE 754 라운딩 모드반올림 오류를 예측하고 수정합니다. IEEE-754의 기본 반올림 모드는 다른 반올림 모드에 비해 상대적 오류가 낮기 때문에 가장 가까운 짝수로 반올림합니다 (마지막).
기본 반올림 모드 인 마지막 자리에서 가장 가까운 자리수까지도 한 번의 작업으로 마지막 자리에서 한 단위의 절반 미만의 오류를 보장합니다. 잘림, 반올림 및 반올림 만 사용하면 마지막 위치에서 한 단위의 절반보다 크지 만 마지막 위치에서 하나보다 적은 오류가 발생할 수 있으므로 이러한 모드는 그렇지 않은 경우 권장되지 않습니다. 간격 산술에 사용됩니다.
요컨대, 부동 소수점 연산에서 오류가 발생하는 근본적인 이유는 하드웨어에서 잘림과 나눗셈에서 역수가 잘림의 조합입니다. IEEE-754 표준은 단일 작업의 마지막 위치에서 한 단위의 절반 미만의 오류 만 필요하므로, 반복 작업에 대한 부동 소수점 오류는 수정하지 않으면 더해집니다.
.1 또는 1/10을 기수 2 (이진)로 변환하면 기수 10에서 1/3을 나타내려고하는 것처럼 소수점 다음에 반복되는 패턴이 나타납니다. 일반 부동 소수점 방법을 사용하여 정확한 수학.
여기에있는 대부분의 답변은이 질문을 매우 건조하고 기술적 인 용어로 해결합니다. 정상적인 인간이 이해할 수있는 용어로이 문제를 해결하고 싶습니다.
피자를 자르려고한다고 상상해보십시오. 피자 조각을 정확하게 자를 수있는 로봇 피자 커터가 있습니다 반으로 . 피자 전체를 반으로 줄이거 나 기존 슬라이스를 반으로 줄 수 있지만, 어쨌든 반은 항상 정확합니다.
그 피자 커터는 매우 미세한 움직임을 가지고 있으며, 당신이 전체 피자로 시작하는 경우, 그 반감, 그리고 작은 조각 각 시간을 절반으로 계속, 당신은 반감을 할 수있는 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의 슬라이스를 얻을 수있을 때 사용해야합니다.
부동 소수점 반올림 오류. 소수점 이하 자릿수 5가 누락되어 10을 밑으로하는 것처럼 0.1을 밑이 2로 정확하게 표현할 수 없습니다. 1/3은 소수를 나타 내기 위해 무한한 자릿수를 취하지 만 밑이 3이면 "0.1"입니다. 0.1은 10 진수가 아닌 2 진수의 무한 자릿수를 사용합니다. 그리고 컴퓨터에는 무한한 양의 메모리가 없습니다.
다른 정답 외에도 부동 소수점 산술 문제를 피하기 위해 값의 스케일링을 고려할 수 있습니다.
예를 들면 다음과 같습니다.
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.3
은 false
JavaScript로 반환 되지만 다행스럽게도 부동 소수점의 정수 산술은 정확하므로 소수 자릿수 표시 오류를 스케일링으로 피할 수 있습니다.
실제적인 예로, 정확도가 가장 중요한 부동 소수점 문제를 피하려면 1 을 센트 수를 나타내는 정수 ( 달러 2550
대신 센트)로 처리 하는 것이 좋습니다 25.50
.
1 Douglas Crockford : JavaScript : 좋은 부분 : 부록 A-끔찍한 부분 (105 페이지) .
내 대답은 꽤 길기 때문에 세 부분으로 나눕니다. 문제는 부동 소수점 수학에 관한 것이므로 기계가 실제로하는 일에 중점을 둡니다. 또한 두 배 (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 입니다.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 (가장 큰 수가 비정규 수이며 가수가 전체적으로 1
s로 구성됨 )는 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 가 두 값 a 와 b 사이에 있으면 최하위 비트가 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 이므로 합은 다음과 같습니다....0011
1
...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.2
IEEE 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.2
은 0.3
마지막 몇 비트가 반올림됩니다.
컴퓨터에 저장된 부동 소수점 숫자는 정수와 밑 수가 정수 부분에 곱한 지수와 두 부분으로 구성됩니다.
컴퓨터가 기본 10에서 작업한다면, 0.1
것 1 x 10⁻¹
, 0.2
것 2 x 10⁻¹
, 그리고 0.3
것이다 3 x 10⁻¹
. 정수 수학은 쉽고 정확하므로 추가 0.1 + 0.2
하면 분명히 결과가 나타납니다 0.3
.
컴퓨터는 일반적으로 기본 10에서 작동하지 않고 기본 2에서 작동합니다. 예를 들어 0.5
is 1 x 2⁻¹
and 0.25
is 1 x 2⁻²
등의 일부 값에 대해서는 정확한 결과를 얻을 수 있으며 결과를 3 x 2⁻²
, 또는 에 추가 할 수 0.75
있습니다. 바로 그거죠.
문제는 10 진수로 정확하게 표현할 수 있지만 2 진수로는 표현할 수없는 숫자와 함께 발생합니다.이 숫자는 가장 가까운 숫자로 반올림해야합니다. 매우 일반적인 IEEE 64 비트 부동 소수점 형식을 가정하면 가장 가까운 숫자 0.1
는3602879701896397 x 2⁻⁵⁵
, 그리고 가장 가까운 번호 0.2
이다 7205759403792794 x 2⁻⁵⁵
; 그것들을 합하면 10808639105689191 x 2⁻⁵⁵
, 또는 정확한 10 진수 값이 0.3000000000000000444089209850062616169452667236328125
됩니다. 부동 소수점 숫자는 일반적으로 표시를 위해 반올림됩니다.
부동 소수점 반올림 오류. 에서 무엇 모든 컴퓨터 과학자한다 알고 부동 소수점 연산에 관하여 :
무한히 많은 실수를 유한 한 비트 수로 압축하려면 대략적인 표현이 필요합니다. 정수는 무한히 많지만 대부분의 프로그램에서 정수 계산 결과는 32 비트로 저장 될 수 있습니다. 반대로, 고정 된 수의 비트가 주어지면 실수를 사용한 대부분의 계산은 많은 비트를 사용하여 정확하게 표현할 수없는 수량을 생성합니다. 따라서 부동 소수점 계산 결과를 유한 표현에 맞추려면 종종 반올림해야합니다. 이 반올림 오류는 부동 소수점 계산의 특징입니다.
많은 좋은 답변이 게시되었지만 하나 더 추가하고 싶습니다.
모든 숫자를 float / doubles 로 표현할 수있는 것은 아닙니다. 예를 들어 IEEE0.2 float 포인트 표준에서 숫자 "0.2"는 단 정밀도로 "0.200000003"으로 표시됩니다.
후드 아래에 실수를 저장하기위한 모델은 다음과 같이 실수를 나타냅니다.
당신은 입력 할 수 있지만 0.2
쉽게, FLT_RADIX
그리고 DBL_RADIX
2; "이진 부동 소수점 산술에 대한 IEEE 표준 (ISO / IEEE 표준 754-1985)"을 사용하는 FPU가있는 컴퓨터의 경우 10이 아닙니다.
따라서 그러한 숫자를 정확하게 나타내는 것은 약간 어렵습니다. 중간 계산없이이 변수를 명시 적으로 지정하더라도
이 유명한 배정도 문제와 관련된 통계입니다.
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 %).
요약
부동 소수점 산술 은 정확하지만 불행히도 일반적인 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 진 문자열 표시를 사용하는 소프트웨어 솔루션이 완벽하게 작동합니다. 그러나 양자 색채 역학이나 공기 역학을 그렇게 할 수는 없습니다.
nextafter()
IEEE 부동 소수점의 이진 표현에 대해 정수 증분 또는 감소로 구현할 수 있습니다 . 또한 부동 소수점을 정수로 비교하고 음수가 둘 다일 때를 제외하고 정답을 얻을 수 있습니다 (기호 크기 대 2의 보수 때문에).
최고의 솔루션 을 제공하기 위해 다음 방법을 발견했다고 말할 수 있습니다.
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
가 같은 보이지만 전혀 작동하지 않습니다! 입력 플로트를 정확한 출력 플로트로 변환하는 함수로 적용 할 수 있기 때문에 첫 번째 솔루션을 선호합니다.
컴퓨터가 계산 목적으로 이진수 (기본 2) 숫자 시스템을 사용하고 십진수 (기본 10)를 사용하기 때문에 이상한 숫자가 나타납니다.
이진수 나 십진수 또는 둘 다로 정확하게 표현할 수없는 대부분의 소수가 있습니다. 결과-반올림 (정확한) 숫자 결과입니다.
이 질문의 많은 중복은 대부분 부동 소수점 반올림이 특정 숫자에 미치는 영향에 대해 묻습니다. 실제로, 관심있는 계산의 결과를 읽는 것보다는 정확한 결과를 보아 작동 방식에 대한 느낌을 얻는 것이 더 쉽습니다. 이러한 변환 등 - 일부 언어는 그 일의 방법을 제공 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이며 이는 짝수로 끝나므로 올바른 결과입니다.
아무도 이것을 언급하지 않았다면 ...
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)
쌍 으로 나타내며 십진 부동 소수점 산술보다 더 정확한 결과를 제공 할 수 있습니다.
이러한 솔루션 중 어느 것도 완벽하지는 않지만 (특히 성능을 보거나 매우 높은 정밀도를 요구하는 경우) 이진 부동 소수점 산술과 관련된 많은 문제를 해결합니다.
그냥 추가해도됩니까? 사람들은 항상 이것이 컴퓨터 문제라고 가정하지만 손 (베이스 10)으로 계산하면 기본 문제 (1/3+1/3=2/3)=true
와 마찬가지로 0.333 ...에서 0.333 ...까지 무한대를 추가하지 않으면 얻을 수 없습니다 (1/10+2/10)!==3/10
2, 0.333 + 0.333 = 0.666으로 자르고 0.667로 반올림하여 기술적으로 부정확 할 수 있습니다.
삼항으로 계산하고 1/3은 문제가되지 않습니다. 어쩌면 각 손에 15 개의 손가락이있는 인종은 왜 십진법이 깨 졌는지 묻습니다 ...
디지털 컴퓨터에서 구현할 수있는 일종의 부동 소수점 수학은 반드시 실제 숫자와 그 근사값의 근사를 사용해야합니다. ( 표준 버전은 50 페이지가 넘는 문서로 구성되며 정오표 및 정교화를 처리 할위원회가 있습니다.)
이 근사값은 서로 다른 종류의 근사값을 혼합 한 것으로, 정확도와의 특정 편차 방식으로 인해 무시되거나 신중하게 설명 될 수 있습니다. 또한 대부분의 사람들이 눈치 채지 못한 채 지나가는 하드웨어 및 소프트웨어 수준에서 명백한 예외적 인 사례가 많이 있습니다.
무한 정밀도가 필요한 경우 (예 : 여러 개의 짧은 스탠드 인 대신 숫자 π 사용) 대신 기호 수학 프로그램을 작성하거나 사용해야합니다.
그러나 부동 소수점 수학의 가치가 불분명하고 논리와 오류가 빠르게 누적 될 수 있다는 아이디어에 만족한다면이를 위해 필요한 요구 사항과 테스트를 작성할 수 있습니다. 당신의 FPU.
재미를 위해 표준 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 인 것 같습니다.
예를 들어 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
. 이것은 계산에 필요한 자릿수에 영향을줍니다.
결론은 이러한 반올림 오류로 인해 부동 소수점 숫자에 ==를 사용하고 싶지 않다는 것입니다. 대신 차이의 절대 값이 고정 된 작은 숫자보다 작은 지 확인할 수 있습니다.
Python 3.5부터는math.isclose()
대략적인 동등성을 테스트 하는 함수를 사용할 수 있습니다 .
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False
이 스레드는 현재 부동 소수점 구현에 대한 일반적인 토론으로 약간 분기되었으므로 문제를 해결하는 프로젝트가 있다고 덧붙였습니다.
예를 들어 https://posithub.org/ 를 살펴보십시오. 여기에서는 더 적은 비트로 더 나은 정확도를 제공하는 posit (및 이전 모델 unum)라는 숫자 유형이 나와 있습니다. 내 이해가 정확하면 질문의 종류도 해결합니다. 매우 흥미로운 프로젝트, 그 뒤에있는 사람은 Dr. John Gustafson 수학자 입니다. 모든 것은 오픈 소스이며 C / C ++, Python, Julia 및 C # ( https://hastlayer.com/arithmetics )에 많은 실제 구현이 있습니다.
실제로는 매우 간단합니다. 베이스 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 진수를 반복합니다.이 반복 소수에 대해 수학을 수행 할 때,
같은 진수 숫자 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 ).
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은 여러 인수를 허용합니다.
다음 결과를 고려하십시오.
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에게 감사드립니다.
다른 질문은이 질문에 대한 중복으로 명명되었습니다.
C ++에서 cout << x
디버거가 표시하는 값 과 결과가 다른 이유는 x
무엇입니까?
x
질문에은입니다 float
변수입니다.
한 가지 예는
float x = 9.9F;
디버거에 다음과 같이 표시 됩니다 . 작업 9.89999962
의 출력은 cout
입니다 9.9
.
대답은 cout
의 기본 정밀도 float
는 6이므로 10 진수로 반올림됩니다.