View Full Version : Python Round woes.
Goombaz Nov 26, 2005, 12:34 PM I am messing around with the python and so far, I have achieved the result I wanted. I modified the basic year display to display CurrentTurn/MaxTurns and the year instead of just the year.
In addition to this, I am trying to make it display the percentage of the game that has elapsed. This is the code in-total for what I have done so far.
This is in the CvMainInterface.py file(well, a copy of it in my mod directory anyhow).
g_szTimeText = unicode(gc.getGame().getGameTurn()+1) + "/" + unicode(gc.getGame().getMaxTurns()) +" " + unicode(round(100*((1.00*(gc.getGame().getGameTurn ()+1)) / gc.getGame().getMaxTurns())),2) + "% " + unicode(CyGameTextMgr().getInterfaceTimeStr(ePlaye r))
I know, it is horribly messy.
This is the part I am concerned with:
unicode(round(100*((1.00*(gc.getGame().getGameTurn ()+1)) / gc.getGame().getMaxTurns())),2)
It works fine if I type it as:
unicode((100*((1.00*(gc.getGame().getGameTurn()+1) ) / gc.getGame().getMaxTurns())))
or as:
unicode(int(100*((1.00*(gc.getGame().getGameTurn() +1)) / gc.getGame().getMaxTurns())))
The problem is, however, that the former has like a 10 digit decimal and looks way too ugly, and the latter isn't quite as fine as I wanted, as whole percentages aren't helpful, and I don't like the idea of 2.99% being shown as 2%. When I attempt to do the round-to-two places as I show above, it simply no longer prints the line to the screen at all...
Does anyone have any idea why it renders it unprintable?
Bjornlo Nov 26, 2005, 12:36 PM Wrong forum. You posted in the civ3 forum. I've let the mods know to move this thread.
Goombaz Nov 26, 2005, 12:37 PM Oh, thanks. I must have misclicked, yes tell them right away.
Padma Nov 26, 2005, 12:52 PM Moved to Civ4 C&C.
Goombaz Nov 26, 2005, 05:40 PM :bump:
Sorry this has been moved/buried so I thought I would bump it once for another go at an answer. I realize I could check a bunch of python references directly, but this is really all I want right at the moment so a direct, simple answer would be preferred.
Requies Nov 30, 2005, 01:24 AM This is the part I am concerned with:
unicode(round(100*((1.00*(gc.getGame().getGameTurn ()+1)) / gc.getGame().getMaxTurns())),2)
It works fine if I type it as:
unicode((100*((1.00*(gc.getGame().getGameTurn()+1) ) / gc.getGame().getMaxTurns())))
or as:
unicode(int(100*((1.00*(gc.getGame().getGameTurn() +1)) / gc.getGame().getMaxTurns())))
The problem is, however, that the former has like a 10 digit decimal and looks way too ugly, and the latter isn't quite as fine as I wanted, as whole percentages aren't helpful, and I don't like the idea of 2.99% being shown as 2%. When I attempt to do the round-to-two places as I show above, it simply no longer prints the line to the screen at all...
Does anyone have any idea why it renders it unprintable?
No clue. Though you could enable logging and then print out what that is using CvUtil.pyPrint. Just to see if it's actually calculating it correctly. Also, why are you multiplying by 1.00? To convert to float? If so, why not use the float() function?
Req
Bhruic Nov 30, 2005, 01:37 AM unicode(round(100*((1.00*(gc.getGame().getGameTurn ()+1)) / gc.getGame().getMaxTurns())),2)
Well, unless you've typed it out differently here than in the script, it's merely a () problem.
Ie, this should work:
unicode(round(100*((1.00*(gc.getGame().getGameTurn ()+1)) / gc.getGame().getMaxTurns()), 2))
Bh
Goombaz Nov 30, 2005, 11:59 AM When I made the change you suggested Bhuric, for some reason that I can't fathom it goes back to the 10ish place decimals :/
snarko Nov 30, 2005, 12:14 PM Not only does it show a weird result (~10 decimals), it shows a wrong one too!
Try unicode(round(1.1111, 2))
The result for me is 1.11000001431
Goombaz Nov 30, 2005, 01:05 PM Well that definately isn't good, is it? I'll have to find a way around this.
Bhruic Nov 30, 2005, 02:56 PM Well, a cheap way to get around the problem is to multiply the number by 100, change it to an int, then divide it by 100.
Bh
Goombaz Nov 30, 2005, 04:03 PM I actually tried that already Bhuric. Oddly enough dividing it by 100 results in a really really long number even AFTER it was chopped into a pure INT before dividing. It looks like it STARTS to work alright, like you get 1.25ish, but it is instead something like 1.25000000001441 or 1.249999999991441. It's like it's not *quite* accurate.
I honestly think Civ 4 python has some kind of deep-seated floating point issue. Like there is some kind of garbage in the numbers you can't quite get past. I really hope I am being naive about this, because if that is the case it could be a nasty bug. I will go back and try that cheap solution again, I am fairly sure it won't work as I already tried it but perhaps the exact numbers involved would provide insight into the nature of the problem.
Goombaz Nov 30, 2005, 04:43 PM Ok, here is the latest on the problem. I have posted the code I used to compute the percentage as well as a column of results showing the problem in detail in a real use scenario. I hope that perhaps this will be detailed enough to lead to a fix or a workaround. This is the code as per Bhurics suggestion and the unfortunate result.
unicode((int(10000*(float(gc.getGame().getGameTurn ()) / gc.getGame().getMaxTurns())))/100.0)
is the code used.
This is the result generated.
http://img397.imageshack.us/img397/8352/showerror7yc.th.gif (http://img397.imageshack.us/my.php?image=showerror7yc.gif)
Click thumbnail for detailed info.
The essence of the problem seems to be the "trailing junk" that seems to result when dividing.
Rayanth Nov 30, 2005, 08:30 PM This may sound odd but... have you had anyone else test your code on another system to make sure it duplicates the issue for them?
also, i know it's tricky to work with but try pumping the numbers through a short script that mimics what's going on in Civ4, in the Python environment and run it (without Civ4) in IDLE/debug mode.
The reason i ask about trying it on another system is that there is still an occasional processor out there with the FLOP bug that Intel was notoriuos for in the older P3 line. The issue you are having is similar to that bug (but more frequent/reproducable than the hardwre FLOP bug... I doubt it's actually the case...)
Goombaz Nov 30, 2005, 08:51 PM That is a good point. I will go grab the Python environment and fire it up.
Tell you what, I will post the buggy version and see if you get the same result. Anyone who wants to is invited to give it a go and report back to me. If it IS a bug with Civ 4 Python specifically it could be important to get the word out, or at least find a workaround.
If you (or anyone else reading this) feel like it download this version, fire it up, and tell me what you get.
Also it is entirely possible that I am just a fool. Don't keep me hanging if I have made a downright stupid mistake.
DeathCyclops Nov 30, 2005, 09:07 PM I got the same exact thing as you did.:crazyeye:
Goombaz Nov 30, 2005, 09:14 PM Ok situation update. I put this through IDLE on python 2.4.2
>>> unicode((int(10000*(float(19) / 440)))/100.0)
u'4.31'
and this corresponds to the result for turn 19/440, which was
4.30999994278
which I think you will agree, is extremely close to 4.31.
So for whatever reason the python within Civ 4 is not accurate with decimals.
Can anyone more qualified than me (I am an intermediate programmer, but *very* new to Python) confirm or disconfirm this problem specific to Civ 4 Python? IDLE is rounding correctly, Civ 4 is not. Or I am doing something wrong I suppose:crazyeye:
DeathCyclops Nov 30, 2005, 10:07 PM Maybe i'll post what I did to the file... Now im getting 2%, 4%, 6% 9%!!!!!!
Here: g_szTimeText = unicode(gc.getGame().getGameTurn()) + "/" + unicode(gc.getGame().getMaxTurns()) +" " + unicode((int(1000*(float(gc.getGame().getGameTurn( )) / gc.getGame().getMaxTurns())))/1.0) + "%" + unicode(CyGameTextMgr().getInterfaceTimeStr(ePlaye r))
This new one is...perfect they are all whole numbers wooohoooo!!!!(um... just check to make sure so i dont embaress myself...) Just replace this with what you have and check if its the right scale and whatever stuff you need.
DeathCyclops Nov 30, 2005, 10:42 PM http://www.civfanatics.net/uploads10/Show_Turns_Zero_Bak2.zip here i think this is modified one
Goombaz Dec 01, 2005, 12:03 AM While I appreciate the effort, I already got that to work. The versions that I have up in the actual modpack thread work this way. Thank you for the attempt to help, however :)
My current effort is to allow 2 decimal digits, which is not that much more clutter visually and allows for a much finer sense of the passage of time.
dlordmagic Dec 01, 2005, 02:19 AM I've been browsing for python resources to see if I could help you out. I found a website referring to your problem. The address is :http://www.python.org/doc/2.4/lib/module-decimal.html: (http://www.python.org/doc/2.4/lib/module-decimal.html)
I dont know if this will help or not as I dont really know the syntax to python yet. Click on the If the link doesnt work just copy and paste into your browser.
dlordmagic Dec 01, 2005, 02:29 AM While browsin the web I came across a site which may help you out. This is the quick address.
http://www.python.org/doc/2.4/lib/module-decimal.html
I dont know if this will work or not cause I dont know the syntax well to try it out. If the link doesnt work just copy and paste to your browser.
dlordmagic Dec 02, 2005, 04:16 AM First sorry bout the repeat reply looked under mod thread to see if it posted. Anyway here it is
g_szTimeText = unicode(gc.getGame().getGameTurn()+1) + "/" + unicode(gc.getGame().getMaxTurns()) +" " + unicode(int(100*((1.00*(gc.getGame().getGameTurn() +1)) / gc.getGame().getMaxTurns()))) + "." + unicode((int(10000*((1.00*(gc.getGame().getGameTur n()+1)) / gc.getGame().getMaxTurns()))) - (100*(int(100*((1.00*(gc.getGame().getGameTurn()+1 )) / gc.getGame().getMaxTurns()))))) + "% " + unicode(CyGameTextMgr().getInterfaceTimeStr(ePlaye r))
Even more messy, tried setting new variables to but could never get it right.
So I stuck to the originals. Basic breakdown is
unicode(int(100*((1.00*(gc.getGame().getGameTurn() +1)) / gc.getGame().getMaxTurns()))) sets the Whole Digits.
The ".' puts a period on the screen beside the whole digits.
unicode((int(10000:goodjob: *((1.00*(gc.getGame().getGameTurn()+1)) / gc.getGame().getMaxTurns()))) - (100:goodjob: *(int(100*((1.00*(gc.getGame().getGameTurn()+1)) / gc.getGame().getMaxTurns()))))) sets the two decimals.
The part after the minus sign is important or the percentage at turn#5 would look like this: 1.113. It takes the extra decimal place of and voila you have what you are looking for which is 1.13
There probaly is a simpler way of doing this but this will definately get the job done. Hope you find this useful. To get an extra decimal place simply add another zero where The green faces are.
Parenthesis tracking is pure hell
Requies Dec 02, 2005, 06:48 AM First sorry bout the repeat reply looked under mod thread to see if it posted. Anyway here it is
g_szTimeText = unicode(gc.getGame().getGameTurn()+1) + "/" + unicode(gc.getGame().getMaxTurns()) +" " + unicode(int(100*((1.00*(gc.getGame().getGameTurn() +1)) / gc.getGame().getMaxTurns()))) + "." + unicode((int(10000*((1.00*(gc.getGame().getGameTur n()+1)) / gc.getGame().getMaxTurns()))) - (100*(int(100*((1.00*(gc.getGame().getGameTurn()+1 )) / gc.getGame().getMaxTurns()))))) + "% " + unicode(CyGameTextMgr().getInterfaceTimeStr(ePlaye r))
Even more messy, tried setting new variables to but could never get it right.
So I stuck to the originals. Basic breakdown is
unicode(int(100*((1.00*(gc.getGame().getGameTurn() +1)) / gc.getGame().getMaxTurns()))) sets the Whole Digits.
The ".' puts a period on the screen beside the whole digits.
unicode((int(10000:goodjob: *((1.00*(gc.getGame().getGameTurn()+1)) / gc.getGame().getMaxTurns()))) - (100:goodjob: *(int(100*((1.00*(gc.getGame().getGameTurn()+1)) / gc.getGame().getMaxTurns()))))) sets the two decimals.
The part after the minus sign is important or the percentage at turn#5 would look like this: 1.113. It takes the extra decimal place of and voila you have what you are looking for which is 1.13
There probaly is a simpler way of doing this but this will definately get the job done. Hope you find this useful. To get an extra decimal place simply add another zero where The green faces are.
Parenthesis tracking is pure hell
Well, that DOES solve the problem.... It just seems so...... inelegant :lol:.
Oh, BTW float() is your friend :D.
Req
Zurai Dec 02, 2005, 09:05 AM That actually is accurate. The problem is because they're using floats instead of doubles; floats are less accurate and will lead to 4.99999998 instead of 5, etc. There's probably a function to truncate floating point numbers; I'd look for that. It'll have the desired result (or if not, just add 0.5 then truncate; that rounds up).
dlordmagic Dec 02, 2005, 09:39 PM Inelagent is so harsh.
I like to think of it as jury-riggin the code.:lol:
Goombaz Dec 02, 2005, 10:36 PM That actually is accurate. The problem is because they're using floats instead of doubles; floats are less accurate and will lead to 4.99999998 instead of 5, etc. There's probably a function to truncate floating point numbers; I'd look for that. It'll have the desired result (or if not, just add 0.5 then truncate; that rounds up).
Ah, so THAT is the problem. Thanks Zurai. I will read up more on Python anyhow so I can do more detailed Civ 4 changes. I must confess I had never run accross that kind of issue before with rounding, but I suppose there is always the first time. The confusing thing is, however, that it DOES work as I have written it in IDLE. So Civ 4 Python has differance/limitations when compared to stand-alone Python?
Also thanks to dlordmagic, I'll try that and if it works I'll post it up in the main thread with props to ya :goodjob:
EDIT: Ok, it's all up in the main thread.
Requies Dec 03, 2005, 12:46 AM Inelagent is so harsh.
I like to think of it as jury-riggin the code.:lol:
Heh. True.
BTW, here's the "elegant" solution. I knew that there was a way similar to C. I just couldn't remember it at the time:
g_szTimeText = unicode(gc.getGame().getGameTurn()) + "/" + unicode(gc.getGame().getMaxTurns()) + " %2.2f" % (100 *(float(gc.getGame().getGameTurn()) / float(gc.getGame().getMaxTurns())))+ "% " + unicode(CyGameTextMgr().getInterfaceTimeStr(ePlaye r))
Note, that it doesn't actually round the number, but since you just want to FORMAT the data, it allows you to do that.
Basically the format's like a printf (if you've done C programming).
But remember this way is ONLY for formatting, it won't actually round.
Req
Zurai Dec 03, 2005, 08:41 AM Ahh, so the %2.2f does it? For people who aren't sure what that means:
% - indicates that what follows it is formatting, not actual text to be displayed
2.2 - 2 digits in front of the decimal, two digits after the decimal
f - floating point number
Now, if you only wanted to display the whole percent and wanted it to round, you'd do " %2.0f" and add 0.5 to the number you got from all the getTurns stuff. Adding that 0.5 effectively rounds the number; if you have 1.49, it'll go to 1.99 and get truncated to 1, but if you have 1.5 it'll go to 2 and there you go.
Goombaz Dec 03, 2005, 03:16 PM Ahh, so the %2.2f does it? For people who aren't sure what that means:
% - indicates that what follows it is formatting, not actual text to be displayed
2.2 - 2 digits in front of the decimal, two digits after the decimal
f - floating point number
Now, if you only wanted to display the whole percent and wanted it to round, you'd do " %2.0f" and add 0.5 to the number you got from all the getTurns stuff. Adding that 0.5 effectively rounds the number; if you have 1.49, it'll go to 1.99 and get truncated to 1, but if you have 1.5 it'll go to 2 and there you go.
So it's done like a formatted print then. I never thought about it, but I bet that is supported in the Unicode function itself. I'll take some time here in the next day or so and put that version into the code just for the sake of completeness/cleanness.
eljeffe Dec 04, 2005, 10:39 AM (incorrect post)
eljeffe Dec 04, 2005, 11:12 AM (bad post 2)
(sorry, i'm getting there)
eljeffe Dec 04, 2005, 03:15 PM This is the good post.
Here's my six part solution to the issue. Each of these functions was built ad hoc for some specific purpose. By the time I made irrationalize(), I found that I had a devised a pretty handy way of formating numbers with as many or as few significant digits as my heart desired.
Here are some examples.
roundize(number, significantDigits=0, leadZero=1)
>>> roundize(123.546)
'124'
>>> roundize(123.546, 2)
'123.55'
sumize(number1, number2, significantDigits=-1, leadZero=1)
>>> sumize(12.6, .09)
'12.69'
>>> sumize(12.6, .09, 1)
'12.7'
>>> sumize(12.6, .09, 0)
'13'
multiplize(number1, number2, significantDigits=-1, leadZero=1)
>>> multiplize(23.4, 6.23)
'145.782'
>>> multiplize(23.4, 6.23, 1)
'145.8'
irrationalize(numerator, denominator, significantDigits=0, leadZero=1)
>>> irrationalize(20, 3)
'7'
>>> irrationalize(23211, 43, 9)
'539.790697674'
>>> irrationalize(656, 43224, 5)
'0.01518'
>>> irrationalize(656, 43224, 5, 0) #Include the 0 at end if you do not want a lead zero
'.01518'
(QUOTE THIS POST TO COPY THESE FUCTIONS WITH CORRECT TAB STOPS!)
def roundize(number, significantDigits=0, leadZero=1):
number=float(number)*10**significantDigits
rounded = decimalize(int(round(number)), significantDigits, leadZero)
return rounded
def sumize(number1, number2, significantDigits=-1, leadZero=1):
number1=str(number1)
number2=str(number2)
sig1=0
sig2=0
if significantDigits<0:
if number1.count('.')==1:
sig1=(len(number1)-1)-number1.find('.')
if number2.count('.')==1:
sig2=(len(number2)-1)-number2.find('.')
if sig1>=sig2:
significantDigits=sig1
else:
significantDigits=sig2
number1=float(number1)*10**significantDigits
number2=float(number2)*10**significantDigits
summed=decimalize(int(round(number1 + number2)), significantDigits, leadZero)
return summed
def multiplize(number1, number2, significantDigits=-1, leadZero=1):
number1=str(number1)
number2=str(number2)
sig1=0
sig2=0
if significantDigits<0:
if number1.count('.')==1:
sig1=(len(number1)-1)-number1.find('.')
if number2.count('.')==1:
sig2=(len(number2)-1)-number2.find('.')
significantDigits=sig1 + sig2
number1=float(number1)*10**sig1
number2=float(number2)*10**sig2
else:
number1=float(number1)*10**significantDigits
number2=float(number2)
multiplied=decimalize(int(round(number1 * number2)), significantDigits, leadZero)
return multiplied
def irrationalize(numerator, denominator, significantDigits=0, leadZero=1):
numerator=float(numerator)*10**significantDigits
denominator=float(denominator)
irrationaled = decimalize(int(round(numerator/denominator)), significantDigits, leadZero)
return irrationaled
def decimalize(number, significantDigits, leadZero=1):
significantDigits=int(significantDigits)
sign=""
if number < 0:
number = number * -1
sign = "-"
if number == 0:
return "0"
else:
number=str(number)
if len(number)<=significantDigits:
zeros = []
for i in range((significantDigits+leadZero)-len(number)):
zeros.append("0")
number=stringize(zeros) + number
prefix=len(number)-significantDigits
result = []
for i in range(prefix):
result.append(number[i])
result.append(".")
for i in range(significantDigits):
result.append(number[i+prefix])
decimaled = sign + stringize(result)
if decimaled.count('.')==1:
if decimaled.find('.')==len(decimaled)-1:
decimaled=decimaled.replace('.','')
return decimaled
def stringize(sequence):
linkChar = ""
for i in range(len(sequence)):
sequence[i]=str(sequence[i])
strung = linkChar.join(sequence)
return strung
zx1111 Dec 05, 2005, 05:27 AM Sorry, Bad post.
|
|