2

Case 1:

for num in [.1, .2, .3, .4, .5, .6, .7, .8, .9,]:
    print(format(num, ".50f"))
0.10000000000000000555111512312578270211815834045410
0.20000000000000001110223024625156540423631668090820
0.29999999999999998889776975374843459576368331909180
0.40000000000000002220446049250313080847263336181641
0.50000000000000000000000000000000000000000000000000
0.59999999999999997779553950749686919152736663818359
0.69999999999999995559107901499373838305473327636719
0.80000000000000004440892098500626161694526672363281
0.90000000000000002220446049250313080847263336181641

Imprecision, as expected (except .5).


Case 2:

for num in [1., 2., 3., 4., 5., 6., 7., 8., 9.]:
    print(format(num, ".50f"))
1.00000000000000000000000000000000000000000000000000
2.00000000000000000000000000000000000000000000000000
3.00000000000000000000000000000000000000000000000000
4.00000000000000000000000000000000000000000000000000
5.00000000000000000000000000000000000000000000000000
6.00000000000000000000000000000000000000000000000000
7.00000000000000000000000000000000000000000000000000
8.00000000000000000000000000000000000000000000000000
9.00000000000000000000000000000000000000000000000000

Perfect precision - ???


As is known, there's no such thing as a perfect float integer in computing: all floats are represented in terms of a binary base, with increasing precision depending on bitsize (float32, float64, etc). So what's the deal with Case 2 above? The zeros persist even for ".1000f", basically implying infinite precision. Further, 0.5 is also somehow represented perfectly.

If format cannot force Python to print the "true" value of a float, then what can?


Attempted alternatives:

  1. format(round(num, 50), ".50f")
  2. format(numpy.float128(num), ".50f")
  3. format(round(numpy.float128(num), 50), ".50f")
  4. format("%.50f" % num)
  5. "{:.50f}".format(num))
  6. f"{num:.50f}"

ACCEPTED ANSWER: clarifies false premise assumed in the question; the answer to the actual question is within the question itself - use format to show true numeric value.

OverLordGoldDragon
  • 1
  • 9
  • 53
  • 101
  • 1
    What is the gola behind this ? Do you really need to show a value with 50 or even 10 zeros ? – azro Nov 30 '19 at 16:01
  • @azro [Yes](https://stackoverflow.com/questions/58740925/why-is-np-dot-imprecise-n-dim-arrays), sometimes - but I'm more interested as to why Python is misleading to this end and how to circumvent it – OverLordGoldDragon Nov 30 '19 at 16:06
  • Why did you expect the second group *couldn't* be represented exactly in floating point? – jonrsharpe Nov 30 '19 at 16:27
  • @jonrsharpe Imperfect powers of 2 - basic float arithmetic theory; see [here](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) or [here](https://docs.python.org/3.7/tutorial/floatingpoint.html) – OverLordGoldDragon Nov 30 '19 at 16:32
  • I don't think you've understood what that's telling you. 3 and 5 *can* be represented exactly, even though they aren't powers of 2. – jonrsharpe Nov 30 '19 at 16:35
  • @jonrsharpe It's possible I'm missing a detail, yes, such as the atomic base of the floats, as negative powers of `.5` are also represented exactly - so that then can be used to answer the question (which I'd welcome you to) – OverLordGoldDragon Nov 30 '19 at 16:36

2 Answers2

4

In commonly used formats, such as IEEE 754 64-bit binary floating point, all finite float numbers are binary fractions, numbers of the form A*2B where A and B are both signed integers.

Of course, a finite format can only represent a finite subset of the binary fractions. Both the number of significant bits in A and the range of B are limited by the format. For normal (not subnormal) IEEE754 64-bit binary, A can have no more than 53 significant bits, and, with non-zero A normalized to the form 1.x, B has to be in the range [−1022, 1023].

0.5 can be represented exactly because it is 1*2-1. Similarly, numbers such as 5.0/8.0 (5*2-3) are exact.

In 64-bit binary floating point all integers that fit in 32 bit binary can be represented exactly, explaining the second table in the question. 9 is 9*20.

It is worth noting for the output side that every binary fraction has a terminating decimal expansion. This is a consequence of 2 being a factor of 10. Print enough digits and you will get the exact value of the floating point number.

Patricia Shanahan
  • 25,849
  • 4
  • 38
  • 75
  • Thanks for the advanced specifics; my question now is, are integral and fractional parts represented _separately_, as suggested by @NG_? They'd thus occupy separate bytes/words/etc in memory. A bitwise diagram of some sort would help, but not essential – OverLordGoldDragon Nov 30 '19 at 17:49
  • @OverLordGoldDragon That is really a different question, and probably a duplicate. Think scientific notation, but normalized to one bit before the binary point and only the fraction actually stores. [Double-precision floating-point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) has a nice diagram. – Patricia Shanahan Nov 30 '19 at 19:15
  • Have a link to the duplicate? – OverLordGoldDragon Dec 01 '19 at 14:14
1

Integral value real numbers actually can be represented in binary in perfect precision. For every natural number n, there exist a natural number k, and a sequence of 0-s and 1-s such that:

n = b0*(2^0) + b1*(2^1) + ... + bk*(2^k)

This, of course holds even if you use type float. The number is stored in finite number of bits, hence with infinite precision.

Some rational numbers can be, too - specifically, those who can be represented as :

s = b1*(0.5)^1 + b*2(0.5)^2 + ... + b*k(0.5)^k + n

for some natural numbers k,n and a binary vector

this is why you get perfect precision for 0.5, but not for other fractional values. Try 0.75 for example - you'd get perfect precision here as well.

NG_
  • 255
  • 3
  • 9
  • Not really following this explanation - if you start with (2^0), how do you get to 0.5? There should be an "atomic unit" somewhere (i.e. smallest representable float) for each precision level, so that `1.` (or `.5`'s negative power multiple) is representable as its power of 2 - can you cite such a unit? – OverLordGoldDragon Nov 30 '19 at 16:52
  • You don't start with 2^0. You start with b*(2^0), with b is 0 or 1. If b is 0, you get: 0*(2^0) = 0. – NG_ Nov 30 '19 at 17:01
  • Again, how do you get `0.5` – OverLordGoldDragon Nov 30 '19 at 17:09
  • Say you use 32 bits to represent the integral part, in our case 0, and 32 bits to represent the fractional part, in our case 1/2. than: 0.5 = 0*(2^31) + 0*(2^30) +...+0*(2^0) + 1*(2^(-1)) + 0 * (2^(-2)) + 0*(2^(-3)) + ..0*(2^(-k))..+ 0*(2^(-32)) – NG_ Nov 30 '19 at 17:14
  • So the integer and fractional parts are represented separately? Didn't know - include this in your answer, and preferably a citation/reference to this, then I'll accept it – OverLordGoldDragon Nov 30 '19 at 17:16
  • A. I will edit and add a reference and add that to the answer, but i don't think I can write about it without reading a bit. What I wrote in the comment is a little simplistic, I think. B. The standard way to represent floats is called IEEE 754, used in python as well. If you need the information now, you could look it up directly. – NG_ Nov 30 '19 at 17:35
  • I'd also suggest you remove that latest edit, as it contradicts the documentation - `format` does after all print the true value – OverLordGoldDragon Nov 30 '19 at 17:37