You are running into the old problem with floating point numbers that not all numbers can be represented exactly. The command line is just showing you the full floating point form from memory.
With floating point representation, your rounded version is the same number. Since computers are binary, they store floating point numbers as an integer and then divide it by a power of two so 13.95 will be represented in a similar fashion to 125650429603636838/(2**53).
Double precision numbers have 53 bits (16 digits) of precision and regular floats have 24 bits (8 digits) of precision. The floating point type in Python uses double precision to store the values.
For example,
>>> 125650429603636838/(2**53)
13.949999999999999
>>> 234042163/(2**24)
13.949999988079071
>>> a = 13.946
>>> print(a)
13.946
>>> print("%.2f" % a)
13.95
>>> round(a,2)
13.949999999999999
>>> print("%.2f" % round(a, 2))
13.95
>>> print("{:.2f}".format(a))
13.95
>>> print("{:.2f}".format(round(a, 2)))
13.95
>>> print("{:.15f}".format(round(a, 2)))
13.949999999999999
If you are after only two decimal places (to display a currency value, for example), then you have a couple of better choices:
- Use integers and store values in cents, not dollars and then divide by 100 to convert to dollars.
- Or use a fixed point number like decimal.
Videos
You are running into the old problem with floating point numbers that not all numbers can be represented exactly. The command line is just showing you the full floating point form from memory.
With floating point representation, your rounded version is the same number. Since computers are binary, they store floating point numbers as an integer and then divide it by a power of two so 13.95 will be represented in a similar fashion to 125650429603636838/(2**53).
Double precision numbers have 53 bits (16 digits) of precision and regular floats have 24 bits (8 digits) of precision. The floating point type in Python uses double precision to store the values.
For example,
>>> 125650429603636838/(2**53)
13.949999999999999
>>> 234042163/(2**24)
13.949999988079071
>>> a = 13.946
>>> print(a)
13.946
>>> print("%.2f" % a)
13.95
>>> round(a,2)
13.949999999999999
>>> print("%.2f" % round(a, 2))
13.95
>>> print("{:.2f}".format(a))
13.95
>>> print("{:.2f}".format(round(a, 2)))
13.95
>>> print("{:.15f}".format(round(a, 2)))
13.949999999999999
If you are after only two decimal places (to display a currency value, for example), then you have a couple of better choices:
- Use integers and store values in cents, not dollars and then divide by 100 to convert to dollars.
- Or use a fixed point number like decimal.
There are new format specifications, String Format Specification Mini-Language:
You can do the same as:
"{:.2f}".format(13.949999999999999)
Note 1: the above returns a string. In order to get as float, simply wrap with float(...):
float("{:.2f}".format(13.949999999999999))
Note 2: wrapping with float() doesn't change anything:
>>> x = 13.949999999999999999
>>> x
13.95
>>> g = float("{:.2f}".format(x))
>>> g
13.95
>>> x == g
True
>>> h = round(x, 2)
>>> h
13.95
>>> x == h
True
Your z2 looks very precise, but it's actually just a Python float and so is truncated after around 16 dps. You need to do
z2 = mpf('1.64493406684822643647241516664602518921894990120679843773555822937000747040320087383362890061975870530400431896233719067962872468700500778793510294633086627683173330936776260509525100687214005479681155879489036082327776191984075645587696323563670971009694890')
to create an mpf object you can compare to zeta(2). (Also, like Sage, mpmath isn't "pure Python", in the sense that it is not part of the standard library).
Verifying a few of the responses you got:
from mpmath import mp
mp.dps = 200
mp.pretty = True
# Value from mpmath
z1 = mp.zeta(2)
print(f"z1 has type {type(z1)}")
print(f"zeta(2) from mpmath {z1}")
# Value from wolframalpha.com
z2 = 1.64493406684822643647241516664602518921894990120679843773555822937000747040320087383362890061975870530400431896233719067962872468700500778793510294633086627683173330936776260509525100687214005479681155879489036082327776191984075645587696323563670971009694890
print(f"z2 has type {type(z2)}")
print(f"z2 from copy-paste {z2}")
Output is:
z1 has type <class 'mpmath.ctx_mp_python.mpf'>
zeta(2) from mpmath 1.6449340668482264364724151666460251892189499012067984377355582293700074704032008738336289006197587053040043189623371906796287246870050077879351029463308662768317333093677626050952510068721400547968116
z2 has type <class 'float'>
z2 from copy-paste 1.6449340668482264
Getting the number of digits to the left of the decimal point is easy:
int(log10(x))+1
The number of digits to the right of the decimal point is trickier, because of the inherent inaccuracy of floating point values. I'll need a few more minutes to figure that one out.
Edit: Based on that principle, here's the complete code.
import math
def precision_and_scale(x):
max_digits = 14
int_part = int(abs(x))
magnitude = 1 if int_part == 0 else int(math.log10(int_part)) + 1
if magnitude >= max_digits:
return (magnitude, 0)
frac_part = abs(x) - int_part
multiplier = 10 ** (max_digits - magnitude)
frac_digits = multiplier + int(multiplier * frac_part + 0.5)
while frac_digits % 10 == 0:
frac_digits /= 10
scale = int(math.log10(frac_digits))
return (magnitude + scale, scale)
Not possible with floating point variables. For example, typing
>>> 10.2345
gives:
10.234500000000001
So, to get 6,4 out of this, you will have to find a way to distinguish between a user entering 10.2345 and 10.234500000000001, which is impossible using floats. This has to do with the way floating point numbers are stored. Use decimal.
import decimal
a = decimal.Decimal('10.234539048538495')
>>> str(a)
'10.234539048538495'
>>> (len(str(a))-1, len(str(a).split('.')[1]))
(17,15)
In the standard library, the decimal module may be what you're looking for. Also, I have found mpmath to be quite helpful. The documentation has many great examples as well (unfortunately my office computer does not have mpmath installed; otherwise I would verify a few examples and post them).
One caveat about the decimal module, though. The module contains several in-built functions for simple mathematical operations (e.g. sqrt), but the results from these functions may not always match the corresponding function in math or other modules at higher precisions (although they may be more accurate). For example,
from decimal import *
import math
getcontext().prec = 30
num = Decimal(1) / Decimal(7)
print(" math.sqrt: {0}".format(Decimal(math.sqrt(num))))
print("decimal.sqrt: {0}".format(num.sqrt()))
In Python 3.2.3, this outputs the first two lines
math.sqrt: 0.37796447300922719758631274089566431939601898193359375
decimal.sqrt: 0.377964473009227227214516536234
actual value: 0.3779644730092272272145165362341800608157513118689214
which as stated, isn't exactly what you would expect, and you can see that the higher the precision, the less the results match. Note that the decimal module does have more accuracy in this example, since it more closely matches the actual value.
For this particular problem, decimal is a great way to go, because it stores the decimal digits as tuples!
>>> a = decimal.Decimal(9999999998)
>>> a.as_tuple()
DecimalTuple(sign=0, digits=(9, 9, 9, 9, 9, 9, 9, 9, 9, 8), exponent=0)
Since you're looking for a property that is most naturally expressed in decimal notation, it's a bit silly to use a binary representation. The wikipedia page you linked to didn't indicate how many "non-grafting digits" may appear before the "grafting digits" begin, so this lets you specify:
>>> def isGrafting(dec, max_offset=5):
... dec_digits = dec.as_tuple().digits
... sqrt_digits = dec.sqrt().as_tuple().digits
... windows = [sqrt_digits[o:o + len(dec_digits)] for o in range(max_offset)]
... return dec_digits in windows
...
>>> isGrafting(decimal.Decimal(9999999998))
True
>>> isGrafting(decimal.Decimal(77))
True
I think there's a good chance the result of Decimal.sqrt() will be more accurate, at least for this, than the result of math.sqrt() because of the conversion between binary representation and decimal representation. Consider the following, for example:
>>> num = decimal.Decimal(1) / decimal.Decimal(7)
>>> decimal.Decimal(math.sqrt(num) ** 2) * 7
Decimal('0.9999999999999997501998194593')
>>> decimal.Decimal(num.sqrt() ** 2) * 7
Decimal('1.000000000000000000000000000')
Try using Decimal.
Read Arbitrary-precision elementary mathematical functions (Python)original for more information
Python's float type maps to whatever your platform's C compiler calls a double (see http://en.wikipedia.org/wiki/IEEE_floating_point_number).
The Python standard library also comes with an arbitrary-precision decimal module, called decimal: http://docs.python.org/2/library/decimal.html