Why can't decimal numbers be represented exactly in binary?

前端 未结 20 3414
不知归路
不知归路 2020-11-21 05:15

There have been several questions posted to SO about floating-point representation. For example, the decimal number 0.1 doesn\'t have an exact binary representation, so it\'

20条回答
  •  余生分开走
    2020-11-21 05:31

    (Note: I'll append 'b' to indicate binary numbers here. All other numbers are given in decimal)

    One way to think about things is in terms of something like scientific notation. We're used to seeing numbers expressed in scientific notation like, 6.022141 * 10^23. Floating point numbers are stored internally using a similar format - mantissa and exponent, but using powers of two instead of ten.

    Your 61.0 could be rewritten as 1.90625 * 2^5, or 1.11101b * 2^101b with the mantissa and exponents. To multiply that by ten and (move the decimal point), we can do:

    (1.90625 * 2^5) * (1.25 * 2^3) = (2.3828125 * 2^8) = (1.19140625 * 2^9)

    or in with the mantissa and exponents in binary:

    (1.11101b * 2^101b) * (1.01b * 2^11b) = (10.0110001b * 2^1000b) = (1.00110001b * 2^1001b)

    Note what we did there to multiply the numbers. We multiplied the mantissas and added the exponents. Then, since the mantissa ended greater than two, we normalized the result by bumping the exponent. It's just like when we adjust the exponent after doing an operation on numbers in decimal scientific notation. In each case, the values that we worked with had a finite representation in binary, and so the values output by the basic multiplication and addition operations also produced values with a finite representation.

    Now, consider how we'd divide 61 by 10. We'd start by dividing the mantissas, 1.90625 and 1.25. In decimal, this gives 1.525, a nice short number. But what is this if we convert it to binary? We'll do it the usual way -- subtracting out the largest power of two whenever possible, just like converting integer decimals to binary, but we'll use negative powers of two:

    1.525         - 1*2^0   --> 1
    0.525         - 1*2^-1  --> 1
    0.025         - 0*2^-2  --> 0
    0.025         - 0*2^-3  --> 0
    0.025         - 0*2^-4  --> 0
    0.025         - 0*2^-5  --> 0
    0.025         - 1*2^-6  --> 1
    0.009375      - 1*2^-7  --> 1
    0.0015625     - 0*2^-8  --> 0
    0.0015625     - 0*2^-9  --> 0
    0.0015625     - 1*2^-10 --> 1
    0.0005859375  - 1*2^-11 --> 1
    0.00009765625...
    

    Uh oh. Now we're in trouble. It turns out that 1.90625 / 1.25 = 1.525, is a repeating fraction when expressed in binary: 1.11101b / 1.01b = 1.10000110011...b Our machines only have so many bits to hold that mantissa and so they'll just round the fraction and assume zeroes beyond a certain point. The error you see when you divide 61 by 10 is the difference between:

    1.100001100110011001100110011001100110011...b * 2^10b
    and, say:
    1.100001100110011001100110b * 2^10b

    It's this rounding of the mantissa that leads to the loss of precision that we associate with floating point values. Even when the mantissa can be expressed exactly (e.g., when just adding two numbers), we can still get numeric loss if the mantissa needs too many digits to fit after normalizing the exponent.

    We actually do this sort of thing all the time when we round decimal numbers to a manageable size and just give the first few digits of it. Because we express the result in decimal it feels natural. But if we rounded a decimal and then converted it to a different base, it'd look just as ugly as the decimals we get due to floating point rounding.

提交回复
热议问题