What happened. A numeral of digits too large for a CLIPS integer came back from read-number as the largest CLIPS integer, with nothing said.
(printout t "v1 [" (read-number) "]" crlf)
(printout t "v2 [" (read-number) "]" crlf)
fed 999999999999999999999 and -999999999999999999999 printed
v1 [9223372036854775807]
v2 [-9223372036854775808]
Why it is a fix and not a preference. Section 12.4.11 of the Basic Programming Guide says that "if a number is successfully parsed, the read-number function will return either an integer (if the number contained just a sign and digits) or a float", and that "if errors are encountered while reading, the symbol FALSE is returned". 9223372036854775807 is not the number that was read.
CLIPS already classifies this numeral as an error. ScanNumber in scanner.c sets errno to 0 around the same strtoll call and warns when it comes back set; Appendix E documents that warning as "[SCANNER1] WARNING: Over or underflow of long long integer. This warning occurs when an integer is outside of the range of values that can be represented in the C long long integer type." Section 12.3.11 shows it in a return value:
CLIPS> (check-syntax "(defrule bad (number 40000000000000000000) =>)")
(FALSE "[SCANNER1] WARNING: Over or underflow of long long integer.
")
read-number on the same numeral said nothing and returned the clamped integer.
Where it came from. ReadNumber in iofun.c calls strtoll and accepts the result on the position of the terminating character alone:
theLong = strtoll(inputString,&charPtr,10);
if ((charPtr != inputString) &&
(isspace(*charPtr) || (*charPtr == '\0')))
{
theToken->tknType = INTEGER_TOKEN;
theToken->value = CreateInteger(theEnv,theLong);
strtoll returns LLONG_MAX or LLONG_MIN for a numeral outside the type and sets ERANGE, and the terminating character is the string's null either way, so the accept path ran. ReadNumber neither set errno to 0 beforehand nor read it afterwards.
The change. ReadNumber sets errno to 0 before the call, as ScanNumber does, and on ERANGE takes the FALSE return already there for a numeral that will not parse — the symbol FALSE with the error value set to READ_ERROR — after warning through ArgumentOverUnderflowErrorMessage in its warning form.
The float branch below is left as it is. ScanNumber converts a float with atof and inspects nothing, so a source literal of 1e400 is an infinity everywhere in CLIPS: (printout t 1e400 crlf) prints inf.0 and so does (string-to-field "1e400"). read-number returning an infinity for the same text agrees with the rest of the implementation and with the manual, which documents no overflow condition for floats. The surprise there is a surprise.
What was checked. Five numerals outside long long: 999999999999999999999 and its negative, 40000000000000000000 from the section 12.3.11 example, and 9223372036854775808 and -9223372036854775809, which are one step past each end. All five warn and return FALSE, and none returns a number. The two clamped values appear in the output exactly twice — for the two reads below that are supposed to produce them.
Then the numerals that must still read: 9223372036854775807 and -9223372036854775808, the two ends of the range exactly, and the three examples in section 12.4.11, 34, 34.0 and 8x8.
Then the error value: (get-error) is FALSE before the read, READ_ERROR after the out-of-range read, and FALSE again after a read that succeeds, so a FALSE returned as a failure is distinguishable from a FALSE that was the value.
The numerals arrive on standard input, because read-number with no logical name reads from stdin; run-fix-tests.sh pairs each script with the file it is to read instead of with /dev/null.
Present in 7.0? Not checked.
Anonymous