Difference with isValid("integer",value) and argument type="numeric" on Lucee and ACF

Hi,

I noticed that isValid("integer","63.") return true and <cfqueryparam cfsqltype="cf_sql_integer" value="63."> also works fine in a SQL query, but if I pass “63.” to a function with a numeric type argument, it generates an error. Be aware that the value is not 63, but 63. with a dot.

There is no error in recent versions of Adobe Coldfusion.

As of this writing, the ACF 2021 engine appears to be stuck on TryCF.com.

In fact, there is two differences with Lucee vs Adobe Coldfusion :

Point 1 - isValid(“integer”,“63.”)

  • For ACF, it’s not a valid integer
  • For Lucee, it’s a valid integer

Point 2 - function with argument type=“numeric”

  • For ACF, it doesn’t throw an error, so I’m guessing it’s considering that value as a numeric
  • For Lucee, it does throw an error, so I’m guessing it’s considering that it’s not a numeric value

Technically speaking, is “63.” supposed to be a valid integer?

And if Lucee considers “63.” to be a valid integer, he should also consider it to be a valid numeric value as well. An integer is a numeric value.

But overall, I feel that isValid(“integer”,“63.”) should return false on Lucee.

I would like to have your opinion on this.

Add a ZERO to your dot.
So 63.0 is a valid number
“63.” is a string
the decimal is a string character unless proceeded by a number.

If “63.” is a string, then why isValid(“integer”,“63.”) return true AND type=“numeric” generate an error? Is it just me seeing the inconsistency?

<cfif isValid("integer","63.")>
    <p>63. is a valid integer</p>
<cfelse>
    <p>63. is not a valid integer</p>
</cfif>

<cfif isValid("numeric","63.")>
    <p>63. is a valid numeric</p>
<cfelse>
    <p>63. is not a valid numeric</p>
</cfif>
  • Lucee return true for integer, but false for numeric.
  • ACF return false for integer, but true for numeric.

Take a look at this : TryCF.com

There are many little differences with ACF and lucee.

if you have any java programming skill, you can look at the source behind the tag and propose a patch, but if you look at this, the “.” seems to be part of several types.

From a logic side, nobody is going to write a check for “63.” written out its six three dollars PERIOD. The correct way would be Sixty three dollars and zero cents.

The problem I currently have is that my application expects an integer value as a parameter in the url, so I use the isValid("integer",...) function to validate that the passed value is indeed an integer. But since the user can modify the parameter to add a dot to it, the application crashes a bit further when that value is passed to a function that expects a numeric argument. As my knowledge in Java is limited and that quite honestly, all that exceeds my competences, I will have to create my own function of validation of integer.

I had one before, but I gradually replaced it with isValid("integer",...) thinking it would be more reliable than my own function.

I still have a hard time accepting that Lucee can consider that a value can be integer, but not numeric. I still understand that it’s more complex than it seems, a bit like validating a date.

In Lucee source code, in core\src\main\java\lucee\runtime\op\Decision.java for the function isValid, I found this :

if ("integer".equals(type)) return isInteger(value, false);

then this

public static boolean isInteger(Object value, boolean alsoBooleans) {
		if (!alsoBooleans && isBoolean(value)) return false;

		double dbl = Caster.toDoubleValue(value, false, Double.NaN);
		if (!Decision.isValid(dbl)) return false;
		int i = (int) dbl;
		return i == dbl;
	}

I’ll analyze this to fully understand what’s going on behind the hood, but I’d be surprised if I could improve on this without blowing out everything that depends on isInteger in Lucee.

I agree with all of this.

I had a look: https://trycf.com/gist/1fd1394ba0d3a387f0a7b4e66b1ac092/lucee5?theme=monokai

notAnInteger = "63."    

writeDump([
    "notAnInteger" = notAnInteger, // both: 63.
    "notAnInteger * 2" = notAnInteger * 2, // both 126
    "isNumeric(notAnInteger)" = isNumeric(notAnInteger), // Lucee: false, CF: true
    'isValid("integer", notAnInteger)' = isValid("integer", notAnInteger) // Lucee: true, CF false
])


function f(numeric x){
}

try {
    f(notAnInteger) // Lucee: error, CF: fine
    writeOutput("use in numeric function OK<br>") 
} catch (any e) {
    writeDump(e.message)
}

try {
    param name="notAnInteger" type="numeric"; // Lucee: error, CF: fine
    writeOutput("params to numeric OK<br>") 
} catch (any e) {
    writeDump(e.message)
}
try {
    param name="notAnInteger" type="integer";// Lucee: fine, CF: error
    writeOutput("params to integer OK<br>") 
} catch (any e) {
    writeDump(e.message)
}

CF has nailed it, Lucee needs to take a look at a venn diagram of “integer” and “numeric” and how the former circle is completely inside the latter one :wink:


Adam

1 Like

We have similar needs and “solved” by wrapping most code with val(). ¯_(ツ)_/¯

<cfif val(valueToTest)>
  ...
</cfif>
1 Like

I stopped using val a while ago when we were on Adobe ColdFusion. We had some issue with maths operations with val if a number was excessively long.

Exemple :
12345678901234567890 * 2 gave 2.4691358e+19.
ACF was doing the val on 2.4691358e+19 and return 2.4691358 only, not the e+19 part.

Look like Lucee doesn’t have this issue with val. Try it with the different engines.

Be careful of using val() in an if statement as well, because it might not always do what you expect, try this:

if (val("12whatever")) {
  writeOutput("yes, it's true");
}

Example on trycf: https://trycf.com/gist/905b056f600536ee9c016406b1e7c6b5/lucee5

If you look at the docs for val val Code Examples and CFML Documentation it states:

Converts numeric characters that occur at the beginning of a
string to a number. If conversion fails, returns zero.

Which makes sense, it takes 12whatever and returns 12

2 Likes

there’s a lot of historical baggage in our beloved cfml language dating back to the mid 90s…

it’s complicated because we all rely on third party code of varying age and quality

and cfml loves to check by casting according to a range of rules

Examples are ok, PRs with annotated test cases are fucking awesome

2 Likes

It’s true. Ha! (pun intended) - the code is only able to do what you’re asking for - and with limitations.

The 20 char “number” 12345678901234567890 mentioned has it’s own issues due to length …

val(12345678901234567890) is not equal to “12345678901234567890” no matter the CF engine.
Even working directly with java.math.BigInteger is going to give you fits if you multiply and doesn’t handle the dot on the end.

More baggage:
https://lucee.daemonite.io/t/incorrect-multiplication-result-for-large-numbers/11054

Seems like a special use-case where you are better off writing your own solution for your special app needs with numbers that long.

FWIW, val() is purely a string manipulation function (in Lucee, at least). All it’s doing is taking the first characters of the string until it reaches a non-number. This, of course, is exactly what it is documented to do. Turning a large number into scientific notation isn’t really a problem with val(), that’s just now the CF engine turns the large number into a string. If you wanted that to work, you’d need to do your math in a manner that output the full number as a string without allowing the CF engine to convert it t a string for you. I tried to get a proof of concept on that, but the annoying limitations in trycf.com prevents me from getting very far :confused:

Another solution would be is avoid decimal, on client do by times 100 on server side convert back to decimal divided by 100.

On the other hand, the initial concern I raised is that I want to validate what is submitted by the user. I want to make sure it’s a valid integer. I have no control over the value he will put in the URL param.

It’s either that we improve Lucee’s isInteger function, or I create my own integer validation function. Creating my own function still seems the easiest, as I have no idea what impact changing Lucee’s isInteger function might have on backwards compatibility.

They (users) will surely throw garbage at any app (accidental or otherwise). It’s up to you as to what you want to do with it.
One thought is to replace [^0-9] with nothing and ignore. Another is reject the request when the replaced len changes.

If we’re to be pedantic about integer, take the max of your current DB (signed or unsigned will change this) into account with your custom function.
From my initial testing, Java has some of the same issues as Lucee with the trailing dot as well as “long integers” losing data when being cast to an int. To me, that’s less a Lucee issue and more of a Java limitation for the type.