UPDATE (July 15, 2009): This "bug" still exists in Adobe ColdFusion 9 beta 1. It also seems to behave the same in OpenBD 1.1. This bug does not seem to exist in Railo (I can't reproduce any similar problems in Railo 3.0 or Railo 3.1). For example, round(4.0005*1000) outputs 4000 from both AdobeCF and OpenBD while Railo outputs the expected 4001. There may very well be other scenarios where a floating point representation causes similar unexpected results in Railo as well, but I have not been able to find it yet (nor have I performed an absolutely thorough test, maybe soon if time permits).
Have you ever used a CFML trick like this to round a number to the nearest hundredth?
I have, and I never had a problem, until...
valueA = 4000.5;
valueB = 4.0005*1000;
roundedA = round(valueA);
roundedB = round(valueB);
You'd be perfectly sane to expect the first two variables to output as 4000.5 and the second two (rounded) to output 4001. Unfortunately, roundedB is output as 4000, not 4001!
This definitely appears to be a bug, but I do have a rather simple workaround...
It appears that, despite the output of both valueA and valueB being the same, the ColdFusion engine must be storing references to two different Java values (possibly a different Java type at that point?), which do not represent the same value.
The comments on the Adobe livedocs page for the CF8 round() function (http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_m-r_40.html) suggest that the CF engineers are simply using round() from java.lang.Math, and sure enough, I get the same output as above with this code:
valueA = 4000.5;
valueB = 4.0005*1000;
roundedA = createObject('java', 'java.lang.Math').round(valueA);
roundedB = createObject('java', 'java.lang.Math').round(valueB);
When I first discovered this issue, I assumed it was a fluke and asked my buddy Brian Hegeman to test it out on his laptop (thanks again, B). Sure enough, same results. So we both got to messing around with javaCast() and along the way discovered that a javaCast to double produces the same (incorrect) results for the above example, but javaCast to float gives us what we want:
#round( javaCast('float', 4.0005*1000) )#
So we seem to have a workaround. I even wrote a script to loop over a whole lot of integers to test and it seems solid, but a little part of me wondered if there may be other similar or new issues brought to light by the javaCast to float. The solution I settled on is to javaCast to string inside the round() function. This may seem odd, but this essentially "erases" any gunked up typing or odd references stored under the hood -- I'm seeing the proper output before calling a round() function, so why not force ColdFusion to store a reference to exactly what is output, then carry on? ColdFusion allows for this dynamic typing, so it's no problem to pass a string as a numeric, so long as it can be represented as a numeric.
With that, I whipped up the following roundBetter() UDF to work around this bug. While I was at it I also allowed for a second (optional) argument to allow me to specify a scale (decimal place) to round to, rather than rounding only to a whole number.
<cfargument name="toRound" type="numeric" required="true" />
<cfargument name="scale" type="numeric" required="false" default="0"
hint="Scale to round to (number of decimal places)." />
var result = arguments.toRound;
var scaleMultiplier = 10^arguments.scale;
result = result * scaleMultiplier;
result = round(javaCast('string', result) );
result = javaCast('string', result/scaleMultiplier);
I'll do my best to report this to Adobe next, though I don't believe they've made their bug tracker public just yet, so I'll probably have to use the generic Feature Request/Bug Report form (http://www.adobe.com/cfusion/mmform/index.cfm?name=wishform).
Has anyone else come across this bug? I did a good bit of searching and didn't turn up any reports, but I'm curious to know if others have stumbled upon this.