Reputation: 147
I was looking for an extended checking method covering various valid numbers
, passed as value
, string
or var reference
to a method, but most solutions resort to basic cases like plain int or float numbers at most, or are dedicated to formatted date strings (which is not scope of my requirements).
So I'm posting a new question for a solution, which verifies, whether a passed argument is a common decimal/int
, floating-point (delimited by either .
or ,
, hex
(beginning with 0x
, containing [0-9a-fA-F]
), octal
(beginning with 0
), scientific notation/exponential
(in the form 52e-14
or -2*10^8
), or a custom base number (as <base>#n
with base range from 2 to 64, allowing number digits [0-9a-fA-F]
).
Upvotes: -1
Views: 70
Reputation: 147
Here's my solution, including extensive testing cases, which verifies, whether a passed argument is a common decimal/int, floating-point (delimited by either . or ,, hex (beginning with 0x, containing [0-9a-fA-F]), octal (beginning with 0), scientific notation/exponential (in the form 52e-14 or -2*10^8), or a custom base number (as #n with base range from 2 to 64, allowing number digits [0-9a-fA-F]).
The elapsed time for 1000 iterations is about 6-7x slower than the fastest isint_Case() method from HERE, but covers and combines many more potentially encountered types of numbers or number strings, as described in GNU.org - Shell arithmetics.
The method numTest(), uses case + extended pattern matching (requiring shopt -s extglob) and returns 0 on success, and 1 on test failures. If required, the commented-out printf statements can pass the results to stdout, e. g. for successive result capturing into a var (which should work for just the values, due to the redirection of informative output to /dev/tty). Also added is a commented-out built-in RegEx-based solution (which is ~4s slower à 1000):
#! /bin/bash
shopt -s extglob
# Function accepts int (decimal), float, scientific (exponential), octal and hex numbers passed directly as values, expandable vars or var name references
numTest() {
# Check for and link val to var name reference
if declare -p "$1" &>/dev/null; then
local -n input="$1" #&>/dev/null
else
local var="$1"
local -n input=var
fi
## CASE PATTERN CHECK SOLUTION (~18s for 1000)
local isNum
case ${input#[-+]} in
# Octal, hex, decimal / scientific/exponential, as parsable by awk '{ print +$1, +$2 }' <<<'12345678e-6 -323*10^34'
@(+([0-9])|0[xX]+([0-9aAbBcCdDeEfF])|+([0-9])?(@([eE]|'^'|'*10^')?([+-])+([0-9]))) )
# Uncomment to print succeeded test values
#printf '%s' "IS INT: " >/dev/tty
#printf '%s\n' "$input"
return 0 ;;
# Explicit base 2 - 64
+([0-9])\#+([0-9aAbBcCdDeEfF]) )
local -i base=${input%%#*}
if [[ $base -ge 2 && $base -le 64 ]]; then
# Uncomment to print succeeded test values
#printf '%s' "IS INT: " >/dev/tty
#printf '%s\n' "$input"
return 0
fi ;;
# Float / scientific/exponential, as parsable by awk '{ print +$1, +$2 }' <<<'0.0314159e2 -323*10^34'
@(*([0-9])[.,]+([0-9])?(@([eE]|'^'|'*10^')?([+-])+([0-9])))|'NaN' )
# Uncomment to print succeeded test values
#printf '%s' "IS FLOAT: " >/dev/tty
#printf '%s\n' "$input"
return 0
;;
esac
# ## REGEX CHECK SOLUTION (~22s, ~4s slower for 1000)
# if [[ $input =~ ^[+-]?([0-9]+|0[xX][0-9aAbBcCdDeEfF]+|[0-9]+(([eE]|'^'|'*10^')[+-]?[0-9]+)?)$ ]]; then
## printf '%s' "IS INT: " >/dev/tty
## printf '%s\n' "$input"
# return 0
# elif [[ $input =~ ^[+-]?[0-9]*[.,][0-9]+(([eE]|'^'|'*10^')[+-]?[0-9]+)?$ || $input == 'NaN' ]]; then
## printf '%s' "IS FLOAT: " >/dev/tty
## printf '%s\n' "$input"
# return 0
# fi
# Uncomment to print failed test values
#printf '%s' "TEST_FAIL: " >/dev/tty
#printf '%s\n' "$input"
return 1
}
# Set to 1 to print test section captions
declare print=
## Declare test vars and array of test values
declare -i int_min
(( int_min=(1<<63) ))
declare -i int_max=$((int_min - 1))
## Success test var candidates
# Success test vars octal
declare -i convertedOctal=073
declare -i convertedOctal2=-073
declare -i convertedOctal3=+073
# Success test vars hex
declare -ai successTestsHex=( x1450 0x9532 0xA0b32 0x0 -0x0 -0X0 +0x0 +0X0 +0x08842 +0X08842 -0x05431 -0X05431 )
declare -a successTestsHexStr=( 'x1450' '0x9532' '0xA0b32' '0x0' '-0x0' '-0X0' '+0x0' '+0X0' '+0x08842' '+0X08842' '-0x05431' '-0X05431' )
# "success" - parsed as null at var assignment -> 0
declare -ai successTestsPseudoHex=( -x0x0 -x0x0 +x0x0 )
# Auto-converted ints to hex
declare -ai successTestsConvertedOctal=( $convertedOctal $convertedOctal2 $convertedOctal3 )
# Success test vars int
declare -ai successTestsInt=( 0 -0 +0 $int_max -$int_max +$int_max $int_min -$int_min +$int_min 1 -1 +1 9015 -9015 +9015 )
# Exponents (only possible as string)
declare -a successTestsIntStr=( '0' '-0' '+0' "$int_max" "-$int_max" "+$int_max" "$int_min" )
declare -a successTestsIntBaseStr=( '2#0' '2#1' '16#aF0' '16#AF0' '32#a' '32#A' '33#a' '33#A' '64#f' '64#F' '10#100' )
declare -a successTestsOctalStr=( '073' '-073' '+073' )
declare -a successTestsSciStr=( '0e0' '0E0' '087e-32' '087E-32' '-087e32' '-087E32' '-22048e-16' '-22048E-16' '+100e050' '+100E050' '+7e-25' '+7E-25' '+000e+00' '+000E+00' '-853e1' '-853E1' '-323^34' '-1^-3' '+166^8' '+89^-16' '-323*10^34' '-1*10^-3' '+166*10^8' '+89*10^-16' )
# Success test vars float
declare -a successTestsFloatStr=( 'NaN' 0.0 +0.0 0,0 -0.0 -0,0 1.34 +1.34 1,34 -1.34 -1,34 .494 ,494 +,494 -.494 -,494 '571.0014' '571,0014' '-571.0014' '-571,0014' )
declare -a successTestsSciFloatStr=( '13.62e06' '13.62E06' '-13.62e06' '-13.62E06' '+13.62e06' '+13.62E06' '-3.23^34' '-0.1^-3' '+16.6^8' '+8.9^-16' '-3,23*10^34' '-0,1*10^-3' '+16,6*10^8' '+8,9*10^-16' )
## Fail tests
# Fail test vars misc
declare -a failTestsMiscStr=( '+' '-' ' ' '_' 'E' 'e' 'a' 'A' 'x' 'X' '.' '-.' ',' '-,' )
declare empty=''
# Fail test vars hex
declare -a failTestsHexStr=( 'x0x0' '-x0x0' '+x0x0' '0x01x' '-0x01x' '+0x42x' '0x' 'x0' )
declare failHexStr="x0x0"
declare failHexStr1="-x0x0"
declare failHexStr2="+x0x0"
# Fail test vars int
declare -a failTestsIntStr=( "-$int_min" "+$int_min" '#1' '2_' 'i3x' ' 1 ' '22 ' ' 333' ' -1 ' '-02 ' ' -033 ' '- 0444' '+0-535' '+-5350' '-+5350' '--5350' '++5350' '0-' '0+' '0ate234' '1993-' '78844355+' )
declare -a failTestsIntBaseStr=( '0#0' '1#1' '0#1' '2#-1' '65#0' '65#a' '2#g' '2#G' )
# Fail test vars float
declare -a failTestsFloatStr=( 'gdXm0.18#' 'gdXm0,18#' '.0-' '.0+' '7.01-' '238.446+' '0.' '0,' '.0.' ',0,' 'df-93.87#' 'df-93,87#' ' -2.057,' ' 09.002 ' '-09.002 ' '+09,002 ' '1.01 ' '-1,01 ' ' +2.02' ' .2,02' ' -33.66 ' '-33,66 ' ' -5 .4 ' '-5 ,4' ' -0. 06' '-0, 06 ' 'e13.62e06' '13E.06' '-13.62Ee06' '-13.62eE06' '+13.62ee06' '+13.62EE06' )
## Run tests and measure elapsed time
declare start=$EPOCHREALTIME
[[ $print ]] && echo -e "\nBEGIN SUCCESS TESTS DIRECT NUMBER INPUT"
for (( i=0; i<100; i++)); do
# Auto-converted octal -> decimal
numTest 0160
numTest -0160
numTest +0160
# Auto-converted (0x) hex -> decimal
numTest 0x3932
numTest 0xaF3e2
numTest -0x3932
numTest +0x3932
numTest 0
numTest -0
numTest +0
numTest $int_max
numTest -$int_max
numTest +$int_max
numTest $int_min
numTest 94316667
numTest -94316667
numTest +94316667
numTest 0e0
numTest 0E0
numTest 087e-32
numTest 087E-32
numTest -087e32
numTest -087E32
numTest -22048e-16
numTest -22048E-16
numTest +100e050
numTest +100E050
numTest +7e-25
numTest +7E-25
numTest +000e+00
numTest +000E+00
numTest -853e1
numTest -853E1
numTest '0'
numTest '-0'
numTest '+0'
numTest "$int_max"
numTest "-$int_max"
numTest "+$int_max"
numTest "$int_min"
numTest '99'
numTest '-2377'
numTest '+356'
numTest NaN
numTest 0.0
numTest -0.0
numTest +0.0
numTest 0,0
numTest -0,0
numTest +0,0
numTest .3930
numTest -.3930
numTest +.3930
numTest ,3930
numTest -,3930
numTest +,3930
numTest 261087.0532
numTest -261087.0532
numTest +261087.0532
numTest 261087,0532
numTest -261087,0532
numTest +261087,0532
# Auto-converted hex 0x
[[ $print ]] && echo -e "\nBEGIN SUCCESS TESTS HEX"
_IFS="$IFS"; IFS=$'\n'
for param in "${successTestsHex[@]}"; do
numTest param
done
for param in "${successTestsPseudoHex[@]}"; do
numTest param
done
[[ $print ]] && echo -e "\nBEGIN SUCCESS TESTS CONVERTED INT HEX"
for param in "${successTestsConvertedOctal[@]}"; do
numTest param
done
[[ $print ]] && echo -e "\nBEGIN SUCCESS TESTS INT"
for param in "${successTestsInt[@]}"; do
numTest param
done
[[ $print ]] && echo -e "\nBEGIN SUCCESS TESTS INT STR"
for param in "${successTestsIntStr[@]}"; do
numTest param
done
[[ $print ]] && echo -e "\nBEGIN SUCCESS TESTS INT BASE STR"
for param in "${successTestsIntBaseStr[@]}"; do
numTest param
done
[[ $print ]] && echo -e "\nBEGIN SUCCESS TESTS OCTAL STR"
for param in "${successTestsOctalStr[@]}"; do
numTest param
done
[[ $print ]] && echo -e "\nBEGIN SUCCESS TESTS SCI INT STR"
for param in "${successTestsSciStr[@]}"; do
numTest param
done
[[ $print ]] && echo -e "\nBEGIN SUCCESS TESTS FLOAT"
for param in "${successTestsFloatStr[@]}"; do
numTest param
done
[[ $print ]] && echo -e "\nBEGIN SUCCESS TESTS SCI FLOAT STR"
for param in "${successTestsSciFloatStr[@]}"; do
numTest param
done
[[ $print ]] && echo -e "\nBEGIN FAIL TESTS MISC"
numTest empty
for param in "${failTestsMiscStr[@]}"; do
numTest param
done
[[ $print ]] && echo -e "\nBEGIN FAIL TESTS HEX"
numTest $failHexStr
numTest failHexStr
numTest $failHexStr1
numTest failHexStr1
numTest $failHexStr2
numTest failHexStr2
for param in "${failTestsHexStr[@]}"; do
numTest param
done
[[ $print ]] && echo -e "\nBEGIN FAIL TESTS INT"
for param in "${failTestsIntStr[@]}"; do
numTest param
done
[[ $print ]] && echo -e "\nBEGIN FAIL TESTS INT BASE STR"
for param in "${failTestsIntBaseStr[@]}"; do
numTest param
done
[[ $print ]] && echo -e "\nBEGIN FAIL TESTS FLOAT"
for param in "${failTestsFloatStr[@]}"; do
numTest param
done
IFS="$_IFS"
[[ $print ]] && echo -e "\nBEGIN FAIL TESTS DIRECT NUMBER INPUT"
numTest
numTest ''
numTest '+'
numTest '+'
numTest '-'
numTest ' '
numTest '_'
numTest 'E'
numTest 'e'
numTest 'a'
numTest 'A'
numTest 'x'
numTest 'X'
numTest '0x'
numTest 'x0'
numTest 0x
numTest x0
numTest 0-
numTest 0+
numTest '0-'
numTest '0+'
numTest -$int_min
numTest +$int_min
numTest "-$int_min"
numTest "+$int_min"
numTest 0ate234
numTest '1993-'
numTest 1993-
numTest '78844355+'
numTest 78844355+
numTest .0-
numTest .0+
numTest 7.01-
numTest 238.446+
done
declare stop=$EPOCHREALTIME
declare startMsStr="${start##*.}"; "${startMsStr%%[!0]*}" 2>/dev/null
startMsStr="${startMsStr#*$_}"
declare stopMsStr="${stop##*.}"; "${stopMsStr%%[!0]*}" 2>/dev/null
stopMsStr="${stopMsStr#*$_}"
declare -i elapsedSeconds=$(( (${stop//.} - ${start//.}) / 1000000 ))
#declare -i elapsedMs=$( [[ $stopMsStr -gt $startMsStr ]] && echo $(( ( stopMsStr - startMsStr ) / 1000 )) || echo $(( ( startMsStr - stopMsStr ) / 1000 )) )
declare -i elapsedMs
if [[ $stopMsStr -gt $startMsStr ]]; then
elapsedMs=$(( ( stopMsStr - startMsStr ) / 1000 ))
else
elapsedMs=$(( ( startMsStr - stopMsStr ) / 1000 ))
fi
echo -e "ELAPSED TIME: $elapsedSeconds.${elapsedMs}s"
shopt -u extglob
The provided tests should cover most checked success and fail cases, applying both value-passing and reference-passing.
There's also fail cases to failTestsHexStr() for wrong hex STR values, for which at INT var assignment time pseudo-'success' occurs and successTestsPseudoHex(), demonstrating pseudo-success result at var assignment time, due to preliminary fail-to-null -> 0 evaluation.
A small glitch occurs for input of _, when printing the results, where not the value, but the ref name is printed instead, but fail reporting is Ok.
Upvotes: 0