부동 소수점 숫자가 왜 정확하지 않습니까?


198

부동 소수점 숫자로 저장할 때 일부 숫자의 정확도가 떨어지는 이유는 무엇입니까?

예를 들어, 십진수 9.2는 두 개의 십진 정수 ( 92/10) 의 비율로 정확하게 표현 될 수 있으며 , 둘 모두 이진 ( 0b1011100/0b1010)으로 정확하게 표현 될 수 있습니다 . 그러나 부동 소수점 숫자로 저장된 동일한 비율은 정확히 다음과 같습니다 9.2.

32-bit "single precision" float: 9.19999980926513671875
64-bit "double precision" float: 9.199999999999999289457264239899814128875732421875

64 비트 의 메모리 에서 표현하기에 그렇게 단순한 숫자가 어떻게 "너무 클"수 있습니까?



2
참조는 다음의 제품에 깨진 수학 떠
크리스 Roofe

답변:


242

대부분의 프로그래밍 언어에서 부동 소수점 숫자는 과학적 표기법 과 비슷하게 표현됩니다 . 지수와 가수 (의미라고도 함). 매우 간단한 숫자 9.2는 실제로이 분수입니다.

5,179,139,571,476,070 * 2 -49

지수가 -49있고 가수가있는 곳 5179139571476070. 그것을 표현하는 것은 불가능 이유는 일부 진수이 방법은 지수와 가수 모두 정수이어야한다는 것입니다. 다시 말해, 모든 float는 정수에 2정수를 곱한 정수 여야합니다 .

9.2n 은 단순히 92/10이지만 n 이 정수 값으로 제한 되면 102 n 으로 표현할 수 없습니다 .


데이터보기

먼저 32 비트 및 64 비트를 만드는 구성 요소를 확인 하는 몇 가지 기능이 있습니다float . 출력에만 관심이 있다면 (파이썬의 예) 다음과 같이 광택을 내십시오.

def float_to_bin_parts(number, bits=64):
    if bits == 32:          # single precision
        int_pack      = 'I'
        float_pack    = 'f'
        exponent_bits = 8
        mantissa_bits = 23
        exponent_bias = 127
    elif bits == 64:        # double precision. all python floats are this
        int_pack      = 'Q'
        float_pack    = 'd'
        exponent_bits = 11
        mantissa_bits = 52
        exponent_bias = 1023
    else:
        raise ValueError, 'bits argument must be 32 or 64'
    bin_iter = iter(bin(struct.unpack(int_pack, struct.pack(float_pack, number))[0])[2:].rjust(bits, '0'))
    return [''.join(islice(bin_iter, x)) for x in (1, exponent_bits, mantissa_bits)]

이 기능에는 많은 복잡성이 있으며 설명하는 것이 매우 중요하지만 관심이 있으시면 우리 목적의 중요한 자원은 struct 모듈입니다.

파이썬 float은 64 비트 배정도 숫자입니다. C, C ++, Java 및 C #과 같은 다른 언어에서는 배정 밀도가 별도의 유형을 가지며 double종종 64 비트로 구현됩니다.

예를 들어 해당 함수를 호출하면 다음과 9.2같이 얻을 수 있습니다.

>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']

데이터 해석

반환 값을 세 가지 구성 요소로 나눈 것을 볼 수 있습니다. 이러한 구성 요소는 다음과 같습니다.

  • 기호
  • 멱지수
  • 가수 (Significand 또는 분수라고도 함)

기호

부호는 첫 번째 구성 요소에 단일 비트로 저장됩니다. 설명하기 쉽습니다 0. float는 양수를 의미합니다. 1그것은 부정적인 것을 의미합니다. 9.2양수 이므로 부호 값은 0입니다.

멱지수

지수는 중간 구성 요소에 11 비트로 저장됩니다. 우리의 경우, 0b10000000010. 10 진수로 값을 나타냅니다 1026. 이 구성 요소의 특질은 당신이 동일한 숫자 빼기해야한다는 것입니다 2 - 1 (비트 수) - 1 진정한 지수를 얻을 수를; 우리의 경우에, 그것은 실제 지수를 얻기 위해 0b1111111111(소수 1023)를 빼는 것을 의미 합니다 0b00000000011(소수 숫자 3).

가수

가수는 세 번째 구성 요소에 52 비트로 저장됩니다. 그러나이 구성 요소에도 단점이 있습니다. 이 문제를 이해하려면 다음과 같이 과학적 표기법을 사용하십시오.

6.0221413x10 23

가수는 6.0221413입니다. 과학적 표기법의 가수는 항상 0이 아닌 하나의 숫자로 시작합니다. 동일은 바이너리은 두 자리 숫자를 가지고 제외하고, 바이너리에 대한 진정한 보유 : 01. 그래서 바이너리 가수는 항상 시작 1! float가 저장 될 때 1공간을 절약하기 위해 이진 가수 앞의가 생략됩니다. 진정한 가수 를 얻으려면 세 번째 요소 앞에 다시 배치해야 합니다 .

1.0010011001100110011001100110011001100110011001100110

세 번째 컴포넌트에 저장된 비트는 실제로 가수 지점 의 오른쪽에있는 가수 의 소수 부분을 나타 내기 때문에 단순한 추가 이상이 필요 합니다 .

십진수를 다룰 때, 우리는 10의 거듭 제곱으로 곱하거나 나눔으로써 "소수점을 움직입니다". 이진수에서는 2의 거듭 제곱으로 나눠서 같은 일을 할 수 있습니다. 하여 2 (52)는 그것을 오른쪽으로 52 곳을 이동합니다 :

0.0010011001100110011001100110011001100110011001100110

10 진법으로, 그 분할과 동일합니다 675539944105574의해 4503599627370496얻을 0.1499999999999999. (이것은 정확하게 이진수로 표현 될 수 있지만 대략 10 진수로 표현 될 수있는 비율의 한 예입니다. 자세한 내용은 675539944105574/4503599627370496을 참조하십시오 .)

이제 세 번째 컴포넌트를 소수로 변환 했으므로 추가 1하면 진정한 가수가됩니다.

구성 요소 리 캐핑

  • 부호 (제 1 성분) : 0양성, 1음성
  • 지수 (중간 구성 요소) : 빼기 2 (비트 수) 1 - - 1 진정한 지수를 얻을 수 있습니다
  • 가수 (마지막 구성 요소) : 2 (비트 수)로 나누고 추가 1하여 진정한 가수를 얻습니다.

숫자 계산

세 부분을 모두 합하면이 이진수가 부여됩니다.

1.0010011001100110011001100110011001100110011001100110 x 10 11

그러면 바이너리에서 10 진수로 변환 할 수 있습니다.

1.1499999999999999 x 2 3 ( inexact !)

9.2부동 소수점 값으로 저장된 후 ( )로 시작한 숫자의 최종 표현을 나타내려면 곱하십시오 .

9.1999999999999993


분수로 표현

9.2

이제 숫자를 만들었으므로 간단한 분수로 재구성 할 수 있습니다.

1.0010011001100110011001100110011001100110011001100110 x 10 11

가수를 정수로 이동하십시오.

10010011001100110011001100110011001100110011001100110 x 10 11-110100

십진수로 변환 :

5179139571476070 x 2 3-52

지수를 빼십시오 :

5,179,139,571,476,070 × 2 -49

음의 지수를 나누기 :

5179139571476070/2 49

지수를 곱하십시오 :

5179139571476070 / 562949953421312

다음과 같습니다.

9.1999999999999993

9.5

>>> float_to_bin_parts(9.5)
['0', '10000000010', '0011000000000000000000000000000000000000000000000000']

이미 가수가 단지 4 자리이며 그 뒤에 0이 많이있는 것을 볼 수 있습니다. 그러나 속도를 봅시다.

이진 과학 표기법을 조립하십시오.

1.0011 x 10 11

소수점을 이동하십시오.

10011 x 10 11-100

지수를 빼십시오 :

10011 x 10-1

이진수를 십진수로 :

19 x 2-1

나누기의 음수 지수 :

19/2 1

지수를 곱하십시오 :

19/2

같음 :

9.5



추가 자료


1
다른 방법으로가는 방법을 보여주는 멋진 자습서 도 있습니다 . 숫자를 십진수로 표현하면 부동 소수점을 어떻게 구성합니까? "긴 나누기"접근 방식은 숫자를 표현하려고 시도한 후 "나머지"로 끝나는 방법을 매우 명확하게 보여줍니다. 당신의 답변에 진정으로 "정식"이되고 싶다면 추가해야합니다.
Floris

1
파이썬과 부동 소수점에 대해 얘기하고 경우에, 나는 당신의 연결에서 파이썬 튜토리얼을 포함하여 적어도 좋을 것 : docs.python.org/3.4/tutorial/floatingpoint.html을 의이 원 스톱 이동-에 있어야하는데 그건 파이썬 프로그래머를위한 부동 소수점 문제에 대한 자료. 어떤 식 으로든 부족하고 거의 확실하다면 업데이트 또는 변경 사항에 대해 Python 버그 추적기에서 문제를여십시오.
Mark Dickinson

@mhlester 커뮤니티 위키로 바뀌면 자유롭게 답변 해주십시오.
Nicu Stiurca

5
이 답변은 초보자를위한 최고의 소개 일 것이므로 floating-point-gui.de 도 확실히 연결되어야합니다 . IMO는 "모든 컴퓨터 과학자들이 알아야 할 것"이상으로 올라 가야합니다. 요즘 골드버그의 논문을 합리적으로 이해할 수있는 사람들은 이미 잘 알고 있습니다.
Daniel Pryden 2014 년

1
"이것은 정확하게 이진수로 표현 될 수 있지만 대략 십진수로 표현 될 수있는 비율의 한 예입니다". 사실이 아닙니다. 이러한 '2의 거듭 제곱에 대한 수'비율은 모두 정확히 10 진수입니다. 편의를 위해 근사값은 소수를 짧게하는 것입니다.
Rick Regan

29

이것은 완전한 대답은 아닙니다 ( mhlester는 이미 복제하지 않을 좋은 근거를 많이 다루었습니다).하지만 숫자의 표현은 작업중인베이스에 얼마나 의존하는지 강조하고 싶습니다.

분수 2/3를 고려하십시오

good-ol 'base 10에서는 일반적으로 다음과 같이 작성합니다.

  • 0.666 ...
  • 0.666
  • 0.667

이러한 표현을 볼 때 첫 번째 표현 만 수학적으로 분수와 동일하더라도 각 표현을 분수 2/3과 연관시키는 경향이 있습니다. 두 번째 및 세 번째 표현 / 근사값은 0.001 정도의 오차를 가지며, 이는 실제로 9.2와 9.1999999999999993 사이의 오차보다 훨씬 나쁩니다. 실제로 두 번째 표현은 올바르게 반올림되지 않습니다! 그럼에도 불구하고, 우리는 숫자 2/3의 근사값으로 0.666에 문제 가 없으므로, 대부분의 프로그램에서 9.2가 어떻게 근사되는지에 대해서는 문제가되지 않습니다 . (예, 일부 프로그램에서는 중요합니다.)

수 기준

여기에 숫자의 기초가 중요합니다. 베이스 3에서 2/3를 나타내려고한다면

(2/3) 10 = 0.2 3

다시 말해, 우리는 밑을 바꾸어 같은 숫자에 대해 정확하고 유한 한 표현을합니다! 테이크 어웨이는 어떤 숫자를 어떤 염기로 변환 할 수 있지만 모든 합리적인 숫자는 어떤 염기에서는 정확한 유한 표현을하지만 다른 것은 아닙니다 .

이 지점을 집으로 몰아 가려면 1/2을 봅시다. 이 완벽하게 간단한 숫자가 10과 2의 정확한 표현이지만, 3의 반복 표현이 필요하다는 사실에 놀라실 것입니다.

(1/2) 10 = 0.5 10 = 0.1 2 = 0.1111 ... 3

부동 소수점 숫자가 왜 정확하지 않습니까?

종종, 그들은 밑수 2 (숫자 반복)에서 유한하게 표현 될 수없는 합리적 근사치이며, 일반적으로 그것들은 임의의 밑수 에서 유한하게 많은 숫자로 표현할 수없는 실제 (비합리적) 수에 근사 합니다.


3
다른 말로하면, base-3 이 완벽한 1/3것처럼 base-3 도 완벽 할 것 1/10입니다. 두 부분 모두 base-2
mhlester

2
@mhlester 예. 일반적으로 base-N 은 분모가 분모 N이거나 그 배수 인 분수에 완벽합니다 .
Nicu Stiurca 1

2
그리고 이것이 일부 수치 도구 상자가 "무엇으로 나뉘 었는지"를 추적하는 한 가지 이유이며, 그 과정에서 모든 합리적인 숫자에 대해 "무한 정확도"를 유지할 수 있습니다. 물리학 자들이 π등의 요인이 사라질 경우를 대비하여 마지막 순간까지 방정식을 상징적으로 유지하는 것처럼.
Floris

3
@Floris 또한 기본 산술 만 수행하는 (즉, 입력의 합리성을 유지하는) 알고리즘이 입력이 (합리적으로) 합리적인지 결정하고, 정상 부동 소수점 산술을 사용하여 수학을 수행 한 다음 합리적인 값을 다시 추정하는 경우도 보았습니다. 반올림 오류를 수정하기위한 근사값입니다. 특히 Matlab의 줄어든 행에 첼론 형식 알고리즘이이를 수행하며 수치 안정성을 엄청나게 도와줍니다.
Nicu Stiurca 1

@SchighSchagh-흥미롭게도, 나는 그것을 몰랐습니다. 나는 수치 안정성이 오늘날 배 배정도의 요즘에는 충분히 가르쳐지지 않은 것임을 알고 있습니다. 이는 많은 아름다운 알고리즘의 우아함에 대해 많은 사람들이 배우지 못하는 것을 의미합니다. 나는 자신의 오류를 계산하고 수정하는 알고리즘을 정말 좋아합니다.
Floris

13

다른 모든 답변은 훌륭하지만 여전히 누락 된 것이 하나 있습니다.

(예 : π, 무리수를 표현하는 것은 불가능하다 sqrt(2), log(3)정밀, 등)!

그리고 그것이 실제로 그들이 비이성적이라고 불리는 이유입니다. 세계의 비트 저장 공간 중 하나라도 저장하기에 충분하지 않습니다. 기호 산술 만 정밀도를 유지할 수 있습니다.

수학의 필요성을 합리적인 수로 제한한다면 정밀도 문제 만 관리 할 수있게됩니다. 당신은 (아마도 매우 큰) 정수의 쌍을 저장해야 a하고 b분수로 표현되는 숫자를 개최 a/b. 모든 산술은 고등학교 수학과 같이 분수에서 수행해야합니다 (예 :) a/b * c/d = ac/bd.

그러나 물론 당신은 여전히 문제의 같은 종류로 실행하는 것 pi, sqrt, log, sin, 등이 참여하고 있습니다.

TL; DR

하드웨어 가속 산술의 경우 제한된 양의 합리적인 숫자 만 나타낼 수 있습니다. 표현할 수없는 모든 수는 근사치입니다. 시스템에 관계없이 일부 숫자 (즉, 비이성적)는 표현할 수 없습니다.


4
흥미롭게도, 비이성적 인 기초가 존재합니다. 예를 들어, Phinary .
Veedrac

5
비이성적 인 숫자는 기본으로 만 표시 될 수 있습니다. 예를 들어 pi는 기본 pi에서 10
phuclv

4
포인트는 유효합니다 : 시스템에 관계없이 일부 숫자는 표현할 수 없습니다. 다른 숫자는 더 이상 표현할 수 없기 때문에 기지를 변경하면 아무것도 얻지 못합니다.
LumpN

4

실수는 무한히 많고 (너무 많아서 열거 할 수는 없습니다), 합리적인 숫자는 무한히 많습니다 (열거 가능합니다).

부동 소수점 표현은 유한 한 것이므로 (컴퓨터의 다른 것) 불가피하게 많은 수의 숫자를 표현할 수 없습니다. 특히 64 비트를 사용하면 18,446,744,073,709,551,616의 서로 다른 값 (무한대와 비교 한 값) 만 구별 할 수 있습니다. 표준 규칙에서 9.2는 그중 하나가 아닙니다. 일부 정수 m 및 e에 대해 m.2 ^ e 형식 일 수 있습니다.


예를 들어 9.2에서 정확한 표현을하는 다른 계산 시스템을 예로들 수 있습니다. 그러나 1/3이라는 다른 숫자는 여전히 표현하기가 불가능합니다.


배정 밀도 부동 소수점 숫자는 매우 정확합니다. 그들은 15 자리까지 정확한 숫자로 매우 넓은 범위의 숫자를 나타낼 수 있습니다. 일상 생활 계산의 경우 4 자리 또는 5 자리이면 충분합니다. 당신이 당신의 일생의 모든 밀리 초를 계산하지 않는 한, 당신은 정말로 그 15를 필요로하지 않을 것입니다.


1

왜 바이너리 부동 소수점으로 9.2를 표현할 수 없습니까?

부동 소수점 숫자는 제한된 자릿수와 기수를 가진 위치 번호 시스템입니다 (약간 단순화).

분모의 소인수 (분수가 가장 낮은 항으로 표현 될 때)가 밑수의 인자 인 경우, 위치 번호 시스템에서 유한 자릿수를 사용하여 분수를 정확하게 표현할 수 있습니다.

10의 소인수는 5와 2이므로 밑 10에서 a / (2 b 5 c ) 형식의 분수를 나타낼 수 있습니다 .

반면에 2의 유일한 소수는 2이므로 밑이 2 인 경우 a / (2 b ) 형식의 분수 만 나타낼 수 있습니다

컴퓨터가 왜이 표현을 사용합니까?

작업하기에 간단한 형식이기 때문에 대부분의 경우 충분히 정확하기 때문입니다. 기본적으로 과학자들이 "과학적 표기법"을 사용하고 결과를 각 단계에서 적당한 자리수로 반올림하는 것과 같은 이유입니다.

예를 들어 32 비트 분자와 32 비트 분모를 사용하여 분수 형식을 정의 할 수 있습니다. IEEE 배정 밀도 부동 소수점으로는 불가능한 숫자를 나타낼 수 있지만, 배정도 부동 소수점으로 표현할 수있는 숫자는 고정 크기 분수 형식으로 표현할 수없는 숫자가 될 수 있습니다.

그러나 가장 큰 문제는 이러한 형식이 계산하기가 어렵다는 것입니다. 두 가지 이유가 있습니다.

  1. 각 숫자를 정확히 하나의 표현으로 나타내려면 각 계산 후에 분수를 가장 낮은 항으로 줄여야합니다. 즉, 모든 연산에 대해 기본적으로 가장 큰 제수 계산을 수행해야합니다.
  2. 계산 후 분자 또는 분모 때문에 표현할 수없는 결과가 나오면 가장 가까운 표현 가능한 결과를 찾아야합니다. 이것은 사소한 일이 아닙니다.

일부 언어는 분수 유형을 제공하지만 일반적으로 임의 정밀도와 조합하여 사용하므로 분수 근사에 대해 걱정할 필요가 없지만 숫자가 많은 수의 계산 단계를 거쳐 분모의 크기와 따라서 분수에 필요한 저장 공간이 폭발 할 수 있습니다.

일부 언어는 10 진수 부동 소수점 유형도 제공하는데, 이는 주로 컴퓨터가 사람을 염두에두고 작성된 기존 반올림 규칙 (주로 재무 계산)과 일치하는 결과가 중요하지 않은 시나리오에서 사용됩니다. 이것들은 바이너리 부동 소수점보다 작동하기가 약간 어렵지만 가장 큰 문제는 대부분의 컴퓨터가 하드웨어 지원을 제공하지 않는다는 것입니다.


-4

이 시도

DecimalFormat decimalFormat = new DecimalFormat("#.##");
String.valueOf(decimalFormat.format(decimalValue))));

' decimalValue'는 변환 할 가치입니다.

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