Menu ▾ ▴

#43 6.4.2 `+`, `*` and `-` wrapped out of the integer range

CLIPS
open
nobody
None
5
2 days ago
2 days ago
Batch Job 1
No

+, * and - wrapped out of the integer range

What happened. An integer sum, product or difference that did not fit in a CLIPS integer came back wrapped, with nothing reported.

(printout t "add: " (+ 9223372036854775807 1) crlf)
(printout t "mul: " (* 9223372036854775807 2) crlf)
(printout t "sub: " (- 0 -9223372036854775808) crlf)

printed

add: -9223372036854775808
mul: -2
sub: -9223372036854775808

Adding 1 gave a smaller number than the one added to. Doubling gave −2. Subtracting a negative number from zero gave a negative number.

Why it is a fix and not a preference. Section 12.5.1 of the Basic Programming Guide says + "returns the sum of its arguments"; 12.5.3 says * "returns the product of its arguments"; 12.5.2 says - "returns the value of the first argument minus the sum of all subsequent arguments". −9223372036854775808 is not the sum of 9223372036854775807 and 1.

Section 2.3.1 says an optional sign followed by digits is stored as an integer and that CLIPS represents an integer as a C long long, so these operands are values a program can write and the scanner will store.

The same directory already treats one non-representable integer result as an evaluation error: DivFunction in bmathfun.c rejects LLONG_MIN divided by −1 through ArgumentOverUnderflowErrorMessage, and ModFunction in emathfun.c rejects the same pair on its integer path.

Where it came from. All three functions keep a long long running total and update it while every argument so far has been an integer. AdditionFunction:

         if (CVIsType(&theArg,INTEGER_BIT))
           { ltotal += theArg.integerValue->contents; }

MultiplicationFunction has ltotal *= and SubtractionFunction has ltotal -= in the same position. An update whose mathematical result is outside long long has no result in C, so the wrapped value is whatever the host's arithmetic produced; the function then handed it to CreateInteger as the answer.

The change. Three static predicates at the top of bmathfun.c — AdditionOverflows, SubtractionOverflows, MultiplicationOverflows — report from the operands whether the result is a long long. Each of the three functions asks before its update and, when the answer is yes, reports through ArgumentOverUnderflowErrorMessage, sets the evaluation error, and returns the operation's identity, as DivFunction returns 1.

The test has to be made from the operands: computing the result first and inspecting it is the thing that has no result to inspect. The sum test subtracts and the difference test adds, in each case on operands of opposite sign, so neither test can leave the range itself. The product test settles 0, 1 and −1 first, which leaves every division in it with a divisor of magnitude 2 or more — LLONG_MIN / -1 is the one quotient outside long long, and it is the case DivFunction reports.

What was checked. Thirteen calls that must report: the three above, the two underflowing counterparts, (* -9223372036854775808 -1), and one step outside each of the four sign combinations the product test distinguishes, plus the two products landing one step past 2⁶³. All thirteen report, each naming its own function, and no wrapped value is printed for any of them. (+ 9223372036854775807 1 -1) has the overflow at the second of three arguments, so a function that carried on would reach 9223372036854775807 again and print it; it reports instead. A call after the reports still evaluates.

Then the results that fit and must still come back, which is where a test from the operands can go wrong by one: the four examples the guide gives; the sum that lands exactly on 9223372036854775807; the difference and the two products that land exactly on −9223372036854775808; −1 times the largest integer; and one step inside each of the four product sign combinations, 3074457345618258602 against ±3. None of them reports. The float paths, which carry the total into double on the first float argument, are untouched and still return 9.22337203685478e+18 and 1.84467440737096e+19 for the calls above with a float operand.

The reason to care about this one: a wrapped total is a plausible-looking integer, and in a program that sums or multiplies a column it propagates into everything downstream before anything looks wrong.

Present in 7.0? Not checked.

2 Attachments

Discussion

Anonymous
Anonymous

Add attachments
Cancel