부동 소수점 반올림 오류에 대한 솔루션


18

많은 수학적 계산을 다루는 응용 프로그램을 만들 때 특정 숫자가 반올림 오류를 일으키는 문제가 발생했습니다.

내가 이해하는 동안 부동 소수점 것은 정확한 아니다 , 문제는 어떻게 계산이되지 않은 문제를 유발하는 포인트 라운딩을 부동 그들에 미리 형성 할 때 내가 있는지 확인하기 위해 정확한 수를 처리합니까?


2
특정 문제가 있습니까? 몇 가지 문제에 대해 테스트를 수행하는 방법은 여러 가지가 있습니다. 답변이 여러 개인 질문은 Q & A 형식에 적합하지 않습니다. 아이디어와 추천을 위해 인터넷을 사용하는 대신 하나의 정답을 얻을 수있는 방식으로 문제를 정의하는 것이 가장 좋습니다.

많은 수학적 계산이 가능한 소프트웨어 응용 프로그램을 만들고 있습니다. NUNIT 또는 JUNIT 테스트는 좋지만 수학 계산 문제에 접근하는 방법에 대한 아이디어를 갖고 싶습니다.
JNL

1
테스트 할 계산의 예를들 수 있습니까? 하나는 일반적으로 자신의 숫자 유형을 테스트하지 않는 한 원시 수학을 단위 테스트하는 것이 아니라 테스트를 테스트하는 것 distanceTraveled(startVel, duration, acceleration)입니다.

한 가지 예는 소수점을 다루는 것입니다. 예를 들어, dist x-0에서 x = 14.589에 대한 특수 설정으로 벽을 만들고 x = 14.589에서 x = wall의 끝으로 배열을 가정 해 보겠습니다. 이진수로 변환 할 때의 거리 .589는 같지 않습니다 ... 특히 14.589 + 0.25와 같은 거리를 추가하면 이진수의 14.84와 같지 않습니다 ... 혼란하지 않기를 바랍니다.
JNL

1
@MichaelT 질문을 편집 해 주셔서 감사합니다. 많은 도움이되었습니다. 이것에 익숙하지 않기 때문에 질문을 짜는 방법에 너무 좋지 않습니다. :) ...하지만 곧 나아질 것입니다.
JNL

답변:


22

부동 소수점 반올림이없는 대체 숫자 유형을 작성하는 데는 세 가지 기본 접근법이 있습니다. 이것들의 일반적인 주제는 다양한 방법으로 정수 수학을 사용한다는 것입니다.

근거

분자와 분모를 사용하여 숫자를 전체 부분과 유리수로 나타냅니다. 숫자 15.589는로 표시됩니다 w: 15; n: 589; d:1000.

0.25 (즉 w: 0; n: 1; d: 4,)에 더하면 LCM을 계산 한 다음 두 숫자를 더해야합니다. 이 방법은 여러 상황에서 잘 작동하지만 상대적으로 소수 인 여러 합리적인 숫자로 작업 할 때 매우 큰 숫자가 발생할 수 있습니다.

고정 점

전체 부분과 소수 부분이 있습니다. 모든 숫자는 그 정밀도로 반올림됩니다 (단어가 있지만 어디에 있는지 알고 있습니다). 예를 들어 소수점이 3 개인 고정 점이있을 수 있습니다. 15.589+ 0.250589 + 250 % 1000소수 부분에 추가됩니다 (그리고 모든 부분에 캐리). 이것은 기존 데이터베이스와 매우 잘 작동합니다. 언급했듯이 반올림이 있지만 위치를 알고 필요한 것보다 더 정확하도록 지정할 수 있습니다 (소수점 3 자만 측정하므로 4로 고정하십시오).

부동 고정 소수점

값과 정밀도를 저장하십시오. 15.589저장되는 15589값과 3하면서 정밀도 0.25로 저장 25하고 2. 이것은 임의의 정밀도를 처리 할 수 ​​있습니다. 나는 이것이 Java BigDecimal의 내부가 (최근에 보지 않은) 내부가 사용하는 것이라고 생각 합니다. 언젠가는이 형식에서 다시 가져 와서 표시하려고합니다. 반올림이 필요할 수도 있습니다 (다시, 위치를 제어).


표현 선택을 결정하면이를 사용하는 기존 타사 라이브러리를 찾거나 직접 작성할 수 있습니다. 직접 작성할 때 단위 테스트를 수행하고 수학을 올바르게 수행하고 있는지 확인하십시오.


2
그것은 좋은 시작이지만 물론 반올림 문제를 완전히 해결하지는 못합니다. π, e 및 √2와 같은 비이성적 인 숫자 는 엄격하게 숫자로 표현되지 않습니다. 정확한 표현을 원한다면 기호로 표현하거나 반올림 오차를 최소화하려면 가능한 한 늦게 평가해야합니다.
Caleb

비합리적인 경우 @Caleb는 반올림으로 인해 문제가 발생할 수있는 범위를 넘어서 평가해야합니다. 예를 들어 22/7은 pi의 0.1 %에 정확하고 355/113은 10 ^ -8에 정확합니다. 소수점 이하 세 자리까지만 숫자로 작업하는 경우 3.141592653을 사용하면 소수점 이하 세 자리에서 반올림 오류를 피해야합니다.

@MichaelT : 합리적인 숫자를 추가하기 위해 LCM을 찾을 필요가 없으며 빠르지 않고 더 빠릅니다 (그리고 "LSB 0"을 취소하는 것이 더 빠르며 절대적으로 필요할 때만 완전히 단순화합니다). 유리수의 경우 일반적으로 "분자 / 분모"만 또는 "분자 / 분모 << 지수"( "전체 부분 + 분자 / 분모"아님)입니다. 또한 "부동 고정 소수점"은 부동 소수점 표현이며 "임의 크기 부동 소수점"과 구별하기 위해 "임의 크기 부동 소수점"으로 더 잘 설명됩니다.
Brendan

일부 용어는 약간 iffy입니다-부동 소수점은 말이되지 않습니다-부동 소수점을 말하려고한다고 생각합니다.
jk.

10

부동 소수점 값에 반올림 문제가 있고 반올림 문제를 겪고 싶지 않은 경우 논리적으로 유일한 조치는 부동 소수점 값을 사용하지 않는 것입니다.

이제 문제는 "부동 소수점 변수없이 정수가 아닌 값을 포함하는 수학을 어떻게 수행합니까?" 대답은 임의 정밀도 데이터 유형 입니다. 하드웨어 대신 소프트웨어로 구현해야하기 때문에 계산 속도가 느리지 만 정확합니다. 사용중인 언어를 말하지 않았으므로 패키지를 추천 할 수는 없지만 가장 널리 사용되는 프로그래밍 언어에 사용할 수있는 임의의 정밀 라이브러리가 있습니다.


지금 VC ++을 사용하고 있습니다. 그러나 다른 프로그래밍 언어에 관한 더 많은 정보도 감사하겠습니다.
JNL

부동 소수점 값이 없어도 여전히 둥근 문제가 발생합니다.
차드

2
@ 차드 사실이지만 목표는 반올림 문제를 제거하지 않는 것입니다 (항상 존재합니다. 사용하는베이스에는 정확한 표현이 없으며 숫자와 무한한 메모리 및 처리 능력이 없기 때문에 항상 존재합니다). 수행하려는 계산에 영향을 미치지 않는 지점으로 줄입니다.
Iker

@Iker 당신이 맞아요. 당신이나 그 질문을하는 사람은 그들이 정확히 어떤 계산을하려고하는지, 그리고 그들이 원하는 정밀도를 지정했습니다. 총을 숫자 이론으로 뛰어 넘기 전에 먼저 그 질문에 대답해야합니다. 말하는 것만으로 lot of mathematical calculations는 도움이되지도 않습니다. 대부분의 경우 (통화를 다루지 않는 경우) float는 충분합니다.
차드

@Chat 그것은 공정한 요점입니다 .OP의 정확한 데이터가 정확히 필요한 정확한 수준인지 알 수있는 데이터는 충분하지 않습니다.
Iker

7

부동 소수점 산술은 일반적으로 매우 정확하고 (a의 경우 십진수 15 자리 double) 매우 유연합니다. 수학을 수행 할 때 정밀도 자릿수가 크게 감소하는 문제가 발생합니다. 여기 몇 가지 예가 있어요.

  • 빼기시 취소 : 1234567890.12345 - 1234567890.12300, 결과 0.0045에는 소수점 이하 두 자릿수 만 있습니다. 이것은 비슷한 크기의 두 숫자를 뺄 때마다 발생합니다.

  • 정밀도 삼키기 :로 1234567890.12345 + 0.123456789012345평가 1234567890.24691하면 두 번째 피연산자의 마지막 10 자리가 손실됩니다.

  • 곱하기 : 15 자리 숫자 두 개를 곱하면 결과에 30 자리 숫자를 저장해야합니다. 그러나 저장할 수 없으므로 마지막 15 비트가 손실됩니다. 이것은 sqrt()(와 같이 sqrt(x*x + y*y): 결과는 7.5 자리의 정밀도 만 가질 것입니다.

이것들은 당신이 알아야 할 주요 함정입니다. 그리고 일단 당신이 그것들을 알고 있다면, 그것들을 피하는 방식으로 수학을 공식화하려고 시도 할 수 있습니다. 예를 들어, 루프에서 반복해서 값을 증분해야하는 경우 다음을 수행하지 마십시오.

for(double f = f0; f < f1; f += df) {

몇 번의 반복 후에는 더 큰 f정밀도의 일부를 삼킬 것입니다 df. 더 나쁜 것은, 오류가 더 해져서 작은 것이 df전반적인 결과를 악화시킬 수 있는 금기 상황으로 이어질 것입니다. 더 잘 작성하십시오 :

for(int i = 0; i < (f1 - f0)/df; i++) {
    double f = f0 + i*df;

증분을 한 번의 곱셈으로 결합하기 때문에 결과 f는 15 진수로 정확합니다.

이것은 단지 예일 뿐이며 다른 이유로 인해 정밀도 손실을 피할 수있는 다른 방법이 있습니다. 그러나 이미 관련된 값의 크기에 대해 생각하고 펜과 종이로 수학을 수행하고 매 단계마다 고정 된 자릿수로 반올림하면 어떻게 될지 상상하는 데 많은 도움이됩니다.


2

문제가 없는지 확인하는 방법 : 부동 소수점 산술 문제에 대해 배우거나 다른 사람을 고용하거나 상식을 사용하십시오.

첫 번째 문제는 정확성입니다. 많은 언어에서 "float"와 "double"(이중 "이중 정밀도"를 의미)이 있으며, 대부분의 경우 "float"는 약 7 자리의 정밀도를 제공하지만 double은 15를 제공합니다. 정밀도가 문제가 될 수있는 상황에서는 15 자리가 7 자리보다 훨씬 낫습니다. 약간 문제가 많은 상황에서 "이중"을 사용한다는 것은 이탈을 의미하고 "부동"은 그렇지 않은 것을 의미합니다. 회사의 시가 총액이 700 억 달러라고 가정 해 봅시다. 이것을 float로 나타내고 가장 낮은 비트는 $ 65536입니다. double을 사용하여 나타내고 가장 낮은 비트는 약 0.012 센트입니다. 그래서 당신이 정말로하고있는 것을 정말로 알지 못한다면, float가 아닌 double을 사용하십시오.

두 번째 문제는 원칙의 문제입니다. 동일한 결과를 제공하는 두 가지 다른 계산을 수행하면 반올림 오류로 인해 발생하지 않는 경우가 많습니다. 동일해야하는 두 결과는 "거의 동일"입니다. 두 결과가 서로 비슷하면 실제 값이 같을 수 있습니다. 아니면 아닐 수도 있습니다. 이를 명심하고 "x는 확실히 y보다 크다"또는 "x는 확실히 y보다 작다"또는 "x와 y는 같을 것"이라는 함수를 작성하고 사용해야합니다.

반올림 (예 : "round x down to 가장 가까운 정수)"을 사용하면이 문제가 훨씬 악화됩니다. 120 * 0.05를 곱하면 결과는 6이어야하지만 얻는 것은 "6에 가까운 숫자"입니다. 그런 다음 "가장 가까운 정수로 반올림"하면 "6에 매우 가까운 숫자"는 "6보다 약간 작음"으로 반올림되어 5로 반올림 될 수 있습니다. 정밀도는 아무리 중요하지 않습니다. 6보다 작 으면 결과가 6에 얼마나 가까운 지는 중요하지 않습니다 .

셋째, 일부 문제는 어렵다 . 그것은 빠르고 쉬운 규칙이 없다는 것을 의미합니다. 컴파일러에서 "long double"을 더 정밀하게 지원하는 경우 "long double"을 사용하여 차이가 있는지 확인할 수 있습니다. 차이가 없다면 괜찮거나 까다로운 문제가있는 것입니다. 그것이 당신이 기대할만한 종류의 차이 (십진수 12 번째 변화와 같은)를 만든다면 당신은 괜찮을 것입니다. 실제로 결과가 바뀌면 문제가있는 것입니다. 도움을 요청.


1
부동 소수점 수학에 대한 "상식"은 없습니다.
whatsisname

그것에 대해 자세히 알아보십시오.
gnasher729

0

대부분의 사람들은 BigDecimal을 두 번 비명을 지르는 실수를 저지르고 실제로 다른 곳으로 문제를 옮겼습니다. Double은 부호 비트 : 1 비트, 지수 너비 : 11 비트를 제공합니다. 유의미한 정밀도 : 53 비트 (52 개 명시 적으로 저장) double의 특성으로 인해 전체 interger가 클수록 상대 정확도를 잃습니다. 여기서 사용하는 상대 정확도를 계산하는 방법은 다음과 같습니다.

계산에서 두 배의 상대 정확도는 다음과 같은 Foluma 2 ^ E <= abs (X) <2 ^ (E + 1)를 사용합니다.

엡실론 = 2 ^ (E-10) % 16 비트 부동 소수점 (반정도)

 Accuracy Power | Accuracy -/+| Maximum Power | Max Interger Value
 2^-1           | 0.5         | 2^51          | 2.2518E+15
 2^-5           | 0.03125     | 2^47          | 1.40737E+14
 2^-10          | 0.000976563 | 2^42          | 4.39805E+12
 2^-15          | 3.05176E-05 | 2^37          | 1.37439E+11
 2^-20          | 9.53674E-07 | 2^32          | 4294967296
 2^-25          | 2.98023E-08 | 2^27          | 134217728
 2^-30          | 9.31323E-10 | 2^22          | 4194304
 2^-35          | 2.91038E-11 | 2^17          | 131072
 2^-40          | 9.09495E-13 | 2^12          | 4096
 2^-45          | 2.84217E-14 | 2^7           | 128
 2^-50          | 8.88178E-16 | 2^2           | 4

다시 말해, 정확도가 +/- 0.5 (또는 2 ^ -1) 인 경우 숫자의 최대 크기는 2 ^ 52입니다. 이보다 큰 값과 부동 소수점 수 사이의 거리는 0.5보다 큽니다.

+/- 0.0005 (약 2 ^ -11)의 정확도를 원하는 경우 숫자의 최대 크기는 2 ^ 42입니다. 이보다 큰 값과 부동 소수점 수 사이의 거리는 0.0005보다 큽니다.

나는 이것보다 더 나은 대답을 할 수 없다. 사용자는 필요한 계산 및 단위 값 (미터, 피트, 인치, mm, cm)을 수행 할 때 원하는 정밀도를 알아야합니다. 대부분의 경우 float은 시뮬레이션하려는 세계의 규모에 따라 간단한 시뮬레이션으로 충분합니다.

그것은 말할 것이지만, 100 미터 x 100 미터 세계 만 시뮬레이션하려는 경우 2 ^ -45 근처의 정확도 순서를 갖습니다. 이것은 CPU 내부의 최신 FPU가 기본 유형 크기를 벗어난 계산을 수행하는 방법에 영향을 미치지 않으며 계산이 완료된 후에 만 ​​(FPU 반올림 모드에 따라) 기본 유형 크기로 반올림합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.