# Correct way to get an exact number of decimal digits (after the point)

If I type in

a = numerical_approx(1/14, digits=7)
print a
b=1/14
print b.n(digits=7)


I get

0.07142857
0.07142857


I count 8 digits after the point.
I wonder: How to get 7 digits as declared?

edit retag close merge delete

Sort by » oldest newest most voted

Hello, @geroyx! The problem you have is that the digits parameter of the N() function actually means number of significant figures.

Updated answer: Sage has a round function, which does exactly what you want. For example,

a = round(1/14, ndigits=7)
print(a)


You will get

0.0714286


Old answer: At the time of posting my answer, I din't know if Sage had a built-in mechanism for dealing with number of digits after the decimal point, but here is a work-around that I posted. I keep it next for historical purposes. (I didn't have the time to mathematically check if this is correct, so maybe you should do it or ask somebody to verify it.)

def N2(x, n=7):
d = floor(log(abs(x), 10)) + 1
return N(x, digits=n+d)


You can check this works fine with:

N2(-100.25)
N2(1/14)
N2(0.0123, 10)


That should return the following:

-100.2500000
0.0714286
0.0123000000


(the exact number of digits after the decimal point.)

I hope this helps!

more

@dsejas OK, I understand. Seems your code has a little mistake. Suggestion:

def NumPrint(x, n):
d = floor(log(abs(x), 10)) + 1
return N(x, digits=n+d)#, d

print NumPrint(-100.25,2)
print NumPrint(1/14,7)
print NumPrint(0.0123, 4)

( 2020-01-09 19:28:26 +0200 )edit

Oops! Sorry, @geroyx. You're right. I mixed one previous version of my code with the final one. I am editing my answer right now. Thank you!

( 2020-01-10 02:05:16 +0200 )edit

This is rather a discussion around the answer, not an answer going to the point. (Because "7 digits as declared" is not really clear. And sometimes, why not have more, when more can be easily given?!)

Let us first see what is numerical_approx doing in detail. A first word of warning is obtained when asking for the documentation of this function. We can ask for the doc string via

sage: a = 1/14
sage: a.numerical_approx?


sage: a.numerical_approx??


we get

def numerical_approx(self, prec=None, digits=None, algorithm=None):
"""
Return a numerical approximation of self with prec bits
(or decimal digits) of precision.

INPUT:

- prec -- precision in bits

- digits -- precision in decimal digits (only used if
prec is not given)

- algorithm -- which algorithm to use to compute this
approximation (the accepted algorithms depend on the object)

If neither prec nor digits is given, the default
precision is 53 bits (roughly 16 digits).

EXAMPLES::

sage: (2/3).numerical_approx()
0.666666666666667
sage: pi.n(digits=10)
3.141592654
sage: pi.n(prec=20)
3.1416

TESTS:

Check that :trac:14778 is fixed::

sage: (0).n(algorithm='foo')
0.000000000000000
"""
from sage.arith.numerical_approx import numerical_approx_generic
if prec is None:
prec = digits_to_bits(digits)
return numerical_approx_generic(self, prec)


So the digits argument is taken and used to see how many bits "would correspond" to this digits precision. In our case, if we convert to a binary number the result...

sage: a = 1/14
sage: an = a.numerical_approx(digits=7)
sage: print('{:.50f}'.format(float(an)))
0.07142857182770967483520507812500000000000000000000


If we use the prec option instead for some sensible values...

sage: for prec in [15..35]:
....:     print(f'a.n(prec={prec}) is {float(a.n(prec=prec)):.50f}')
a.n(prec=15) is 0.07143020629882812500000000000000000000000000000000
a.n(prec=16) is 0.07142829895019531250000000000000000000000000000000
a.n(prec=17) is 0.07142829895019531250000000000000000000000000000000
a.n(prec=18) is 0.07142877578735351562500000000000000000000000000000
a.n(prec=19) is 0.07142853736877441406250000000000000000000000000000
a.n(prec=20) is 0.07142853736877441406250000000000000000000000000000
a.n(prec=21) is 0.07142859697341918945312500000000000000000000000000
a.n(prec=22) is 0.07142856717109680175781250000000000000000000000000
a.n(prec=23) is 0.07142856717109680175781250000000000000000000000000
a.n(prec=24) is 0.07142857462167739868164062500000000000000000000000
a.n(prec=25) is 0.07142857089638710021972656250000000000000000000000
a.n(prec=26) is 0.07142857089638710021972656250000000000000000000000
a.n(prec=27) is 0.07142857182770967483520507812500000000000000000000
a.n(prec=28) is 0.07142857136204838752746582031250000000000000000000
a.n(prec=29) is 0.07142857136204838752746582031250000000000000000000
a.n(prec=30) is 0.07142857147846370935440063476562500000000000000000
a.n(prec=31) is 0.07142857142025604844093322753906250000000000000000
a.n(prec=32) is 0.07142857142025604844093322753906250000000000000000
a.n(prec=33) is 0.07142857143480796366930007934570312500000000000000
a.n(prec=34) is 0.07142857142753200605511665344238281250000000000000
a.n(prec=35) is 0.07142857142753200605511665344238281250000000000000


So a.n(digits=7) uses the precision...

sage: IR = RealField(3000)
sage: for prec in [1..50]:
....:     if IR(a.n(prec=prec)) == IR(a.n(digits=7)):
....:         print(prec)
27


Note that the print of the a.n() objects is using the str or repr functionality implemented in the corresponding class, which is:

sage: a.n(prec=300)
0.0714285714285714285714285714285714285714285714285714285714285714285714285714285714285714286
sage: type(_)
<class 'sage.rings.real_mpfr.RealNumber'>
sage: a.n(digits=7)
0.07142857
sage: type(_)
<class 'sage.rings.real_mpfr.RealNumber'>


To have "the own print", i converted above to (the class / type of) IR or to float.

Now to the question. "How to get 7 digits as declared?"

We have always "more digits" if we want to, to display $7$ and only $7$ digits in a printed message, use the corresponding print functionality. We use as intermediate most simply the float converter.

sage: print(f'{float(a):.7f}')
0.0714286
sage: print(f'{RR(a):.7f}')
0.0714286


Note that the printed value is rounded. If this is not wanted, than let us go this way...

sage: import decimal
sage: d = decimal.Decimal(float(a.n(80)))
sage: d
Decimal('0.0714285714285714246063463406244409270584583282470703125')
sage: c = decimal.getcontext().copy()
sage: c.prec = int(7)
sage: c.rounding = decimal.ROUND_DOWN
sage: d.normalize(c)
Decimal('0.07142857')
sage: d.normalize(c).as_tuple()
DecimalTuple(sign=0, digits=(7, 1, 4, 2, 8, 5, 7), exponent=-8)


Well, we get $7$ significant decimals...

more
def Ns(x, nd = 5):
# round x to a given number of significant digits
d = ceil(log(abs(x), 10))
return round(x, ndigits = -d + nd)

more