IsNumeric() Broken? Only up to a point.

Posted by Phil Factor on Simple Talk See other posts from Simple Talk or by Phil Factor
Published on Thu, 13 Jan 2011 06:34:00 GMT Indexed on 2011/01/13 13:57 UTC
Read the original article Hit count: 189

Filed under:
In SQL Server, probably the best-known 'broken' function is poor ISNUMERIC() . The documentation says
'ISNUMERIC returns 1 when the input expression evaluates to a valid numeric data type; otherwise it returns 0. ISNUMERIC returns 1 for some characters that are not numbers, such as plus (+), minus (-), and valid currency symbols such as the dollar sign ($).'
Although it will take numeric data types (No, I don't understand why either), its main use is supposed to be to test strings to make sure that you can convert them to whatever numeric datatype you are using (int, numeric, bigint, money, smallint, smallmoney, tinyint, float, decimal, or real). It wouldn't actually be of much use anyway, since each datatype has different rules. You actually need a RegEx to do a reasonably safe check. The other snag is that the IsNumeric() function  is a bit broken.
SELECT ISNUMERIC(',')
This cheerfully returns 1, since it believes that a comma is a currency symbol (not a thousands-separator) and you meant to say 0, in this strange currency.  However,
SELECT ISNUMERIC(N'£')
isn't recognized as currency.  '+' and  '-' is seen to be numeric, which is stretching it a bit. You'll see that what it allows isn't really broken except that it doesn't recognize Unicode currency symbols: It just tells you that one numeric type is likely to accept the string if you do an explicit conversion to it using the string. Both these work fine, so poor IsNumeric has to follow suit.
SELECT  CAST('0E0' AS FLOAT)
SELECT  CAST (',' AS MONEY)
but it is harder to predict which data type will accept a '+' sign.
SELECT  CAST ('+' AS money) --0.00

SELECT  CAST ('+' AS INT)   --0

SELECT  CAST ('+' AS numeric)
/* Msg 8115, Level 16, State 6, Line 4
Arithmetic overflow error converting varchar to data type numeric.*/

SELECT  CAST ('+' AS FLOAT)
/*Msg 8114, Level 16, State 5, Line 5
Error converting data type varchar to float.
*/>
So we can begin to say that the maybe IsNumeric isn't really broken, but is answering a silly question 'Is there some numeric datatype to which i can convert this string? Almost, but not quite. The bug is that it doesn't understand Unicode currency characters such as the euro or franc which are actually valid when used in the CAST function. (perhaps they're delaying fixing the euro bug just in case it isn't necessary).
SELECT ISNUMERIC (N'?23.67') --0
SELECT  CAST (N'?23.67' AS money) --23.67
SELECT ISNUMERIC (N'£100.20') --1
SELECT  CAST (N'£100.20' AS money) --100.20

Also the CAST function itself is quirky in that it cannot convert perfectly reasonable string-representations of integers into integers
SELECT ISNUMERIC('200,000')       --1
SELECT  CAST ('200,000' AS INT)   --0
/*Msg 245, Level 16, State 1, Line 2
Conversion failed when converting the varchar value '200,000' to data type int.
*/
 
A more sensible question is 'Is this an integer or decimal number'. This cuts out a lot of the apparent quirkiness. We do this by the '+E0' trick. If we want to include floats in the check, we'll need to make it a bit more complicated. Here is a small test-rig.
SELECT  PossibleNumber, 
        
ISNUMERIC(CAST(PossibleNumber AS NVARCHAR(20)) + 'E+00') AS Hack,
        
ISNUMERIC (PossibleNumber + CASE WHEN PossibleNumber LIKE '%E%'
                                        
THEN '' ELSE 'E+00' END) AS Hackier,
        
ISNUMERIC(PossibleNumber) AS RawIsNumeric
FROM    (SELECT CAST(',' AS NVARCHAR(10)) AS PossibleNumber
        
UNION SELECT '£' UNION SELECT '.'
        
UNION SELECT '56' UNION SELECT '456.67890'
        
UNION SELECT '0E0' UNION SELECT '-'
        
UNION SELECT '-' UNION SELECT '.'
        
UNION  SELECT N'?' UNION SELECT N'¢'
       
UNION  SELECT N'?' UNION SELECT N'?34.56'
         UNION SELECT '-345' UNION SELECT '3.332228E+09')
AS examples
Which gives the result ...
PossibleNumber Hack        Hackier     RawIsNumeric
-------------- ----------- ----------- ------------
? 0 0 0
- 0 0 1
, 0 0 1
. 0 0 1
¢ 0 0 1
£ 0 0 1
? 0 0 0
?34.56 0 0 0
0E0 0 1 1
3.332228E+09 0 1 1
-345 1 1 1
456.67890 1 1 1
56 1 1 1
I suspect that this is as far as you'll get before you abandon IsNumeric in favour of a regex. You can only get part of the way with the LIKE wildcards, because you cannot specify quantifiers. You'll need full-blown Regex strings like these ..
[-+]?\b[0-9]+(\.[0-9]+)?\b #INT or REAL
[-+]?\b[0-9]{1,3}\b #TINYINT
[-+]?\b[0-9]{1,5}\b #SMALLINT

.. but you'll get even these to fail to catch numbers out of range.
So is IsNumeric() an out and out rogue function? Not really, I'd say, but then it would need a damned good lawyer.

© Simple Talk or respective owner