Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nicely representing a floating-point number in python [duplicate]

I want to represent a floating-point number as a string rounded to some number of significant digits, and never using the exponential format. Essentially, I want to display any floating-point number and make sure it “looks nice”.

There are several parts to this problem:

  • I need to be able to specify the number of significant digits.
  • The number of significant digits needs to be variable, which can't be done with with the string formatting operator. [edit] I've been corrected; the string formatting operator can do this.
  • I need it to be rounded the way a person would expect, not something like 1.999999999999

I've figured out one way of doing this, though it looks like a work-round and it's not quite perfect. (The maximum precision is 15 significant digits.)

>>> def f(number, sigfig):
    return ("%.15f" % (round(number, int(-1 * floor(log10(number)) + (sigfig - 1))))).rstrip("0").rstrip(".")

>>> print f(0.1, 1)
0.1
>>> print f(0.0000000000368568, 2)
0.000000000037
>>> print f(756867, 3)
757000

Is there a better way to do this? Why doesn't Python have a built-in function for this?

like image 713
dln385 Avatar asked Apr 18 '10 19:04

dln385


People also ask

How do you represent a floating-point number in Python?

The float type in Python represents the floating point number. Float is used to represent real numbers and is written with a decimal point dividing the integer and fractional parts. For example, 97.98, 32.3+e18, -32.54e100 all are floating point numbers.

Are Python floats doubles?

Python's built-in float type has double precision (it's a C double in CPython, a Java double in Jython). If you need more precision, get NumPy and use its numpy. float128 . 0.1 + 0.2 is not exact 0.3 in python, in every other language this is a float problem but never a double problem.

How do you represent numbers in a floating-point?

Floating-Point Number Representation. A floating-point number is typically expressed in the scientific notation, with a fraction ( F ), and an exponent ( E ) of a certain radix ( r ), in the form of F×r^E . Decimal numbers use radix of 10 ( F×10^E ); while binary numbers use radix of 2 ( F×2^E ).

How do you format sig figs in Python?

Use String Formatting to Round a Number to the Given Significant Digit in Python. In Python, the %g specifier in string formats a float rounded to a specified significant figure. If the magnitude of the final number is huge, then it shows it in scientific notation. This method returns a string.


1 Answers

It appears there is no built-in string formatting trick which allows you to (1) print floats whose first significant digit appears after the 15th decimal place and (2) not in scientific notation. So that leaves manual string manipulation.

Below I use the decimal module to extract the decimal digits from the float. The float_to_decimal function is used to convert the float to a Decimal object. The obvious way decimal.Decimal(str(f)) is wrong because str(f) can lose significant digits.

float_to_decimal was lifted from the decimal module's documentation.

Once the decimal digits are obtained as a tuple of ints, the code below does the obvious thing: chop off the desired number of sigificant digits, round up if necessary, join the digits together into a string, tack on a sign, place a decimal point and zeros to the left or right as appropriate.

At the bottom you'll find a few cases I used to test the f function.

import decimal

def float_to_decimal(f):
    # http://docs.python.org/library/decimal.html#decimal-faq
    "Convert a floating point number to a Decimal with no loss of information"
    n, d = f.as_integer_ratio()
    numerator, denominator = decimal.Decimal(n), decimal.Decimal(d)
    ctx = decimal.Context(prec=60)
    result = ctx.divide(numerator, denominator)
    while ctx.flags[decimal.Inexact]:
        ctx.flags[decimal.Inexact] = False
        ctx.prec *= 2
        result = ctx.divide(numerator, denominator)
    return result 

def f(number, sigfig):
    # http://stackoverflow.com/questions/2663612/nicely-representing-a-floating-point-number-in-python/2663623#2663623
    assert(sigfig>0)
    try:
        d=decimal.Decimal(number)
    except TypeError:
        d=float_to_decimal(float(number))
    sign,digits,exponent=d.as_tuple()
    if len(digits) < sigfig:
        digits = list(digits)
        digits.extend([0] * (sigfig - len(digits)))    
    shift=d.adjusted()
    result=int(''.join(map(str,digits[:sigfig])))
    # Round the result
    if len(digits)>sigfig and digits[sigfig]>=5: result+=1
    result=list(str(result))
    # Rounding can change the length of result
    # If so, adjust shift
    shift+=len(result)-sigfig
    # reset len of result to sigfig
    result=result[:sigfig]
    if shift >= sigfig-1:
        # Tack more zeros on the end
        result+=['0']*(shift-sigfig+1)
    elif 0<=shift:
        # Place the decimal point in between digits
        result.insert(shift+1,'.')
    else:
        # Tack zeros on the front
        assert(shift<0)
        result=['0.']+['0']*(-shift-1)+result
    if sign:
        result.insert(0,'-')
    return ''.join(result)

if __name__=='__main__':
    tests=[
        (0.1, 1, '0.1'),
        (0.0000000000368568, 2,'0.000000000037'),           
        (0.00000000000000000000368568, 2,'0.0000000000000000000037'),
        (756867, 3, '757000'),
        (-756867, 3, '-757000'),
        (-756867, 1, '-800000'),
        (0.0999999999999,1,'0.1'),
        (0.00999999999999,1,'0.01'),
        (0.00999999999999,2,'0.010'),
        (0.0099,2,'0.0099'),         
        (1.999999999999,1,'2'),
        (1.999999999999,2,'2.0'),           
        (34500000000000000000000, 17, '34500000000000000000000'),
        ('34500000000000000000000', 17, '34500000000000000000000'),  
        (756867, 7, '756867.0'),
        ]

    for number,sigfig,answer in tests:
        try:
            result=f(number,sigfig)
            assert(result==answer)
            print(result)
        except AssertionError:
            print('Error',number,sigfig,result,answer)
like image 101
unutbu Avatar answered Sep 29 '22 14:09

unutbu