It's been a few weeks (months?) since my last Friday ColdFusion Contest, but I had an interesting idea today while on the treadmill. The news reported that the amount of lost luggage had declined in 2009 over 2010. They reported that "11 out of a 1000" people reported lost luggage. That struct me as a bit odd as normally you see a figure in the form of "N out of 100." It then occurred to me that it must just be that the percentage is less than one percent. Most folks probably wouldn't be able to grok 0.11% at 7AM.
Your task today - and remember - this should take no more than five minutes - is to write a UDF that accepts a percentage value and returns a string. For percentages from 1 to 100, it should be return "N out of 100". For percentages less than 1 it should return "N out of 1000", or, if you want, make it go even deeper, for example, converting 0.001 to "1 out of 10000". (And if my math is wrong, I blame the lack of coffee!) You could also handle rounding numbers, so 5.6 becomes "6 out of 100."
As before, I'll be giving away a 20 dollar Amazon Gift Certificate. The "winner" will be 100% arbitrary and flattery will be counted.
Brian Rinaldi informed me that my math is wrong. Therefore, Brian Rinaldi will never win a contest again. Thanks Brian. ;) So 11 out of 1000 is really 1.1%. So that actually adds an interesting twist. Should you convert 1.1 to "1 out of 100" (ie, round down), or "11 out of 1000"? I'm not going to answer that - but will leave it up to you guys to decide how to answer ir.
Archived Comments
BTW, best pic ever!
11 out of 1000 is actually 1.1%, but even that is an awkward percentage. So perhaps we should we convert percentages less than 2 to N out of 1000? For example: 1.2% would be 12 out of 1000.
@Olivier - Yep, I was wrong. Brian Rinaldi pinged me on IM to give me a math beatdown. (Reload the blog post to see new last comment.) I'll take _any_ interesting solution as an entry.
function causePanic( percent )
{
var data = StructNew();
data['statistic'] = RandRange( 0,100 ) & ' out of 100 people become zombies each year!';
data['message'] = 'Run for the hills! The Zombie invasion has begun!';
data['note'] = 'Lies, damned lies and statistics!'
return data;
}
That ought to do!
<cffunction name="outOfConvert" access="public" returntype="string">
<cfargument name="inPercent" type="numeric" required="true">
<cfset var outSTR = "">
<cfset var percent = arguments.inPercent/>
<cfset var outOf = 100/>
<cfloop condition="#percent# LESS THAN 1">
<cfset percent *= 10/>
<cfset outOf *= 10/>
</cfloop>
<cfset percent = round(percent)/>
<cfset outSTR = percent & " out of " & outOf/>
<cfreturn outSTR/>
</cffunction>
The zombie one is way better than mine. And it's an important safety tip!
OK, Total Cheat, but I combined the two entries above to get you "ZombieStats"
function zombiesStats(normalStat) {
var zombiesStat = "";
var humans = arguments.normalStat;
var Zombies = 100;
while (humans LT 1) {
humans *= 10;
Zombies *= 10;
}
humans = round(humans);
zombiesStat = humans & " OUT OF " & Zombies;
return zombiesStat;
}
Nice one @Kevin - LOL - I heart zombies. You've gotta check out that new show on Comedy Central about the zombies and demons - it's pretty good.
Here's my attempt
<cffunction name="percentToString" access="public" returntype="string">
<cfargument name="enteredPercent" type="numeric" required="true">
<cfargument name="TwentyFourHrNewsNetworkStatistics" type="boolean" required="false" default="false">
<cfset var length = 0>
<cfset var percent = 0>
<cfset var multiplier = 1>
<cfset var newnum = 0>
<cfif arguments.TwentyFourHrNewsNetworkStatistics>
<cfreturn RandRange(0,100) & " out of 100">
</cfif>
<cfif arguments.enteredPercent gte 1>
<cfset percent = arguments.enteredPercent / 100>
<cfset length = len(percent) - 2>
<cfelse>
<cfset percent = arguments.enteredPercent>
<cfset length = len(percent) - 1>
</cfif>
<cfloop from="#length#" to="1" step="-1" index="i">
<cfset multiplier = multiplier & 0>
</cfloop>
<cfset newnum = percent * multiplier>
<cfreturn newnum & " out of " & multiplier>
</cffunction>
So far, only two "real" submissions - chances are good for you guys. ;)
<cffunction name="percentString" returntype="string">
<cfargument name="percentIn" type="numeric">
<cfset var numDecPlaces = Len(ListLast(percentIn, "."))>
<cfset var outOf = 100>
<cfif FindNoCase(".", percentIn) lte 0>
<cfset numDecPlaces = 0>
</cfif>
<cfset outOf = outOf & RepeatString('0', numDecPLaces )>
<cfreturn ReplaceNoCase(percentIn, ".", "", "ALL") & " out of " & outOf>
</cffunction>
<cfoutput>
#percentString(1)#<br />
#percentString(.25)#<br />
#percentString(25)#<br />
#percentString(1.1)#<br />
#percentString(1000)#<br />
#percentString(100)#<br />
</cfoutput>
I don't have access to ColdFusion right now so I can't test to make sure there are no errors. But here are my thoughts...
<cffunction name="ConvertPercentToString" access="public" returntype="string">
<cfargument name="pct" type="numeric" required="true">
<!--- Create return variable --->
<cfset var s = "">
<!--- Convert percent to decimal --->
<cfset var dec = (pct / 100)>
<!--- Set defaults --->
<cfset var iNumDecimalPlaces = -1>
<cfset var iNumericValuePosition = -1>
<!--- Convert to string and remove leading "0." --->
<cfset dec = ToString(dec)>
<cfset dec = Replace(dec, "0.", "", "ALL")>
<!--- Loop over remaining digits of string so we can get count of decimal places and mark position of first non-zero digit --->
<cfloop from="1" to="#Len(dec)#" index="iNumDecimalPlaces">
<cfif (Int(Mid(dec, iNumDecimalPlaces, 1)) > 0) AND (iNumericValuePosition EQ -1)>
<cfset iNumericValuePosition = iNumDecimalPlaces>
</cfif>
</cfloop>
<!--- If non-zero digit found, then use it and number of decimal places to build string --->
<cfif (iNumericValuePosition GT -1)>
<cfset sNumericPortion = Right(dec, (Len(dec) - iNumericValuePosition))>
<cfset s = sNumericPortion & " out of " & (10 ^ (iNumDecimalPlaces - 1))>
</cfif>
<cfreturn s>
</cffunction>
I swear I didn't read Danny or Curt's entries before writing this. It's very similar, but I'm submitting it anyway since it converts 90% to 9 out of 10 instead of 90 out of 100, which seems more like how it would appear in a news report. Also, I have added more flattery, which ought to count for something
<cffunction name="NOutOf" output="false" returntype="string">
<cfargument name="percent" type="numeric" required="true">
<cfset var n = arguments.percent/100>
<cfset var outof = 1>
<cfloop condition="round(n) is not n">
<cfset n = n*10>
<cfset outof = outof*10>
</cfloop>
<cfreturn "#NumberFormat(n)# out of #NumberFormat(outof)# people think that Ray's blog is awesome.">
</cffunction>
My entry (changed your rules slightly to also allow 90% to be returned as "9 out of 10" - all those dentists can't be wrong!)
=====
<cffunction name="textPercent" access="public" output="false" returntype="any">
<cfargument name="percent" type="any" required="true"/>
<cfset var local = StructNew()>
<cfset local.rc = "Invalid percentage!">
<cfif isvalid('Numeric',arguments.percent)>
<cfset local.p = arguments.percent/10.0>
<cfset local.multiplier=10>
<cfloop index="local.i" from="1" to="4" step="1">
<cfif local.p eq int(local.p)><cfbreak></cfif>
<cfset local.p *= 10>
<cfset local.multiplier *= 10>
</cfloop>
<cfset local.rc = local.p & ' out of ' & local.multiplier>
</cfif>
<cfreturn local.rc />
</cffunction>
I've got no access to a ColdFusion box to test this (or even a CFML aware text editor). Hope this works:
<cffunction name="XOutOf" output="false" returntype="string">
<cfargument name="percentage" type="numeric" required="true" />
<cfset var factor = "1#RepeatString("0", Len(ListLast(arguments.percentage, "."))#" />
<cfreturn "#arguments.percentage * factor# out of #10 * factor#" />
</cffunction>
Just an FYI, going to make a random pick, I mean err a serious, hard choice at 4PM CST.
Guys - I apologize. I totally dropped the ball on this. Going to pick a winner tomorrow.
"That struct me as a bit odd..." A wee bit too much coldfusion vernacular? hehehe (some english beatdown for an english major? hehe)
Some results. I used this as a 'test harness':
<cfset tests = "1,2,3,0.1,0.11,0.5,1.5">
<cfset theFunc = x>
<cfloop index="t" list="#tests#">
<cfoutput>#t# = #thefunc(t)#<br/></cfoutput>
</cfloop>
where x was the udf to test.
@Danny - yours didn't seem to work for 0.1 or 0.11.
@Josh: for 0.1 it set 10 out of 100 - it got all the x.y ones wrong.
@Michael: Yours seems right, but, I'll ding it a bit for returning "01 out of 1000" and "011 ..." notice it left a trailing 0.
@Rick - I had to change one > to gt, but it had the same issue as Michael.
@David - ok, flattery aside, so far this is the best, and I like the it used numberformat.
@Bob - yours worked well as well.
@JonH: Ha to add one more )... it returned it wrong off by a factor of 10... but interesting example there!
So being that I'm 3 days late (sorry guys again!) I'm going with David! David, please ping me via email with your preferred email for the Amazon gift cert, and thanks everyone!
Ray, I was taking 0.1 to mean 10%. 10 out of 100 is 10%, although it should have returned 1 out of 10 at that point. Strangely enough, .1 will return 1 out of 10. I assumed anything under 1 was to be treated as the actual percentage, ie .25 = 25% and 5.6 = 5.6%.
Makes sense - I hope I didn't come off too critical - I was just trying to judge this quickly as I had been so late in it!
No, I just didn't want to have my intent misinterpreted.