最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

floating point - Raising a float near 1 to an infinite power in Python - Stack Overflow

programmeradmin4浏览0评论
1.000000000000001 ** float('inf') == float('inf')

while

1.0000000000000001 ** float('inf') == 1.0

What determines the exact threshold here? It seems to be an issue of float precision, where numbers below a certain threshold are considered the same as 1.0. Is there a way to get better precision in Python?

1.000000000000001 ** float('inf') == float('inf')

while

1.0000000000000001 ** float('inf') == 1.0

What determines the exact threshold here? It seems to be an issue of float precision, where numbers below a certain threshold are considered the same as 1.0. Is there a way to get better precision in Python?

Share Improve this question edited Mar 27 at 3:45 Selcuk 59.6k12 gold badges111 silver badges115 bronze badges asked Mar 27 at 3:35 Elan SKElan SK 1392 silver badges11 bronze badges 1
  • Tip, when exploring details about FP objects, assign/print the value in hexadecimal notation as in f"{-0.1:a}" --> '-0x1.999999999999ap-4'. – chux Commented Mar 27 at 18:38
Add a comment  | 

3 Answers 3

Reset to default 5

Floating point numbers are stored in 53 bits:

IEEE 754 binary64 values contain 53 bits of precision, so on input the computer strives to convert 0.1 to the closest fraction it can of the form J/2**N where J is an integer containing exactly 53 bits.

1 / 2**53 is ~1.11e-16, so any decimal part less than that will be rounded down to 0.

It can more easily be demonstrated with:

>>> 1.000000000000001 == 1.0
False
>>> 1.0000000000000001 == 1.0
True

If you need better precision, you can use Decimals:

>>> from decimal import Decimal
>>> Decimal("1.0000000000000001") == Decimal("1.0")
False

As others have said, this is due to IEEE754 floats not being able to represent your constants with the precision you're expecting.

Python provides some useful tools that lets you see what's going on, specifically the math.nextafter method and and decimal module.

The decimal module is useful to see the decimal expansion of a float (i.e. how us humans read/write numbers), e.g.:

from decimal import Decimal as D

a = 1.000000000000001
b = 1.0000000000000001

print(D(a), D(b))

which outputs:

1.0000000000000011102230246251565404236316680908203125 1

these are the actual values seen by your computer when you use those constants. As others have said, you can see that b is actually just 1 so it's "lost" the tailing digit you entered. You just have to know that it's going to do that as a programmer.

To see nearby values the nextafter method is very useful, e.g.:

import math

c = math.nextafter(1.0, math.inf)
print(c, D(c))

which outputs:

1.0000000000000002 1.0000000000000002220446049250313080847263336181640625

the first number is what Python prints by default and only includes enough precision to be able to unambiguously distinguish the value, the second uses the decimal module and is exact.

Having a play with these is a great way to further understand what you're actually telling your computer to do. Chux also talked about hex representations of floats, in my experience these take a bit longer for people to understand but are another tool to know about when your code seems to be misbehaving. In Python you use float.hex() to see the actual bits that make up a float, using the format defined by C99, e.g.:

one = 1.0
print(one.hex(), c.hex(), math.nextafter(c, math.inf).hex())

which will output:

0x1.0000000000000p+0 0x1.0000000000001p+0 0x1.0000000000002p+0

This output is closer to how your computer represents floats. The hexadecimal fractional digits can make these values somewhat awkward to interpret, but you can get there with some more magic numbers (i.e. these come from the spec). The second is saying something like 0x10000000000001 * 2**(0-52), the -52 is needed to "shift" the all but the first digit right into the fraction.

Text "1.000000000000001" is converted into a binary64 encoded float. This value is not exactly representable. The 2 closest choices are shown below in decimal and hexadecimal formats. The closer one is used and is more than 1.0 so ... ** float('inf') has an infinite result.

//                          v-------------v  53 bits
 1.0000000000000008882... 0x1.0000000000004p+0 
"1.000000000000001"
 1.0000000000000011102... 0x1.0000000000005p+0  // Closer

Text "1.0000000000000001" is converted into a binary64. This value is not exactly representable either. The 2 closest choices are shown below. The closer one is used and is exactly 1.0 so ... ** float('inf') has a finite result of 1.0.

 1.0000000000000000000    0x1.0000000000000p+0 // Closer 
"1.0000000000000001"
 1.0000000000000002220... 0x1.0000000000001p+0

What determines the exact threshold here?

Consider the binary64 just above and below 1.0

0.99999999999999988898... 0x1.fffffffffffffp-1 
1.0000000000000000000     0x1.0000000000000p+0 
1.0000000000000002220...  0x1.0000000000001p+0 

... and look at the value half-way between 1.0 and that next value:

//                        Using an extended FP type
0.99999999999999994449... 0x1.fffffffffffff_8p-1
1.0000000000000000000     0x1.0000000000000_0p+0 
1.0000000000000001110...  0x1.0000000000000_8p+0

So "text" values about the [0.99999999999999994449... to 1.0000000000000001110...] range will convert exactly to a binary64 encoded 1.0. With ... ** float('inf') will have a finite result of 1.0.

Notice the delta above 1.0 is twice the delta below 1.0. This is due to the ULP of the values changes at 1.0.

发布评论

评论列表(0)

  1. 暂无评论