많은 수학적 계산을 다루는 응용 프로그램을 만들 때 특정 숫자가 반올림 오류를 일으키는 문제가 발생했습니다.
내가 이해하는 동안 부동 소수점 것은 정확한 아니다 , 문제는 어떻게 계산이되지 않은 문제를 유발하는 포인트 라운딩을 부동 그들에 미리 형성 할 때 내가 있는지 확인하기 위해 정확한 수를 처리합니까?
distanceTraveled(startVel, duration, acceleration)
입니다.
많은 수학적 계산을 다루는 응용 프로그램을 만들 때 특정 숫자가 반올림 오류를 일으키는 문제가 발생했습니다.
내가 이해하는 동안 부동 소수점 것은 정확한 아니다 , 문제는 어떻게 계산이되지 않은 문제를 유발하는 포인트 라운딩을 부동 그들에 미리 형성 할 때 내가 있는지 확인하기 위해 정확한 수를 처리합니까?
distanceTraveled(startVel, duration, acceleration)
입니다.
답변:
부동 소수점 반올림이없는 대체 숫자 유형을 작성하는 데는 세 가지 기본 접근법이 있습니다. 이것들의 일반적인 주제는 다양한 방법으로 정수 수학을 사용한다는 것입니다.
근거
분자와 분모를 사용하여 숫자를 전체 부분과 유리수로 나타냅니다. 숫자 15.589
는로 표시됩니다 w: 15; n: 589; d:1000
.
0.25 (즉 w: 0; n: 1; d: 4
,)에 더하면 LCM을 계산 한 다음 두 숫자를 더해야합니다. 이 방법은 여러 상황에서 잘 작동하지만 상대적으로 소수 인 여러 합리적인 숫자로 작업 할 때 매우 큰 숫자가 발생할 수 있습니다.
고정 점
전체 부분과 소수 부분이 있습니다. 모든 숫자는 그 정밀도로 반올림됩니다 (단어가 있지만 어디에 있는지 알고 있습니다). 예를 들어 소수점이 3 개인 고정 점이있을 수 있습니다. 15.589
+ 0.250
는 589 + 250 % 1000
소수 부분에 추가됩니다 (그리고 모든 부분에 캐리). 이것은 기존 데이터베이스와 매우 잘 작동합니다. 언급했듯이 반올림이 있지만 위치를 알고 필요한 것보다 더 정확하도록 지정할 수 있습니다 (소수점 3 자만 측정하므로 4로 고정하십시오).
부동 고정 소수점
값과 정밀도를 저장하십시오. 15.589
저장되는 15589
값과 3
하면서 정밀도 0.25
로 저장 25
하고 2
. 이것은 임의의 정밀도를 처리 할 수 있습니다. 나는 이것이 Java BigDecimal의 내부가 (최근에 보지 않은) 내부가 사용하는 것이라고 생각 합니다. 언젠가는이 형식에서 다시 가져 와서 표시하려고합니다. 반올림이 필요할 수도 있습니다 (다시, 위치를 제어).
표현 선택을 결정하면이를 사용하는 기존 타사 라이브러리를 찾거나 직접 작성할 수 있습니다. 직접 작성할 때 단위 테스트를 수행하고 수학을 올바르게 수행하고 있는지 확인하십시오.
부동 소수점 값에 반올림 문제가 있고 반올림 문제를 겪고 싶지 않은 경우 논리적으로 유일한 조치는 부동 소수점 값을 사용하지 않는 것입니다.
이제 문제는 "부동 소수점 변수없이 정수가 아닌 값을 포함하는 수학을 어떻게 수행합니까?" 대답은 임의 정밀도 데이터 유형 입니다. 하드웨어 대신 소프트웨어로 구현해야하기 때문에 계산 속도가 느리지 만 정확합니다. 사용중인 언어를 말하지 않았으므로 패키지를 추천 할 수는 없지만 가장 널리 사용되는 프로그래밍 언어에 사용할 수있는 임의의 정밀 라이브러리가 있습니다.
lot of mathematical calculations
는 도움이되지도 않습니다. 대부분의 경우 (통화를 다루지 않는 경우) float는 충분합니다.
부동 소수점 산술은 일반적으로 매우 정확하고 (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 진수로 정확합니다.
이것은 단지 예일 뿐이며 다른 이유로 인해 정밀도 손실을 피할 수있는 다른 방법이 있습니다. 그러나 이미 관련된 값의 크기에 대해 생각하고 펜과 종이로 수학을 수행하고 매 단계마다 고정 된 자릿수로 반올림하면 어떻게 될지 상상하는 데 많은 도움이됩니다.
문제가 없는지 확인하는 방법 : 부동 소수점 산술 문제에 대해 배우거나 다른 사람을 고용하거나 상식을 사용하십시오.
첫 번째 문제는 정확성입니다. 많은 언어에서 "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 번째 변화와 같은)를 만든다면 당신은 괜찮을 것입니다. 실제로 결과가 바뀌면 문제가있는 것입니다. 도움을 요청.
대부분의 사람들은 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 반올림 모드에 따라) 기본 유형 크기로 반올림합니다.