Array.Sort() Operator Has Trouble With Return Values Between Zero And One In Lucee CFML 5.3.7.47
As it is documented, the Array.sort()
method (and arraySort()
function), when given an operator to execute, are supposed to return the values -1
, 0
, and 1
when comparing two values within the collection. Documentation aside, the .sort()
method is actually much more flexible than that, allowing for almost any number to be returned. This is why we can implement our sort operator using math. However, playing fast-and-loose with the return value can result in funky edge-cases, such as accidentally returning a value that falls outside of the INT
space. Last week, I ran into yet another such edge-case. Apparently, the Array.sort()
operation does not like handling decimal values between 0
and 1
, such as 0.345
in Lucee CFML 5.3.7.47.
To this in action, all we have to do is try using math to sort a bunch of decimal values between 0
and 1
:
<cfscript>
// NOTE: All of these values are less that "1.0" apart.
values = [
0.1,
0.8,
0.5,
0.2,
0.7,
0.4
];
values.sort(
( a, b ) => {
return( a - b ); // Sort ascending.
}
);
for ( value in values ) {
echo( value & "<br />" );
}
</cfscript>
Note that I am attempting to use "math", (a - b)
, to power the sort operation. However, when we run this, we get the following output:
0.1
0.8
0.5
0.2
0.7
0.4
As you can see, the values come back in the original order that the array was defined - not in a sorted order. I assume that this is because any value between -1
and 1
- excluding -1
and 1
- tell the underlying algorithm that the values are equal, therefor leaving them in-place.
In my original code, I wasn't actually sorting numbers - I was sorting dates. And, as you may know, dates can be represented as numbers in ColdFusion. You can see this by taking a date/time value and multiplying it by 1
:
echo( now() * 1 ); // Outputs 44215.279176435186
This numeric value is the factional days since the "ColdFusion zero day", which is Saturday, December 30, 1899
. Because of this behavior, you can perform date math on date/time objects in ColdFusion by simply adding and subtracting two dates.
In what was an approach that ended-up being "too clever", I was attempting to use this date-math behavior to sort an array of date/time values. Notice that the date/times values in my input array are all less than 24-hours apart:
<cfscript>
// NOTE: All of these dates are LESS than 24-hours apart, but are on different days.
dates = [
parseDateTime( "2019-11-27 10:54:01" ),
parseDateTime( "2019-11-26 15:53:17" ),
parseDateTime( "2019-11-27 12:40:29" ),
parseDateTime( "2019-11-26 19:50:17" ),
parseDateTime( "2019-11-27 02:44:16" )
];
// Attempting to use "date math" to sort the dates. When performing raw math on
// date/time values, ColdFusion implicitly converts them to a numeric representation
// of fractional days (this is NOT epoch time). As such, I was ATTEMPTING to return
// the sort indication by simply subtracting one date from another, leaving us with a
// value that was either less-than-zero, zero, or more-than-zero.
dates.sort(
( a, b ) => {
return( b - a ); // Sort descending.
}
);
for ( value in dates ) {
echo( "#value# → as number: #( value * 1 )# <br />" );
}
</cfscript>
Since my input dates were all less than 24-hours apart, it means that all of the fractional day differences (date math) are less than 1
. Which, as we saw above, means that ColdFusion won't see them as "different" in the sorting algorithm. As such, when we run this Lucee CFML code, we get the following output:
{ts '2019-11-27 10:54:01'} → as number: 43796.45417824074
{ts '2019-11-26 15:53:17'} → as number: 43795.66200231481
{ts '2019-11-27 12:40:29'} → as number: 43796.52811342593
{ts '2019-11-26 19:50:17'} → as number: 43795.826585648145
{ts '2019-11-27 02:44:16'} → as number: 43796.114074074074
As you can see - just as with our first demo - the values are left in-place. Essentially, the whole sort was a no-op (No Operation) since the differences between all the values were less than 1
.
To "fix" this behavior, all I had to do was revert back to using the "documented" behaviors of the Array.sort()
function:
<cfscript>
// Instead of trying to do anything fancy, we're just going to use the SORT() method
// the way it was always documented - by returning -1, 0, and 1.
dates.sort(
( a, b ) => {
if ( a == b ) {
return( 0 );
}
return( ( a < b ) ? 1 : -1 );
}
);
</cfscript>
Obviously, all of this "trouble" comes from an attempt to push the limits of what the .sort()
method can do. If you just use the method as documented, you'll never run into any of these issues. But, it's nice to know where the limits are in Lucee CFML 5.3.7.47.
Want to use code from this post? Check out the license.
Reader Comments