8

I had written the following function in which values of x,y were passed:

def check(x, y):
    print(type(x))
    print(type(y))
    print(x)
    print(y)
    if x == y:
        print "Yes"

Now when I called check(1.00000000000000001, 1.0000000000000002) it was printing:

<type 'float'>
<type 'float'>
1.0
1.0

Now from the print statements of variables x & y, it was hard for me to debug why x != y (though both were printing same values). Though I resolved it by printing x - y which gave me the difference but is there any way to modify print statement so that to know why x!=y in this particular use case without using any external print libraries and subtraction solution.

bluenote10
  • 20,013
  • 11
  • 98
  • 156
skaul05
  • 1,994
  • 2
  • 15
  • 24
  • I am using Python 2.7 – skaul05 Sep 27 '18 at 07:06
  • This question is not a duplicate of the linked question. The linked question asks for printing with a fixed number of decimals. This question searches for a possibility to get a string representation with **full precision**, but not necessarily a fixed number of decimals. This makes a huge difference, and the solution in the other question (`'{0:.16f}'.format(1.6)`) does not work at all for that purpose, e.g. `1e-300` and `2e-300` have the same fixed decimal string representation and don't satisfy "full precision". – bluenote10 Feb 01 '20 at 09:19
  • I have found a solution to that, but I can't post it anywhere because of wrong duplicate marking... – bluenote10 Feb 01 '20 at 09:29
  • Does `print(repr(x))` not do the job? – ɲeuroburɳ Jul 24 '21 at 19:50

4 Answers4

6

To get full precision and correct formatting do

format(2**(1/2), '.60g') 
# -> '1.4142135623730951454746218587388284504413604736328125'

and check it with

import decimal
print(decimal.Decimal.from_float(2**(1/2))
# -> '1.4142135623730951454746218587388284504413604736328125'

The g format type switchs to exponential notation when needed.

akuzminykh
  • 4,212
  • 4
  • 13
  • 34
angeldeluz777
  • 189
  • 2
  • 4
  • This answer produces a string representation that uses much more digits than the IEEE standard requires (wasting space by more than a factor of 2). See my answer for a string representation that produces the **minimal** literal to represent a float. – bluenote10 Aug 05 '21 at 17:51
5

UPDATE: In Python 3 str on a float is guaranteed to produce a string literal with full precision

This was not the case in Python 2. For instance str(1.0000000000000002) was '1.0' in Python 2, but in Python 3 it gives '1.0000000000000002' as expected.

This has often been a source of confusion because a simply print(x) may not have printed x with the full precision, making wrong conclusions -- like in the example of the question.

For the background of this change see here.


Python 2 solution

A simple solution to get a string representation of a float with full precision is to use either repr or json.dumps.

JSON serialization/deserialization has to make sure that roundtrips are loss-less, and thus, the implementation produces a string representation you are looking for:

def check(x, y):
    print(repr(x))
    print(repr(y))
    print("x == y is {}".format(x == y))
In [1]: check(1.00000000000000001, 1.0000000000000002)
1.0
1.0000000000000002
x == y is False

In [2]: check(1e-300, 2e-300)
1e-300
2e-300
x == y is False

In [3]: check(1e+300, 2e+300)
1e+300
2e+300
x == y is False

This also clarifies that 1.00000000000000001 actually is 1.0. This can also be checked by enumerating the numbers around 1.0 using np.nextafter, which produces the next larger/smaller representable floating point value:

    0.9999999999999994
    0.9999999999999996
    0.9999999999999997
    0.9999999999999998
    0.9999999999999999
[*] 1.0               
    1.0000000000000002
    1.0000000000000004
    1.0000000000000007
    1.0000000000000009
    1.000000000000001 

In reply to @Robert:

json.dumps also works in your case. The formatting with '.60g' simply produces a literal that has more unnecessary digits than a IEEE double precision number can hold. The precision of the other literal produced by json.dumps is sufficient to represent that particular floating point number, which you can check by:

enter image description here

The two IEEE literals closest to 1./math.sqrt(3.) are:

enter image description here

The first one the closest possible representation already, storing further digits of 1./math.sqrt(3.) will always give you back that same number.

json.dumps has loss-less round-trips for 64-bit IEEE floating point numbers, so it is guaranteed that the number of digits it produces is sufficient.

bluenote10
  • 20,013
  • 11
  • 98
  • 156
  • json.dumps actually does not work for me. ```json.dumps(1./math.sqrt(3.))``` prints 0.5773502691896258 where ```format(1./math.sqrt(3.), '.60g')``` prints 0.57735026918962584208117050366126932203769683837890625. – Robert Andreas Fritsch Aug 05 '21 at 12:43
  • 1
    @RobertAndreasFritsch The precision of the `json.dumps` literal is sufficient in this case as well. I've added a bit of explanation in the answer. Hope it helps. – bluenote10 Aug 05 '21 at 17:37
  • you are right. I was fooled by the number of digits. – Robert Andreas Fritsch Aug 20 '21 at 23:36
1

What you really need here is decimals. Python float won't allow you for such precision.

In [28]: d= Decimal('1.00000000000000001')

In [29]: print d
1.00000000000000001
-1

a solution to a similar case was discussed here: print float to n decimal places including trailing 0's

I don't know though if there is a way of printing the full length of the float. But I guess if you use like 100 values after a floating point, it will solve must of your problems.

akhavro
  • 101
  • 7
  • 1
    In that case, I have to mention maximum decimal places beforehand which can fail in certain cases. – skaul05 Sep 27 '18 at 07:09
  • String formatting doesnt help here: print '{:.20f}'.format(1.00000000000000001) -> 1.00000000000000000000 – Radosław Ganczarek Sep 27 '18 at 07:13
  • @RadosławGanczarek That's because this value is indeed exactly one. But notice that the `1.0000000000000002` in the OPs example has one less zero digit, and `'{:.20f}'.format(1.0000000000000002)` returns `1.00000000000000022204`. – bluenote10 Feb 01 '20 at 09:11