I was wondering if anybody knew of a quick way in python to check and see if a fraction gives a repeating decimal.
I have a small function that takes in two numbers and divides them. If the quotient is a repeating decimal I would like to round to 2 decimal places and if the quotient is not repeating I would like to round to just one
Example:
800/600 = 1.33333333333333 which would equal 1.33
900/600 = 1.5 would stay as 1.5
I know that I need to use the two statements for the two types of rounding
output = "{:.2f}".format(float(num))
output = "{:,}".format(float(num))
but I am having trouble with the if statement to direct to one or the other.
Can anybody help with some insight?
Use the fractions module, which implements exact rational arithmetic:
import fractions
# fractions.Fraction instances are automatically put in lowest terms.
ratio = fractions.Fraction(numerator, denominator)
You can then inspect the denominator of the result:
def is_repeating(fraction):
denom = fraction.denominator
while not (denom % 2):
denom //= 2
while not (denom % 5):
denom //= 5
return denom != 1
Just a workaround using regex :)
import re
result = str(800/600)
# result = str(900/600)
repeating_pair = re.escape(result.split('.')[1][:2])
check_within = result.split('.')[1][2:]
if re.match(repeating_pair, check_within):
print("{:.2f}".format(float(result)))
else:
print("{:.1f}".format(float(result)))
Output:
1.33
And for 900/600
1.5
Try this: Just use brute force. Since you want only 2 decimal places. Just divide and then test it when it is rounded to 0 and 1 decimal place and see where it stops being unique. If it is not unique at this point, then round to 2 decimal places.
def f(x):
if x == round(x,0):
return '{:.0f}'.format(x)
elif x == round(x,1):
return '{:.1f}'.format(x)
else:
return round(x,2)
y = [1, 2, 3, 3/2, 1/9, 8/9, 1/11, 12/11, 10/11, 14/13, 1/3]
for item in y:
print(f(item))
Output:
1
2
3
1.5
0.11
0.89
0.09
1.09
0.91
1.08
0.33
>>>
repeating decimal
There are only 10 fractions that can be written as some repeated digit - .(0), .(1), ... .(9). Thus, if you only care about repeating pattern starting right after decimal point, you only need to check against those cases.
All those numbers (and only them) give an integer if multiplied by 9.
Thus, if (9 * numenator) % denominator == 0, you'll print 2 digits.
You'll probably want to exclude .(0) pattern though. To do that, test if your fraction is in fact an integer - numenator % denominator == 0.
Also check out fractions module in case you have some wheels to reinvent.
Of course, if you only have your number as a float, there is some ambiguity about what numenator and denominator are, because floats don't actually store rational numbers like 1/3. You can experiment with fractions's .limit_denominator() to choose something that works for your case.
Related
Suppose I have 8.8333333333333339, and I want to convert it to 8.84. How can I accomplish this in Python?
round(8.8333333333333339, 2) gives 8.83 and not 8.84. I am new to Python or programming in general.
I don't want to print it as a string, and the result will be further used. For more information on the problem, please check Tim Wilson's Python Programming Tips: Loan and payment calculator.
8.833333333339 (or 8.833333333333334, the result of 106.00/12) properly rounded to two decimal places is 8.83. Mathematically it sounds like what you want is a ceiling function. The one in Python's math module is named ceil:
import math
v = 8.8333333333333339
print(math.ceil(v*100)/100) # -> 8.84
Respectively, the floor and ceiling functions generally map a real number to the largest previous or smallest following integer which has zero decimal places — so to use them for 2 decimal places the number is first multiplied by 102 (or 100) to shift the decimal point and is then divided by it afterwards to compensate.
If you don't want to use the math module for some reason, you can use this (minimally tested) implementation I just wrote:
def ceiling(x):
n = int(x)
return n if n-1 < x <= n else n+1
How all this relates to the linked Loan and payment calculator problem:
From the sample output it appears that they rounded up the monthly payment, which is what many call the effect of the ceiling function. This means that each month a little more than 1⁄12 of the total amount is being paid. That made the final payment a little smaller than usual — leaving a remaining unpaid balance of only 8.76.
It would have been equally valid to use normal rounding producing a monthly payment of 8.83 and a slightly higher final payment of 8.87. However, in the real world people generally don't like to have their payments go up, so rounding up each payment is the common practice — it also returns the money to the lender more quickly.
This is normal (and has nothing to do with Python) because 8.83 cannot be represented exactly as a binary float, just as 1/3 cannot be represented exactly in decimal (0.333333... ad infinitum).
If you want to ensure absolute precision, you need the decimal module:
>>> import decimal
>>> a = decimal.Decimal("8.833333333339")
>>> print(round(a,2))
8.83
You want to use the decimal module but you also need to specify the rounding mode. Here's an example:
>>> import decimal
>>> decimal.Decimal('8.333333').quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_UP)
Decimal('8.34')
>>> decimal.Decimal('8.333333').quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN)
Decimal('8.33')
>>>
A much simpler way is to simply use the round() function. Here is an example.
total_price = float()
price_1 = 2.99
price_2 = 0.99
total_price = price_1 + price_2
If you were to print out total_price right now you would get
3.9800000000000004
But if you enclose it in a round() function like so
print(round(total_price,2))
The output equals
3.98
The round() function works by accepting two parameters. The first is the number you want to round. The second is the number of decimal places to round to.
The easiest way to do this is by using the below function, which is built in:
format()
For example:
format(1.242563,".2f")
The output would be:
1.24
Similarly:
format(9.165654,".1f")
would give:
9.2
If you round 8.8333333333339 to 2 decimals, the correct answer is 8.83, not 8.84. The reason you got 8.83000000001 is because 8.83 is a number that cannot be correctly reprecented in binary, and it gives you the closest one. If you want to print it without all the zeros, do as VGE says:
print "%.2f" % 8.833333333339 #(Replace number with the variable?)
If you want to round, 8.84 is the incorrect answer. 8.833333333333 rounded is 8.83 not 8.84. If you want to always round up, then you can use math.ceil. Do both in a combination with string formatting, because rounding a float number itself doesn't make sense.
"%.2f" % (math.ceil(x * 100) / 100)
Just for the record. You could do it this way:
def roundno(no):
return int(no//1 + ((no%1)/0.5)//1)
There, no need for includes/imports
Here is my solution for the round up/down problem
< .5 round down
> = .5 round up
import math
def _should_round_down(val: float):
if val < 0:
return ((val * -1) % 1) < 0.5
return (val % 1) < 0.5
def _round(val: float, ndigits=0):
if ndigits > 0:
val *= 10 ** (ndigits - 1)
is_positive = val > 0
tmp_val = val
if not is_positive:
tmp_val *= -1
rounded_value = math.floor(tmp_val) if _should_round_down(val) else math.ceil(tmp_val)
if not is_positive:
rounded_value *= -1
if ndigits > 0:
rounded_value /= 10 ** (ndigits - 1)
return rounded_value
# test
# nr = 12.2548
# for digit in range(0, 4):
# print("{} decimals : {} -> {}".format(digit, nr, _round(nr, digit)))
# output
# 0 decimals : 12.2548 -> 12
# 1 decimals : 12.2548 -> 12.0
# 2 decimals : 12.2548 -> 12.3
# 3 decimals : 12.2548 -> 12.25
I have this code:
tax = (tax / 100) * price
and then this code:
tax = round((tax / 100) * price, 2)
round worked for me
Use the decimal module: http://docs.python.org/library/decimal.html
ََََََ
Here is a simple function to do this for you:
def precision(num,x):
return "{0:.xf}".format(round(num))
Here, num is the decimal number. x is the decimal up to where you want to round a floating number.
The advantage over other implementation is that it can fill zeros at the right end of the decimal to make a deciaml number up to x decimal places.
Example 1:
precision(10.2, 9)
will return
10.200000000 (up to 9 decimal points)
Example 2:
precision(10.2231, 2)
will return
10.22 (up to two decimal points)
I am aware of the nature of floating point math but I still find the following surprising:
from fractions import Fraction
print(Fraction(0.2)) # -> 3602879701896397/18014398509481984
print(Fraction(str(0.2))) # -> 1/5
print(Fraction(0.2)==Fraction(str(0.2))) # returns False
print(0.2 == float(str(0.2))) # but this returns True!
From the documentation I could not find anything that would explain that. It does state:
...In addition, any string that represents a finite value and is
accepted by the float constructor is also accepted by the Fraction
constructor...
but to me this implies a similar behavior to float() which I just do not see as shown above.
Is there any explanation for this?
It is important to note that the behavior shown above is not specific to the value (0.2) but rather general; everything I tried behaved the same way.
Interestingly enough:
from fractions import Fraction
for x in range(1, 257):
if Fraction(str(1/x))==Fraction(1/x):
print(x)
prints only the powers of 2 that are smaller than the selected upper bound:
1
2
4
8
16
32
64
128
256
Have a look at the def __new__(): implementation in fractions.py, if a string is given:
The regex _RATIONAL_FORMAT ( see link if you are interested in the parsing part) puts out numerator as 0 and decimal as 2
Start quote from fractions.py source, with comments by me
elif isinstance(numerator, str):
# Handle construction from strings.
m = _RATIONAL_FORMAT.match(numerator)
if m is None:
raise ValueError('Invalid literal for Fraction: %r' %
numerator)
numerator = int(m.group('num') or '0') # 0
denom = m.group('denom')
if denom: # not true for your case
denominator = int(denom)
else: # we are here
denominator = 1
decimal = m.group('decimal') # yep: 2
if decimal:
scale = 10**len(decimal) # thats 10^1
numerator = numerator * scale + int(decimal) # thats 0 * 10^1+0 = 10
denominator *= scale # thats 1*2
exp = m.group('exp')
if exp: # false
exp = int(exp)
if exp >= 0:
numerator *= 10**exp
else:
denominator *= 10**-exp
if m.group('sign') == '-': # false
numerator = -numerator
else:
raise TypeError("argument should be a string "
"or a Rational instance")
end quote from source
So '0.2' is parsed to 2 / 10 = 0.2 exactly, not its nearest float approximation wich my calculater puts out at 0,20000000000000001110223024625157
Quintessential: they are not simply using float( yourstring ) but are parsing and calculating the string itself, that is why both differ.
If you use the same constructor and provide a float or decimal the constructor uses the builtin as_integer_ratio() to get numerator and denominator as representation of that number.
The closest the float representation comes to 0.2 is 0,20000000000000001110223024625157 which is exactly what the as_integer_ratio() method returns nominator and denominator for.
As eric-postpischil
and mark-dickinson pointed out, this float value is limited by its binary representations to "close to 0.2". When put into str() will be truncated to exact '0.2' - hence the differences between
print(Fraction(0.2)) # -> 3602879701896397/18014398509481984
print(Fraction(str(0.2))) # -> 1/5
In print(Fraction(0.2)), the source text 0.2 is converted to a floating-point value. The result of this conversion is exactly 0.200000000000000011102230246251565404236316680908203125, or 3602879701896397/18014398509481984. This value is then passed to Fraction, which produces the same value represented as a rational number.
In print(Fraction(str(0.2))), 0.2 is again converted to a floating-point value, yielding the number above. Then str converts it to a string. In current Python versions, when a floating-point value is converted to a string, Python does not generally produce the exact mathematical value. Instead, it produces the just enough digits so that converting the string back to floating-point produces the input number. In this case, that results in “0.2”. So the string “0.2” is passed to Fraction. Then Fraction analyzes “0.2” and determines it is 1/5.
Notice the last digit in the denominator. It appears the fractions module takes this into consideration when storing the object internally, but when used in operations python can round.
from fractions import Fraction
Fraction(3602879701896397, 18014398509481985) == Fraction(1, 5) # True
Fraction(3602879701896397, 18014398509481984) == Fraction(1, 5) # False
3602879701896397 / 18014398509481985 == 0.2 # True
3602879701896397 / 18014398509481984 == 0.2 # True
Now the question of why the fractions module chooses an approximation (i.e. 18014398509481984 instead of correct 18014398509481985) is not one I can answer.
I need to round down and it should be two decimal places.
Tried the following,
a = 28.266
print round(a, 2)
28.27
But the expected value is 28.26 only.
Seems like you need the floor:
import math
math.floor(a * 100)/100.0
# 28.26
It seems you want truncation, not rounding.
A simple way would be to combine floor division // and regular division /:
>>> a = 28.266
>>> a // 0.01 / 100
28.26
Instead of the regular division you could also multiply (as noted in the comments by cmc):
>>> a // 0.01 * 0.01
28.26
Similarly you could create a function to round down to other more/less decimals. But because floats are inexact numbers, this can lead to inaccuracies.
def round_down(value, decimals):
factor = 1 / (10 ** decimals)
return (value // factor) * factor
print(round_down(28.266, 2))
# 28.26
But as said it's not exactly exact:
for i in range(0, 8):
print(i, round_down(12.33333, i))
0 12.0
1 12.3
2 12.33
3 12.333
4 12.333300000000001 # weird, but almost correct
5 12.33332 # wrong
6 12.33333
7 12.33333
There are other (more precise) approaches though:
A solution using the fraction module
A fraction can represent a decimal number much more exact than a float. Then one can use the "multiply, then floor, then divide" approach mentioned by Psidom but with significantly higher precision:
import fractions
import math
a = 28.266
def round_down(value, decimals):
factor = 10 ** decimals
f = fractions.Fraction(value)
return fractions.Fraction(math.floor(f * factor), factor)
print(round_down(28.266, 2))
# 1413/50 <- that's 28.26
And using the test I did with the floats:
for i in range(0, 8):
print(i, round_down(12.33333, i))
0 12
1 123/10
2 1233/100
3 12333/1000
4 123333/10000
5 1233333/100000
6 1233333/100000
7 1233333/100000
However creating a Fraction will not magically fix an inexact float, so typically one should create the Fraction from a string or a "numerator-denominator pair" instead of from float.
A solution using the decimal module
You could also use the decimal module, which offers a variety of rounding modes, including rounding down.
For this demonstration I'm using a context manager to avoid changing the decimal rounding mode globally:
import decimal
def round_down(value, decimals):
with decimal.localcontext() as ctx:
d = decimal.Decimal(value)
ctx.rounding = decimal.ROUND_DOWN
return round(d, decimals)
print(round_down(28.266, 2)) # 28.26
Which gives more sensible results for the rounding:
for i in range(0, 8):
print(i, round_down(12.33333, i))
0 12
1 12.3
2 12.33
3 12.333
4 12.3333
5 12.33333
6 12.333330
7 12.3333300
As with Fraction a Decimal should be created from a string to avoid the intermediate inexact float. But different from Fraction the Decimal have limited precision, so for values with lots of significant figures it will also become inexact.
However "rounding down" is just one of the available options. The list of available rounding modes is extensive:
Rounding modes
decimal.ROUND_CEILING Round towards Infinity.
decimal.ROUND_DOWN Round towards zero.
decimal.ROUND_FLOOR Round towards -Infinity.
decimal.ROUND_HALF_DOWN Round to nearest with ties going towards zero.
decimal.ROUND_HALF_EVEN Round to nearest with ties going to nearest even integer.
decimal.ROUND_HALF_UP Round to nearest with ties going away from zero.
decimal.ROUND_UP Round away from zero.
decimal.ROUND_05UP Round away from zero if last digit after rounding towards zero would have been 0 or 5; otherwise round towards zero.
With Python 3 you can use quantize()
from decimal import *
>>> Decimal('7.325').quantize(Decimal('.01'), rounding=ROUND_DOWN)
Decimal('7.32')
here's a simple function that isn't affected by float precision errors
def truncate_float(n, places):
return int(n * (10 ** places)) / 10 ** places
Tests:
>>> truncate_float(28.266, 3)
28.266
>>> truncate_float(28.266, 2)
28.26
>>> truncate_float(28.266, 1)
28.2
simply try this:
import math
a = 28.266
print((math.floor(a * 100)) / 100.0)
Output:
28.26
There's an even simpler way of doing this generically, by subtracting a small quantity before rounding, like so:
a = 28.269
digits = 2
print(round(a - 0.5/10**digits, digits))
This is based on the intuition that one way of rounding a float to the nearest integer is by adding 0.5, then truncating. The above solution does the opposite by subtracting a half of the minimal 'tick' that the desired precision allows, then rounding.
Simple function which you can use in your codes. This function you can also use for integer floor numbers.
import math
def floorDecimal(number, decimal):
return math.floor(number * pow(10, decimal))/pow(10, decimal)
Example of using:
number = 256.789
newNumber = floorDecimal(number, 2) # newNumber is 256.78
newNumber = floorDecimal(number, -2) # newNumber is 200
Here is the function I use with f for the float and d for the number of decimals
from math import floor
def floor_decimal(f, d):
n = 10 ** d
return floor(f * n) / n
Suppose I have 8.8333333333333339, and I want to convert it to 8.84. How can I accomplish this in Python?
round(8.8333333333333339, 2) gives 8.83 and not 8.84. I am new to Python or programming in general.
I don't want to print it as a string, and the result will be further used. For more information on the problem, please check Tim Wilson's Python Programming Tips: Loan and payment calculator.
8.833333333339 (or 8.833333333333334, the result of 106.00/12) properly rounded to two decimal places is 8.83. Mathematically it sounds like what you want is a ceiling function. The one in Python's math module is named ceil:
import math
v = 8.8333333333333339
print(math.ceil(v*100)/100) # -> 8.84
Respectively, the floor and ceiling functions generally map a real number to the largest previous or smallest following integer which has zero decimal places — so to use them for 2 decimal places the number is first multiplied by 102 (or 100) to shift the decimal point and is then divided by it afterwards to compensate.
If you don't want to use the math module for some reason, you can use this (minimally tested) implementation I just wrote:
def ceiling(x):
n = int(x)
return n if n-1 < x <= n else n+1
How all this relates to the linked Loan and payment calculator problem:
From the sample output it appears that they rounded up the monthly payment, which is what many call the effect of the ceiling function. This means that each month a little more than 1⁄12 of the total amount is being paid. That made the final payment a little smaller than usual — leaving a remaining unpaid balance of only 8.76.
It would have been equally valid to use normal rounding producing a monthly payment of 8.83 and a slightly higher final payment of 8.87. However, in the real world people generally don't like to have their payments go up, so rounding up each payment is the common practice — it also returns the money to the lender more quickly.
This is normal (and has nothing to do with Python) because 8.83 cannot be represented exactly as a binary float, just as 1/3 cannot be represented exactly in decimal (0.333333... ad infinitum).
If you want to ensure absolute precision, you need the decimal module:
>>> import decimal
>>> a = decimal.Decimal("8.833333333339")
>>> print(round(a,2))
8.83
You want to use the decimal module but you also need to specify the rounding mode. Here's an example:
>>> import decimal
>>> decimal.Decimal('8.333333').quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_UP)
Decimal('8.34')
>>> decimal.Decimal('8.333333').quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN)
Decimal('8.33')
>>>
A much simpler way is to simply use the round() function. Here is an example.
total_price = float()
price_1 = 2.99
price_2 = 0.99
total_price = price_1 + price_2
If you were to print out total_price right now you would get
3.9800000000000004
But if you enclose it in a round() function like so
print(round(total_price,2))
The output equals
3.98
The round() function works by accepting two parameters. The first is the number you want to round. The second is the number of decimal places to round to.
The easiest way to do this is by using the below function, which is built in:
format()
For example:
format(1.242563,".2f")
The output would be:
1.24
Similarly:
format(9.165654,".1f")
would give:
9.2
If you round 8.8333333333339 to 2 decimals, the correct answer is 8.83, not 8.84. The reason you got 8.83000000001 is because 8.83 is a number that cannot be correctly reprecented in binary, and it gives you the closest one. If you want to print it without all the zeros, do as VGE says:
print "%.2f" % 8.833333333339 #(Replace number with the variable?)
If you want to round, 8.84 is the incorrect answer. 8.833333333333 rounded is 8.83 not 8.84. If you want to always round up, then you can use math.ceil. Do both in a combination with string formatting, because rounding a float number itself doesn't make sense.
"%.2f" % (math.ceil(x * 100) / 100)
Just for the record. You could do it this way:
def roundno(no):
return int(no//1 + ((no%1)/0.5)//1)
There, no need for includes/imports
Here is my solution for the round up/down problem
< .5 round down
> = .5 round up
import math
def _should_round_down(val: float):
if val < 0:
return ((val * -1) % 1) < 0.5
return (val % 1) < 0.5
def _round(val: float, ndigits=0):
if ndigits > 0:
val *= 10 ** (ndigits - 1)
is_positive = val > 0
tmp_val = val
if not is_positive:
tmp_val *= -1
rounded_value = math.floor(tmp_val) if _should_round_down(val) else math.ceil(tmp_val)
if not is_positive:
rounded_value *= -1
if ndigits > 0:
rounded_value /= 10 ** (ndigits - 1)
return rounded_value
# test
# nr = 12.2548
# for digit in range(0, 4):
# print("{} decimals : {} -> {}".format(digit, nr, _round(nr, digit)))
# output
# 0 decimals : 12.2548 -> 12
# 1 decimals : 12.2548 -> 12.0
# 2 decimals : 12.2548 -> 12.3
# 3 decimals : 12.2548 -> 12.25
I have this code:
tax = (tax / 100) * price
and then this code:
tax = round((tax / 100) * price, 2)
round worked for me
Use the decimal module: http://docs.python.org/library/decimal.html
ََََََ
Here is a simple function to do this for you:
def precision(num,x):
return "{0:.xf}".format(round(num))
Here, num is the decimal number. x is the decimal up to where you want to round a floating number.
The advantage over other implementation is that it can fill zeros at the right end of the decimal to make a deciaml number up to x decimal places.
Example 1:
precision(10.2, 9)
will return
10.200000000 (up to 9 decimal points)
Example 2:
precision(10.2231, 2)
will return
10.22 (up to two decimal points)
This question already has answers here:
Is floating point arbitrary precision available?
(5 answers)
Closed 2 years ago.
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?
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)
If you want floating point precision you need to use the decimal module, which is part of the Python Standard Library:
>>> import decimal
>>> d = decimal.Decimal('0.0000000000368568')
>>> print '%.15f' % d
0.000000000036857
Here is a snippet that formats a value according to the given error bars.
from math import floor, log10, round
def sigfig3(v, errplus, errmin):
i = int(floor(-log10(max(errplus,errmin)) + 2))
if i > 0:
fmt = "%%.%df" % (i)
return "{%s}^{%s}_{%s}" % (fmt % v,fmt % errplus, fmt % errmin)
else:
return "{%d}^{%d}_{%d}" % (round(v, i),round(errplus, i), numpy.round(i))
Examples:
5268685 (+1463262,-2401422) becomes 5300000 (+1500000,-2400000)
0.84312 +- 0.173124 becomes 0.84 +- 0.17
Arbitrary precision floats are needed to properly answer this question. Therefore using the decimal module is a must. There is no method to convert a decimal to a string without ever using the exponential format (part of the original question), so I wrote a function to do just that:
def removeExponent(decimal):
digits = [str(n) for n in decimal.as_tuple().digits]
length = len(digits)
exponent = decimal.as_tuple().exponent
if length <= -1 * exponent:
zeros = -1 * exponent - length
digits[0:0] = ["0."] + ["0"] * zeros
elif 0 < -1 * exponent < length:
digits.insert(exponent, ".")
elif 0 <= exponent:
digits.extend(["0"] * exponent)
sign = []
if decimal.as_tuple().sign == 1:
sign = ["-"]
print "".join(sign + digits)
The problem is trying to round to significant figures. Decimal's "quantize()" method won't round higher than the decimal point, and the "round()" function always returns a float. I don't know if these are bugs, but it means that the only way to round infinite precision floating point numbers is to parse it as a list or string and do the rounding manually. In other words, there is no sane answer to this question.