+;;; Return an integer consisting of ones in its N least significant
+;;; bit positions and zeroes in all others. If N is negative, return 0.
+(declaim (inline ones))
+(defun ones (n)
+ (lognot (ash -1 n)))
+
+;;; The functions LOG{AND,IOR,XOR}-DERIVE-UNSIGNED-BOUNDS below use
+;;; algorithms derived from those in the chapter "Propagating Bounds
+;;; through Logical Operations" from _Hacker's Delight_, Henry S.
+;;; Warren, Jr., 2nd ed., pp 87-90.
+;;;
+;;; We used to implement the algorithms from that source (then its first
+;;; edition) very faithfully here which exposed a weakness of theirs,
+;;; namely worst case quadratical runtime in the number of bits of the
+;;; input values, potentially leading to excessive compilation times for
+;;; expressions involving bignums. To avoid that, I have devised and
+;;; implemented variations of these algorithms that achieve linear
+;;; runtime in all cases.
+;;;
+;;; Like Warren, let's start with the high bound on LOGIOR to explain
+;;; how this is done. To follow, please read Warren's explanations on
+;;; his "maxOR" function and compare this with how the second return
+;;; value of LOGIOR-DERIVE-UNSIGNED-BOUNDS below is calculated.
+;;;
+;;; "maxOR" loops starting from the left until it finds a position where
+;;; both B and D are 1 and where it is possible to decrease one of these
+;;; bounds by setting this bit in it to 0 and all following ones to 1
+;;; without the resulting value getting below the corresponding lower
+;;; bound (A or C). This is done by calculating the modified values
+;;; during each iteration where both B and D are 1 and comparing them
+;;; against the lower bounds.
+;;; The trick to avoid the loop is to exchange the order of the steps:
+;;; First determine from which position rightwards it would be allowed
+;;; to change B or D in this way and have the result be larger or equal
+;;; than A or C respectively and then find the leftmost position equal
+;;; to this or to the right of it where both B and D are 1.
+;;; It is quite simple to find from where rightwards B could be modified
+;;; this way: This is the leftmost position where B has a 1 and A a 0,
+;;; or, cheaper to calculate, the leftmost position where A and B
+;;; differ. Thus (INTEGER-LENGTH (LOGXOR A B)) gives us this position
+;;; where a result of 1 corresponds to the rightmost bit position. As we
+;;; don't care which of B or D we modify we can take the maximum of this
+;;; value and of (INTEGER-LENGTH (LOGXOR C D)).
+;;; The rest is equally simple: Build a mask of 1 bits from the thusly
+;;; found position rightwards, LOGAND it with B and D and feed that into
+;;; INTEGER-LENGTH. From this build another mask and LOGIOR it with B
+;;; and D to set the desired bits.
+;;; The special cases where A equals B and/or C equals D are covered by
+;;; the same code provided the mask generator treats an argument of -1
+;;; the same as 0, which both ZEROES and ONES do.
+;;;
+;;; To calculate the low bound on LOGIOR we need to treat X and Y
+;;; independently for longer but the basic idea stays the same.
+;;;
+;;; LOGAND-DERIVE-UNSIGNED-BOUNDS can be derived by sufficiently many
+;;; applications of DeMorgan's law from LOGIOR-DERIVE-UNSIGNED-BOUNDS.
+;;; The implementation additionally avoids work (that is, calculations
+;;; of one's complements) by using the identity (INTEGER-LENGTH X) =
+;;; (INTEGER-LENGTH (LOGNOT X)) and observing that ZEROES is cheaper
+;;; than ONES.
+;;;
+;;; For the low bound on LOGXOR we use Warren's formula
+;;; minXOR(a, b, c, d) = minAND(a, b, !d, !c) | minAND(!b, !a, c, d)
+;;; where "!" is bitwise negation and "|" is bitwise or. Both minANDs
+;;; are implemented as in LOGAND-DERIVE-UNSIGNED-BOUNDS (the part for
+;;; the first result), sharing the first LOGXOR and INTEGER-LENGTH
+;;; calculations as (LOGXOR A B) = (LOGXOR (LOGNOT B) (LOGNOT A)).
+;;;
+;;; For the high bound on LOGXOR Warren's formula seems unnecessarily
+;;; complex. Instead, with (LOGNOT (LOGXOR X Y)) = (LOGXOR X (LOGNOT Y))
+;;; we have
+;;; maxXOR(a, b, c, d) = !minXOR(a, b, !d, !c)
+;;; and rewriting minXOR as above yields
+;;; maxXOR(a, b, c, d) = !(minAND(a, b, c, d) | minAND(!b, !a, !d, !c))
+;;; This again shares the first LOGXOR and INTEGER-LENGTH calculations
+;;; between both minANDs and with the ones for the low bound.
+;;;
+;;; LEU, 2013-04-29.
+
+(defun logand-derive-unsigned-bounds (x y)
+ (let* ((a (numeric-type-low x))
+ (b (numeric-type-high x))
+ (c (numeric-type-low y))
+ (d (numeric-type-high y))
+ (length-xor-x (integer-length (logxor a b)))
+ (length-xor-y (integer-length (logxor c d))))
+ (values
+ (let* ((mask (zeroes (max length-xor-x length-xor-y)))
+ (index (integer-length (logior mask a c))))
+ (logand a c (zeroes (1- index))))
+ (let* ((mask-x (ones length-xor-x))
+ (mask-y (ones length-xor-y))
+ (index-x (integer-length (logand mask-x b (lognot d))))
+ (index-y (integer-length (logand mask-y d (lognot b)))))
+ (cond ((= index-x index-y)
+ ;; Both indexes are 0 here.
+ (logand b d))
+ ((> index-x index-y)
+ (logand (logior b (ones (1- index-x))) d))
+ (t
+ (logand (logior d (ones (1- index-y))) b)))))))