Reputation: 12504
I would like to display a percentage with three decimal places unless it's greater than 99%. Then, I'd like to display the number with all the available nines plus 3 non-nine characters.
How can I write this in Python? The "%.8f"
string formatting works decently, but I need to keep the last three characters after the last string of nines.
So:
54.8213% -> 54.821%
95.42332% -> 95.423%
99.9932983% -> 99.99330%
99.99999999992318 -> 99.9999999999232%
Upvotes: 0
Views: 840
Reputation: 76715
Mark Ransom's answer is a beautiful thing. With a little bit of work, it can solve the problem for any inputs. I went ahead and did the little bit of work.
You just need to add some code to nines():
def nines(x):
x = abs(x) # avoid exception caused if x is negative
x -= int(x) # keep fractional part of x only
cx = ceilpowerof10(x) - x
if 0 == cx:
return 0 # if x is a power of 10, it doesn't have a string of 9's!
return -int(math.log10(cx))
Then threeplaces() works for anything. Here are a few test cases:
>>> threeplaces(0.9999357)
'0.9999357'
>>> threeplaces(1000.9999357)
'1000.9999357'
>>> threeplaces(-1000.9999357)
'-1000.9999357'
>>> threeplaces(0.9900357)
'0.99004'
>>> threeplaces(1000.9900357)
'1000.99004'
>>> threeplaces(-1000.9900357)
'-1000.99004'
Upvotes: 2
Reputation: 146043
def ilike9s(f):
return re.sub(r"(\d*\.9*\d\d\d)\d*",r"\1","%.17f" % f)
So...
>>> ilike9s(1.0)
'1.000'
>>> ilike9s(12.9999991232132132)
'12.999999123'
>>> ilike9s(12.345678901234)
'12.345'
And don't forget to import re
Upvotes: 0
Reputation: 59645
I am quite confident that this is not possible with standard formating. I suggest to use something like the following (C# like pseudo code). Especially I suggest to rely on string operations and not to use math code because of many possible precision and rounding problems.
string numberString = number.ToStringWithFullPrecision();
int index = numberString.IndexOf('.');
while ((index < numberString.Length - 1) && (numberString[index + 1] == '9'))
{
index++;
}
WriteLine(number.PadRightWithThreeZeros().SubString(0, index + 4));
If you like regular expression, you can use them to. Take the following expression and match it against the full precision number string padded with three zeros and you are done.
^([0-9]|[1-9][0-9]|100)\.(9*)([0-8][0-9]{2})
I just realized that both suggestion may cause rounding errors. 99.91238123
becomes 99.9123
when it should become 99.9124
- so the last digits requires additional correction. Easy to do, but makes my suggestion even uglier. This is far away from an elegant and smart algorithm.
Upvotes: 0
Reputation: 54882
Try this:
import math
def format_percentage(x, precision=3):
return ("%%.%df%%%%" % (precision - min(0,math.log10(100-x)))) % x
Upvotes: 3
Reputation: 308138
def ceilpowerof10(x):
return math.pow(10, math.ceil(math.log10(x)))
def nines(x):
return -int(math.log10(ceilpowerof10(x) - x))
def threeplaces(x):
return ('%.' + str(nines(x) + 3) + 'f') % x
Note that nines() throws an error on numbers that are a power of 10 to begin with, it would take a little more work to make it safe for all input. There are probably some issues with negative numbers as well.
Upvotes: 1
Reputation: 34091
Try this:
def print_percent(p):
for i in range(30):
if p <= 100. - 10.**(-i):
print ("%." + str(max(3,3+i-1)) + "f") % p
return
or this if you just want to retrieve the string
def print_percent(p):
for i in range(20):
if p <= 100. - 10.**(-i):
return ("%." + str(max(3,3+i-1)) + "f") % p
Upvotes: 0