What happened. Asserting a fact with a negative float in it, or one whose magnitude is at least 2⁶⁴, performed a conversion the C standard leaves undefined.
(assert (data -1.0))
Built with -fsanitize=float-cast-overflow, that reports multifld.c:800: runtime error: -1 is outside the range of representable values of type 'unsigned long'.
The trigger is a one-line CLIPS program. loop-for-count at the extreme of the integer type, random over a span of 2⁶⁴, and interning the most negative integer are all reachable but contrived; (assert (data -1.0)) is not. The conversion is also in a hash function, where the standard permitting anything includes permitting a different bucket on a different compiler — and a hash that disagrees with itself between an assert and a retract would lose a fact rather than answer oddly.
Where it came from. HashMultifield in clips_core_source_642/core/multifld.c:
case FLOAT_TYPE:
fis.liv = 0;
fis.fv = fieldPtr[i].floatValue->contents;
count += (fis.liv * (i + 29)) +
(unsigned long) fieldPtr[i].floatValue->contents;
break;
The first term reads the double's bits through a union, which is well defined and is what the rest of CLIPS does. The second converts its value, which is undefined whenever the integral part will not fit — every negative float, and every magnitude of 2⁶⁴ or more.
An ordered fact keeps every field after the relation name in one multifield slot, and HashFact in facthsh.c hashes that slot, so (assert (data -1.0)) reaches it. HandleFactDuplication and RemoveHashedFact both call HashFact, so asserting and retracting both do.
What was checked. The hash exists to answer whether a proposition is already stored, so that is what the tests ask, over floats of every shape: −1.0, 10²⁰, −10²⁰, 0.0, −0.0, 10⁻³⁰⁰ and an infinity. Asserted twice each and counted. The same through an ordered fact, which is the shape that reaches this code. Two orderings of the same pair of floats, which must stay two facts. Retracted and asserted again, so the hash is computed on the way out as well as in. And a thousand distinct negative floats, which must remain a thousand facts rather than collapsing into fewer.
Every count is identical before and after.
Anonymous
Present in 7.0? Not checked.