문제

다음 코드를 고려해보세요.

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

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

도움이 되었습니까?

해결책

이진 부동 소수점 수학은 이와 같습니다. 대부분의 프로그래밍 언어에서는 IEEE 754 표준. JavaScript는 64 비트 부동 소수점 표현을 사용합니다. double. 문제의 핵심은 숫자 가이 형식으로 표시되었다는 것입니다. 합리적 숫자 (예 : 0.1, 그것은 1/10) 분모가 2의 힘이 아닌 사람은 정확히 표현 될 수 없습니다.

을 위한 0.1 표준에서 binary64 형식, 표현은 정확히 다음과 같이 쓸 수 있습니다

대조적으로, 합리적 번호 0.1, 그것은 1/10, 정확히 작성할 수 있습니다

  • 0.1 소수점에서 또는
  • 0x1.99999999999999...p-4 C99 16 진수 표기법의 아날로그에서 ... 끝없는 9의 순서를 나타냅니다.

상수 0.2 그리고 0.3 귀하의 프로그램에서는 또한 실제 가치에 대한 근사치가됩니다. 가장 가까운 일이 발생합니다 double 에게 0.2 합리적 번호보다 큽니다 0.2 그러나 가장 가까운 것 double 에게 0.3 합리적 번호보다 작습니다 0.3. 의 합 0.1 그리고 0.2 합리적 번호보다 더 크게 증가합니다 0.3 따라서 코드의 상수에 동의하지 않습니다.

부동 소수점 산술 문제에 대한 상당히 포괄적 인 치료는 다음과 같습니다 모든 컴퓨터 과학자가 부동 소수점 산술에 대해 알아야 할 것. 소화기 쉬운 설명은 참조하십시오 플로팅 포인트 gui.de.

참고 사항 : 모든 위치 (Base-N) 번호 시스템은이 문제를 정밀하게 공유합니다.

평범한 오래된 소수점 (기본 10) 숫자는 동일한 문제가 있으므로 1/3과 같은 숫자가 0.33333333으로 끝나는 이유입니다.

소수 시스템에서 쉽게 표현하기 쉽지만 이진 시스템에 맞지 않는 숫자 (3/10)를 우연히 발견했습니다. 1/16은 소수점 (0.0625)의 못생긴 숫자이지만, 이진에서는 10,000 번째가 소수점 (0.0001)처럼 깔끔하게 보입니다. 우리의 일상 생활에서 Base-2 번호 시스템을 사용하는 습관, 당신은 그 숫자를보고, 무언가를 반복하고 반복해서 반복해서 반복해서 도착할 수 있다는 것을 본능적으로 이해할 것입니다.

** 물론, 그것은 플로팅 포인트 숫자가 메모리에 저장되는 방식이 아닙니다 (과학적 표기법을 사용합니다). 그러나 우리가 일반적으로 작업하는 데 관심이있는 "실제 세계"숫자는 종종 10의 힘이기 때문에 바이너리 플로팅 포인트 정밀 오류가 발생하는 경향이 있다는 점을 설명합니다. 오늘. 그렇기 때문에 우리는 "7 명 중 5 명"대신 71%와 같은 것을 말할 수있는 이유입니다 (5/7은 10 진수로 정확하게 표현할 수 없기 때문에 71%는 근사치입니다).

따라서 아니오 : 바이너리 플로팅 포인트 번호는 깨지지 않습니다. 다른 모든 기본 번호 시스템만큼 불완전합니다 :)

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

실제로,이 정밀 문제는 반올림 기능을 사용하여 부동 소수점 번호를 반올림하지만, 당신이 그것을 표시하기 전에 관심있는 소수점이자 많은 장소로 반올림해야한다는 것을 의미합니다.

또한 평등 테스트를 어느 정도의 허용량을 허용하는 비교로 대체해야합니다. 이는 다음을 의미합니다.

하다 ~ 아니다 하다 if (float1 == float2) { ... }

대신 if (Math.Abs(float1 - float2) < myToleranceValue) { ... }.

특정 응용 프로그램을 위해 MyToleranceValue를 선택해야합니다. 그리고 허용 할 준비가 된 "Wiggle Room"의 양과 비교할 최대의 숫자는 (정밀도의 상실로 인해 어떤 것도 비교할 수있는 가장 많은 수와 관련이 있습니다. 문제). 선택한 언어의 "double.epsilon"스타일 상수를 조심하십시오 (JavaScript의 NUMBER.EPSILON). 이것들은 ~ 아니다 공차 값으로 사용됩니다.

공차에 대한 자세한 정보 :

(편집자의 Shameless Self Promotion- 납치를 위해 죄송합니다)

공차를 선택하는 방법과 숫자를 피하는 이유에 대한 자세한 설명을 모았습니다. https://dev.to/alldanielscott/how-to-compare-numbers-correctly-in-javascript-1l4i

다른 팁

하드웨어 디자이너의 관점

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

1.개요

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

2.표준

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

IEEE-754 표준에서 하드웨어 설계자는 마지막 위치에서 한 장치의 절반 미만인 한 모든 오류/엡실론 값을 허용하며 결과는 마지막 위치에서 한 장치의 절반 미만이면 됩니다. 한 번의 작업을 위한 장소입니다.이는 반복되는 작업이 있을 때 오류가 추가되는 이유를 설명합니다.IEEE-754 배정밀도의 경우 이는 54번째 비트입니다. 53비트는 부동 소수점 숫자(예: 가수라고도 함)의 숫자 부분(정규화된)을 나타내는 데 사용되기 때문입니다(예:5.3e5의 5.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 분할과 같은 빠른 분할;각 항목은 가능한 가장 낮은 오류를 생성하기 위해 나누기 알고리즘에 따라 수정됩니다.어쨌든 모든 상호작용은 근사치 실제 상호의 일부 오류 요소를 도입합니다.느린 나눗셈과 빠른 나눗셈 방법 모두 반복적으로 몫을 계산합니다.각 단계에서 일부 비트 수의 몫이 계산된 다음 결과가 피제수에서 차감되고 나눗셈은 오류가 마지막 위치에서 한 단위의 절반 미만이 될 때까지 단계를 반복합니다.느린 나눗셈 방법은 각 단계에서 고정된 수의 몫을 계산하고 일반적으로 구축 비용이 저렴하며, 빠른 나눗셈 방법은 단계당 가변 자릿수를 계산하며 일반적으로 구축 비용이 더 비쌉니다.나눗셈 방법의 가장 중요한 부분은 대부분이 다음과 같은 반복적인 곱셈에 의존한다는 것입니다. 근사 상호적이므로 오류가 발생하기 쉽습니다.

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

모든 작업에서 반올림 오류가 발생하는 또 다른 원인은 IEEE-754에서 허용하는 최종 답변의 다양한 잘림 모드입니다.0에 가깝게 반올림된 잘린 부분이 있습니다. 가장 가까운 값으로 반올림(기본값), 반올림, 반올림.모든 방법은 단일 작업에 대해 마지막으로 1단위 미만의 오류 요소를 도입합니다.시간이 지남에 따라 반복되는 작업으로 인해 잘림도 결과 오류에 누적적으로 추가됩니다.이러한 잘림 오류는 어떤 형태로든 반복되는 곱셈을 포함하는 지수 계산에서 특히 문제가 됩니다.

5.반복 작업

부동 소수점 계산을 수행하는 하드웨어는 단일 작업에 대해 마지막 위치에서 한 단위의 절반 미만의 오류가 있는 결과만 생성하면 되기 때문에 관찰하지 않으면 반복 작업을 통해 오류가 커집니다.이것이 제한된 오류가 필요한 계산에서 수학자들이 가장 가까운 값으로 반올림하는 등의 방법을 사용하는 이유입니다. 마지막 자리의 짝수 IEEE-754의 이유는 시간이 지남에 따라 오류가 서로 상쇄될 가능성이 높기 때문입니다. 간격 산술 다양한 변형이 결합된 IEEE 754 반올림 모드 반올림 오류를 예측하고 수정합니다.다른 반올림 모드에 비해 상대 오차가 낮기 때문에 가장 가까운 짝수(마지막 자리)로 반올림하는 것이 IEEE-754의 기본 반올림 모드입니다.

기본 반올림 모드는 가장 가까운 값으로 반올림됩니다. 마지막 자리의 짝수, 한 번의 작업에 대해 마지막 위치에서 한 단위의 절반 미만의 오류를 보장합니다.잘림, 반올림, 내림을 단독으로 사용하면 마지막 위치에서 한 단위의 1/2보다 크지만 마지막 위치에서는 한 단위 미만의 오류가 발생할 수 있으므로 이러한 모드는 권장되지 않습니다. 간격 산술에 사용됩니다.

6.요약

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

.1 또는 1/10을베이스 2 (바이너리)로 변환하면 10 진수 10에서 1/3을 나타내는 것과 마찬가지로 소수점 이후 반복 패턴을 얻습니다. 값은 정확하지 않으므로 할 수 없습니다. 정상적인 부동 소수점 방법을 사용하여 정확한 수학.

여기서 대부분의 답변은이 질문을 매우 건조하고 기술적 인 용어로 다룹니다. 나는 정상적인 인간이 이해할 수 있다는 용어로 이것을 다루고 싶습니다.

피자를 썰 으려고한다고 상상해보십시오. 피자 조각을자를 수있는 로봇 피자 커터가 있습니다. 바로 그거죠 반으로. 피자 전체를 절반으로 줄일 수 있거나 기존 조각을 반으로 줄일 수 있지만 어쨌든 절반은 항상 정확합니다.

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

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


물론 가장 숙련 된 프로그래머는 실제 대답을 알고 있습니다. 정확한 피자의 10 분의 1 또는 5 분의 1은 그 슬라이스를 사용하여 아무리 미세하게 얇게 썬다. 당신은 꽤 좋은 근사치를 할 수 있으며, 근사치 0.2로 0.1의 근사치를 추가하면 0.3의 꽤 좋은 근사치를 얻지 만 여전히 근사치입니다.

For double-precision numbers (which is the precision that allows you to halve your pizza 53 times), the numbers immediately less and greater than 0.1 are 0.09999999999999999167332731531132594682276248931884765625 and 0.1000000000000000055511151231257827021181583404541015625. 후자는 전자보다 0.1에 약간 가깝기 때문에 0.1의 입력을 감안할 때 숫자 파서는 후자를 선호합니다.

(이 두 숫자의 차이점은 상향 바이어스를 소개하거나 배제를 도입하는 "가장 작은 슬라이스"입니다. ULP.)

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

두 경우 모두 0.1과 0.2의 근사치는 약간의 위쪽 바이어스를 갖습니다. 이러한 편견을 충분히 추가하면 숫자를 우리가 원하는 것보다 더 멀리 떨어 뜨릴 것입니다. 실제로 0.1 + 0.2의 경우 바이어스가 더 이상 가장 가까운 숫자가 될 수있을 정도로 높습니다. 0.3.

In particular, 0.1 + 0.2 is really 0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125, whereas the number closest to 0.3 is actually 0.299999999999999988897769753748434595763683319091796875.


추신 일부 프로그래밍 언어는 또한 할 수있는 피자 커터를 제공합니다. 슬라이스를 정확한 10 분의 1로 분할하십시오. 그러한 피자 커터는 드문 일이지만, 당신이 하나에 접근 할 수 있다면, 당신은 정확히 1/10 또는 1/5의 슬라이스를 얻을 수있는 것이 중요 할 때 그것을 사용해야합니다.

(원래 Quora에 게시 됨)

부동 소수점 반올림 오류. 0.1은 누락 된 프라임 계수 5로 인해 Base-2에서 정확하게 표현할 수 없습니다. 0.1은 Base-10이 아닌 Base-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 비트) 정밀도에만 해당되었지만 인수는 모든 부동 소수점 산술에 동일하게 적용됩니다.

전문

an IEEE 754 이중-프리즈 바이너리 플로팅 포인트 형식 (Binary64) 숫자는 여러 형식을 나타냅니다

값 = (-1)^S * (1.M5150...중210)2 * 2E-1023

64 비트로 :

  • 첫 번째 비트는 사인 비트: 1 숫자가 음수 인 경우 0 그렇지 않으면1.
  • 다음 11 비트는 멱지수, 그것은 오프셋 다시 말해서, 이중 정화 번호에서 지수 비트를 읽은 후, 1023을 빼서 2의 전력을 얻어야합니다.
  • 나머지 52 비트는 중요합니다 (또는 Mantissa). Mantissa에서 '암시' 1. 항상2 이진 값 중 가장 중요한 비트는 생략됩니다. 1.

1 -IEEE 754는 a의 개념을 허용합니다 제로에 서명했습니다 - +0 그리고 -0 다르게 취급됩니다. 1 / (+0) 긍정적 인 무한대입니다. 1 / (-0) 부정적인 무한대입니다. 0 값의 경우 Mantissa와 지수 비트는 모두 0입니다. 참고 : 0 값 (+0 및 -0)은 명시 적으로 분류되지 않습니다.2.

2 - 이것은 사실이 아닙니다 거절 숫자, 오프셋 지수가 0의 오프셋 지수를 가지고 있습니다 (및 묵시적 0.). 거부 이중 정밀 번호의 범위는 d입니다최소 ≤ | x | ≤ d맥스, 여기서 d최소 (가장 작은 대표적이지 않은 숫자)는 2입니다-1023 - 51 (≈ 4.94 * 10-324) 및 d맥스 (Mantissa가 전적으로 구성되는 가장 큰 거절 수 1s)는 2입니다-1023 + 1 - 2-1023 - 51 (≈ 2.225 * 10-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 버전의 바닥으로 건너 뛰기)

카토 존스턴 (질문 Asker)는 0.1 + 0.2! = 0.3을 물었습니다.

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

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

Mantissa는 반복 숫자로 구성되어 있습니다. 0011. 이것은 열쇠 계산에 오류가있는 이유 -0.1, 0.2 및 0.3은 바이너리에 표시 될 수 없습니다. 정확하게 안에 한정된 1/9, 1/3 또는 1/7 이상의 이진 비트 수는 정확하게 표현 될 수 있습니다. 십진수.

또한 지수의 전력을 52로 줄이고 바이너리 표현의 지점을 오른쪽으로 52 개의 장소로 바꿀 수 있습니다.-3 * 1.23 == 10-5 * 123). 그러면 우리는 이진 표현을 a * 2 형식으로 나타내는 정확한 값으로 표현할 수 있습니다.. 여기서 'a'는 정수입니다.

지수를 소수점으로 변환하고, 오프셋 제거 및 묵시적 재건 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} 우리는 지수를 하나씩 증가시키고 소수점을 이동합니다 (이진) 포인트 :

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

Mantissa에는 현재 53 비트가 있습니다 (53 번째는 위의 라인에 정사각형 괄호 안에 있습니다). 기본값 반올림 모드 IEEE 754는 '둥글게 가장 가까운' - 숫자 인 경우 엑스 두 값 사이에서 떨어집니다 그리고 , 가장 유의미한 비트가 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

주목하십시오 그리고 마지막 비트에서만 다릅니다. ...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 x 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 ~이다 1 x 2⁻¹ 그리고 0.25 ~이다 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⁻⁵⁵, 또는 정확한 소수점 값 0.3000000000000000444089209850062616169452667236328125. 부동 소수점 번호는 일반적으로 디스플레이를 위해 둥글게됩니다.

플로팅 포인트 반올림 오류. 에서 모든 컴퓨터 과학자가 부동 소수점 산술에 대해 알아야 할 것:

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

내 해결 방법 :

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

정도 첨가 중 소수점 이후 보존하려는 숫자 수를 나타냅니다.

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

모든 숫자를 통해 표현할 수있는 것은 아닙니다 부유물/더블스예를 들어, "0.2"숫자는 IEEE754 플로트 포인트 표준에서 단일 정밀도에서 "0.200000003"으로 표시됩니다.

후드 아래에 저장 실수 모델에 대한 모델은 플로트 번호를 나타냅니다.

enter image description here

입력 할 수 있지만 0.2 용이하게, FLT_RADIX 그리고 DBL_RADIX IS 2; FPU가있는 컴퓨터의 경우 10이 아닌 "이진 부유 포인트 산술 용 IEEE 표준 (ISO/IEEE STD 754-1985)"을 사용합니다.

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

이 유명한 이중 정밀 질문과 관련된 일부 통계.

모든 값을 추가 할 때 (a + b) 0.1 (0.1 ~ 100)의 단계를 사용하여 정밀 오차의 ~ 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)

모든 값을 빼면 (a -b 어디 a> b) 0.1 (100에서 0.1까지)의 단계를 사용하여 ~ 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을 사용하십시오. 소수 자리 2 자리 (단계 0.01)로 상황은 조금 더 악화됩니다 (18% 및 36%).

아니요, 파손되지는 않지만 대부분의 소수점 분수는 근사해야합니다.

요약

부동 소수점 산술 ~이다 불행히도, 그것은 우리의 일반적인 Base-10 숫자 표현과 잘 어울리지 않으므로, 우리는 종종 우리가 쓴 내용에서 약간 벗어난 입력을 제공하는 것으로 나타났습니다.

0.01, 0.02, 0.03, 0.04 ... 0.24와 같은 간단한 숫자조차도 이진 분획으로 정확하게 표현할 수 없습니다. 0.01, .02, .03을 계산하면 0.25에 도달하기 전까지는 기본에서 첫 번째 분수를 나타냅니다.2. FP를 사용하여 시도했다면 0.01이 약간 꺼 졌을 것이므로 25 개를 agice 정확한 0.25에 추가하는 유일한 방법은 가드 비트와 반올림과 관련된 긴 인과 관계가 필요했을 것입니다. 예측하기가 어렵습니다. "FP는 부적절하다", 그러나 그것은 사실이 아닙니다.

우리는 끊임없이 FP 하드웨어에 기본 10에서 단순 해 보이지만 기본 2에서 반복되는 분획을 제공합니다.

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

우리가 소수점으로 글을 쓸 때, 모든 분수 (특히, 매번 소수점 종료) 형식의 합리적인 수입니다

           A / (2N x 5)

이진에서는 우리는 만 얻습니다 2N 용어, 즉 :

           A / 2N

따라서 소수점에서는 대표 할 수 없습니다 1/3. 베이스 10에는 2가 기본 요소로 포함되기 때문에 이진 분수로 쓸 수있는 모든 숫자 또한 베이스 10 분수로 작성할 수 있습니다. 그러나 우리가 기지로 쓰는 것은 거의 없습니다10 분수는 이진으로 표현 될 수 있습니다. 0.01, 0.02, 0.03 ... 0.99 범위에서 숫자는 1/4, 1/2 및 3/4이기 때문에 FP 형식으로 0.25, 0.50 및 0.75로 표시 될 수 있습니다.N 기간.

기지에서10 우리는 대표 할 수 없습니다 1/3. 그러나 이진에서는 할 수 없습니다 1/10 또는 1/3.

따라서 모든 이진 분획은 소수점으로 작성 될 수 있지만 그 반대는 사실이 아닙니다. 실제로 대부분의 소수점 분획은 이진에서 반복됩니다.

그것을 다루고 있습니다

개발자는 보통 지시를받습니다 <엡실론 비교, 더 나은 조언은 통합 값 (C 라이브러리 : Round () 및 Roundf (), 즉 FP 형식으로 유지)를 반올림 한 다음 비교하는 것입니다. 특정 소수점 분획 길이로 반올림하면 대부분의 출력 문제가 해결됩니다.

또한, 실수 숫자가 크 런칭 문제 (FP가 초기에 무섭고 비싼 컴퓨터에서 발명 된 문제)에서 우주의 물리적 상수와 다른 모든 측정은 상대적으로 적은 수의 중요한 수치로만 알려져 있으므로 전체 문제 공간이 있습니다. 어쨌든 "부정"이었다. FP "정확도"는 이러한 종류의 응용 프로그램에서 문제가되지 않습니다.

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

나는 피자 대답을 좋아합니다 크리스, 그것은 "부정확성"에 대한 일반적인 수작업뿐만 아니라 실제 문제를 설명하기 때문에. FP가 단순히 "부정확 한"경우 고치다 그것은 수십 년 전에 그것을했을 것입니다. 우리가하지 않은 이유는 FP 형식이 작고 빠르기 때문에 많은 숫자를 바꾸는 가장 좋은 방법이기 때문입니다. 또한 우주 시대 및 무기 경주의 유산이며 작은 메모리 시스템을 사용하여 매우 느린 컴퓨터로 큰 문제를 해결하려는 초기 시도입니다. (때로는 개인 자기 코어 1 비트 스토리지의 경우입니다 다른 이야기.)

결론

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

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

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

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

나는 C#의 과학 시뮬레이션 프로젝트에서도 같은 문제를 겪었고, 나비 효과를 무시하면 큰 뚱뚱한 용으로 변하고 A **에 물게 될 것이라고 말할 수 있습니다.

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

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

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

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시 이후에 원치 않는 0과 무엇을 하시겠습니까? 당신이 원하는대로 그것을 만들기 위해 그것을 float로 변환 할 때입니다.

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

처럼 W3Schools 다른 솔루션도 있음을 제안합니다. 위의 문제를 해결하기 위해 곱하고 나눌 수 있습니다.

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

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

아무도 이것을 언급하지 않았다는 것을 감안할 때 ...

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

  • 파이썬 decimal 기준 치수 그리고 Java 's 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 기준 치수 그리고 Apache Common 's BigFraction 수업. 둘 다 합리적 숫자를 나타냅니다 (numerator, denominator) 쌍과 그들은 소수점 부동 소수점 산술보다 더 정확한 결과를 줄 수 있습니다.

이러한 솔루션 중 어느 것도 완벽하지는 않지만 (특히 공연을 보거나 매우 정밀도가 필요한 경우), 여전히 이진 부동 소수점 산술로 많은 문제를 해결합니다.

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

이것은 언어 공유 질문이므로 언어에 대한 도구가 필요합니다. 부동 소수점에서 부동 소수점 변환기.

두 배로 취급되는 질문의 숫자에 적용하십시오.

0.1은 0.10000000000000000555111512312578270211815834045415625로 변환합니다.

0.2 0.2000000000000000011112330246251565404233316680908203125로 변환합니다.

0.3 converts to 0.299999999999999988897769753748434595763683319091796875, and

0.30000000000000004 변환 0.30000000000000444444444444444006261694526723328125로 변환합니다.

처음 두 숫자를 수동으로 또는 다음과 같은 소수점 계산기에 추가 완전 정밀 계산기, 실제 입력의 정확한 합은 0.3000000000000000166534369373481063544750213623046875입니다.

0.3으로 반올림 된 경우 반올림 오차는 0.00000000000000002755575615628913590791702705078125입니다. 0.3000000000000000004에 해당하는 반올림은 반올림 오류 0.0000000000000027755575615628913510590791702705078125를 제공합니다. 둥근 타이 브레이커가 적용됩니다.

플로팅 포인트 컨버터로 돌아가서 0.30000000000000004의 RAW 16 진수는 3FD333333333334로 짝수로 끝나므로 올바른 결과입니다.

그냥 추가해도 될까요?사람들은 항상 이것이 컴퓨터 문제라고 생각하지만, 손으로 세면(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로 반올림할 수 있으며 이는 기술적으로도 부정확합니다.

삼항으로 세고 삼분의 일은 문제가 되지 않습니다. 어쩌면 각 손에 15개의 손가락이 있는 일부 종족은 왜 십진수 계산이 깨졌는지 물을 것입니다...

디지털 컴퓨터에서 구현할 수있는 부동 소수점 수학은 반드시 실수와 작업의 근사치를 사용합니다. (그만큼 기준 버전은 50 페이지가 넘는 문서로 실행되며 오류 및 추가 개선을 다루는위원회가 있습니다.)

이 근사치는 서로 다른 종류의 근사치의 혼합이며, 각각의 정확성과의 특정 편차 방식으로 인해 무시되거나 신중하게 설명 될 수 있습니다. 또한 대부분의 사람들이 눈치 채지 못하는 척하면서 과거를 걷는 하드웨어 및 소프트웨어 수준 모두에서 여러 가지 예외적 인 사례가 포함됩니다.

무한한 정밀도가 필요한 경우 (예를 들어, 더 짧은 스탠드 중 하나 대신 숫자 π를 사용하여) 상징적 수학 프로그램을 작성하거나 사용해야합니다.

그러나 때로는 부동 소수점 수학이 가치가 흐릿하고 논리와 오류가 빠르게 축적 될 수 있다는 생각에 괜찮 으면 요구 사항과 테스트를 작성하여이를 허용 할 수 있습니다. 당신의 FPU.

재미를 위해, 나는 표준 C99의 정의에 따라 플로트 표현을 가지고 놀았으며 아래 코드를 썼습니다.

코드는 3 개의 분리 된 그룹에서 플로트의 이진 표현을 인쇄합니다.

SIGN EXPONENT FRACTION

그리고 그 후에는 합계를 인쇄합니다. 충분한 정밀도로 합산하면 하드웨어에 실제로 존재하는 값이 표시됩니다.

그래서 당신이 쓸 때 float x = 999..., 컴파일러는 기능에 의해 인쇄 된 비트 표현으로 해당 숫자를 변환합니다. xx 함수에 의해 합계가 인쇄되도록 yy 주어진 숫자와 동일합니다.

실제로이 합은 근사치 일뿐입니다. 숫자 999,999,999의 경우 컴파일러는 플로트의 비트 표현에 1,000,000,000을 삽입합니다.

코드 후에는 콘솔 세션을 첨부하여 컴파일러에 의해 삽입 된 하드웨어에 실제로 존재하는 상수 (Minus 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);
}

다음은 하드웨어에 존재하는 플로트의 실제 값을 계산하는 콘솔 세션입니다. 나는 사용했다 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

그게 다야. 99999999의 값은 실제로입니다

999999999.999999446351872

당신은 또한 확인할 수 있습니다 bc 그 -3.14도 교란되었습니다. a를 설정하는 것을 잊지 마십시오 scale 요소를 고려하십시오 bc.

표시된 합은 하드웨어 내부입니다. 계산하여 얻은 값은 설정 한 척도에 따라 다릅니다. 나는 그것을 설정했다 scale 수학적으로, 무한한 정밀도로 1,000,000,000 인 것 같습니다.

이것을 보는 또 다른 방법 : 사용 된 것은 숫자를 나타내는 64 비트입니다. 결과적으로 2 ** 64 = 18,446,744,073,709,551,616 다른 숫자가 정확하게 표현 될 수 있습니다.

그러나 Math는 이미 0과 1 사이에 이미 무한히 많은 소수점이 있다고 말합니다. IEE 754는 훨씬 더 큰 수준의 공간과 NAN 및 +/- 인피니티에 효율적 으로이 64 비트를 효율적으로 사용하는 인코딩을 정의하므로 정확하게 표시된 숫자 사이에 간격이 가득 차 있습니다. 숫자는 근사입니다.

불행히도 0.3은 간격에 앉아 있습니다.

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

보세요 https://posithub.org/ 예를 들어, Posit (및 전임자 Unum)라는 숫자 유형을 보여주는 비트가 적은 정확도를 제공 할 것을 약속합니다. 내 이해가 정확하다면 질문의 문제도 해결됩니다. 꽤 흥미로운 프로젝트, 그 뒤에있는 사람은 수학자입니다. 존 구스타프 슨 박사. 모든 것은 오픈 소스이며, C/C ++, Python, Julia 및 C#에서 많은 실제 구현이 있습니다.https://hastlayer.com/arithmetics).

예를 들어 8 자리의 정확도로베이스 10에서 일한다고 상상해보십시오. 당신은 있는지 확인합니다

1/3 + 2 / 3 == 1

그리고 이것이 돌아온다는 것을 배우십시오 false. 왜요? 글쎄, 우리에게는 실수로

1/3 = 0.333.... 그리고 2/3 = 0.666....

10 진수 자리에서 잘린다. 우리는 얻는다

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, 지수와 만티사에 몇 비트를 할당 한 비트에 따라. 이는 계산을 위해 얼마나 많은 정밀도를 얻는지에 영향을 미칩니다.

상향은 이러한 반올림 오류로 인해 플로팅 포인트 숫자에서 ==를 사용하고 싶지 않다는 것입니다. 대신, 차이의 절대 값이 일부 고정 된 소수보다 작은지 확인할 수 있습니다.

파이썬 3.5 이후 당신이 사용할 수있는 math.isclose() 대략적인 평등을 테스트하기위한 기능 :

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

수학.합 (자바스크립트) ....일종의 연산자 교체

.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;
        }
    },
    diff:{
        value: function(A,B){
            var prec = this.max(this.get_precision(A),this.get_precision(B));
            return +this.precision(A-B,prec);
        }
    },
    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를 사용하는 것입니다.

Math.diff(0.2, 0.11) == 0.09 // true
0.2 - 0.11 == 0.09 // false

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

Math.sum은 임의 개수의 인수를 허용합니다.

실제로 매우 간단합니다. 기본 10 시스템 (예 : 우리와 같은)이 있으면베이스의 주요 요인을 사용하는 분수 만 표현할 수 있습니다. 10의 주요 요인은 2와 5입니다. 따라서 1/2, 1/4, 1/5, 1/8 및 1/10은 분모가 모두 10의 주요 요인을 사용하기 때문에 모두 깨끗하게 표현할 수 있습니다. 대조적으로, 1 /3, 1/6 및 1/7은 분모가 3 또는 7의 주요 계수를 사용하기 때문에 모두 반복되는 소수점입니다. 이진 (또는베이스 2)에서 유일한 주요 요인은 2입니다. 따라서 분수를 깨끗하게 표현할 수 있습니다. 2를 주요 요인으로 만 포함합니다. 바이너리에서, 1/2, 1/4, 1/8은 모두 소수성으로 깨끗하게 표현 될 것이다. 반면 1/5 또는 1/10은 소수성을 반복합니다. 따라서 0.1 및 0.2 (1/10 및 1/5)베이스 10 시스템의 깨끗한 소수점은베이스 2 시스템에서 컴퓨터가 작동 중입니다.이 반복되는 소수점에서 수학을 할 때 남은 음식으로 끝납니다. 컴퓨터의 기본 2 (바이너리) 번호를 더 인간 읽기 쉬운베이스 10 숫자로 변환 할 때 수행합니다.

에서 https://0.30000000000000004.com/

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

C ++에서는 왜 결과입니까? cout << x 디버거가 표시하는 값과 다릅니다. x?

그만큼 x 질문에는 a float 변하기 쉬운.

한 가지 예입니다

float x = 9.9F;

디버거가 표시됩니다 9.89999962, 출력 cout 운영입니다 9.9.

대답은 그 것으로 밝혀졌습니다 cout의 기본 정밀도 float 6이므로 6 자리 숫자로 둥글게됩니다.

보다 여기 참조

이것은 실제로 답변으로 의도되었습니다 이 질문 - 복제본으로 닫혔습니다 이것 의문, 동안 나는이 답을 모으고 있었기 때문에 이제 거기에 게시 할 수 없어 ... 대신 여기에 게시하겠습니다!


질문 요약 :

워크 시트에서 10^-8/1000 그리고 10^-11 AS를 평가하십시오 동일한 VBA에있는 동안 그들은 그렇지 않습니다.

워크 시트에서 숫자는 과학적 표기법으로 채무 불이행됩니다.

셀을 숫자 형식으로 변경하면 (Ctrl 키+1) 의 Number ~와 함께 15 소수점, 당신은 얻을 수 있습니다 :

=10^-11 returns 0.000000000010000
=10^(-8/1000) returns 0.981747943019984

따라서, 그것들은 분명히 동일하지 않습니다 ... 하나는 단지 0이고 다른 하나는 약 1입니다.

Excel은 처리하도록 설계되지 않았습니다 극도로 적은 숫자 - 적어도 재고 설치가 아닙니다. 숫자 정밀도를 향상시키는 데 도움이되는 추가 기능이 있습니다.


Excel은 바이너리 플로팅 포인트 산술에 대한 IEEE 표준에 따라 설계되었습니다.IEEE 754). 표준은 방법을 정의합니다 부동 소수점 번호 저장 및 계산됩니다. 그만큼 IEEE 754 표준은 플로팅 포인트 숫자가 합리적인 양의 공간에 저장 될 수있게되므로 널리 사용되며 계산은 비교적 빠르게 발생할 수 있습니다.

고정 점 표현에 부유하는 장점은 더 넓은 범위의 값을 지원할 수 있다는 것입니다. 예를 들어, 세 번째 자리에 배치 된 소수점이있는 5 자리 숫자가있는 고정점 표현은 숫자를 나타낼 수 있습니다. 123.34, 12.23, 2.45, 5 자리 정밀도의 부동 소수점 표현은 1.2345, 12345, 0.00012345 등을 나타낼 수 있지만, 플로팅 포인트 표현은 또한 정밀도를 유지하면서 광범위한 크기에 걸쳐 계산할 수 있습니다. 예를 들어,

img


기타 참조 :

다음과 같은 소수점 분획 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

이러한 계산이보다 안정적으로 평가 되려면 부동 소수점 값에 대한 소수점 기반 표현을 사용해야합니다. C 표준은 기본적으로 이러한 유형을 지정하지 않고 기술 보고서 . 유형 _Decimal32, _Decimal64 그리고 _Decimal128 시스템에서 사용할 수 있습니다 (예 : gcc 그들을 지원합니다 선택된 대상, 하지만 clang OS/X에서 그들을 지원하지 않습니다).

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