1

I haven't been able to find a common-lisp function (or macro) that simply inverts a bit. There are functions that operate on bit arrays, and logical functions that operate on numbers, but these would seem to involve extra steps for inversion of a single bit variable. A tentative solution is

(define-modify-macro invertf (&rest args)
  (lambda (bit)
    (if (= bit 0) 1 0)))

which works, but I was wondering if there is a more elegant or simpler solution.

davypough
  • 1,847
  • 11
  • 21

1 Answers1

3

There are a lot of operators for bitwise arithmetic:

For example you can use

(logxor bit 1)

This will give you 0 if the bit is 1 and 1 if the bit is 0:

(logxor 0 1) ; => 1
(logxor 1 1) ; => 0

Of course you can put the bit part as the second argument:

(logxor 1 bit)

Bonus:

Perhaps you can make a function and optimise it with a type declaration:

(defun invert (bit)
  "Inverts a bit"
  (declare (type bit bit))
  (logxor bit 1))

After running (disassemble 'invert) I got the following results on SBCL:

WITHOUT type declaration:

; disassembly for INVERT
; Size: 56 bytes. Origin: #x10059E97FC
; 7FC:       498B4C2460       MOV RCX, [R12+96]               ; thread.binding-stack-pointer
                                                              ; no-arg-parsing entry point
; 801:       48894DF8         MOV [RBP-8], RCX
; 805:       BF02000000       MOV EDI, 2
; 80A:       488BD3           MOV RDX, RBX
; 80D:       4883EC18         SUB RSP, 24
; 811:       48896C2408       MOV [RSP+8], RBP
; 816:       488D6C2408       LEA RBP, [RSP+8]
; 81B:       B904000000       MOV ECX, 4
; 820:       FF1425980B1020   CALL QWORD PTR [#x20100B98]     ; TWO-ARG-XOR
; 827:       488B5DF0         MOV RBX, [RBP-16]
; 82B:       488BE5           MOV RSP, RBP
; 82E:       F8               CLC
; 82F:       5D               POP RBP
; 830:       C3               RET
; 831:       0F0B10           BREAK 16                        ; Invalid argument count trap

WITH type declaration

; disassembly for INVERT
; Size: 25 bytes. Origin: #x1005767CA9
; A9:       498B4C2460       MOV RCX, [R12+96]                ; thread.binding-stack-pointer
                                                          ; no-arg-parsing entry point
; AE:       48894DF8         MOV [RBP-8], RCX
; B2:       488BD3           MOV RDX, RBX
; B5:       4883F202         XOR RDX, 2
; B9:       488BE5           MOV RSP, RBP
; BC:       F8               CLC
; BD:       5D               POP RBP
; BE:       C3               RET
; BF:       0F0B10           BREAK 16                         ; Invalid argument count trap

It seems to me that the type declaration saved a few operations.

Let's measure the difference:

(defun invert (bit)
  "Inverts a bit"
  (declare (type bit bit))
  (logxor bit 1))

(defun invert-no-dec (bit)
  "Inverts a bit"
  (logxor bit 1))

Now running:

(time (loop repeat 1000000 do (invert 1)))

Outputs:

Evaluation took:
  0.007 seconds of real time
  0.005164 seconds of total run time (0.005073 user, 0.000091 system)
  71.43% CPU
  14,060,029 processor cycles
  0 bytes consed

While running:

(time (loop repeat 1000000 do (invert-no-dec 1)))

Outputs:

Evaluation took:
  0.011 seconds of real time
  0.011327 seconds of total run time (0.011279 user, 0.000048 system)
  100.00% CPU
  25,505,355 processor cycles
  0 bytes consed

It seems the type declaration makes the function twice as fast. It must be noted that the call to invert will probably offset the gain in performance, unless you use (declaim (inline invert)). From the CLHS:

inline specifies that it is desirable for the compiler to produce inline calls to the functions named by function-names; that is, the code for a specified function-name should be integrated into the calling routine, appearing ``in line'' in place of a procedure call.

anonymous
  • 1,522
  • 14
  • 24
  • 1
    "the call to `invert` will probably offset the gain in performance" - You can use `(declaim (inline invert))` to inline the call. – jkiiski Feb 04 '17 at 08:30
  • Added! Thank you! :) – anonymous Feb 04 '17 at 08:40
  • @tsikov: Thank you, logxor was what I was looking for, but missing its significance. Assume now I can do `(define-modify-macro invertf (&rest args) (lambda (bit) (declare (type bit bit)) (logxor bit 1)))` to take advantage of declarations & inlining? – davypough Feb 04 '17 at 19:11
  • 1
    Yeah, it seems to be working on my machine. (The type declaration is saving CPU, but you cannot inline macros) Just a quick question: why do you want to define a macro to write a place. Why not a simple function? Just curious... :) – anonymous Feb 04 '17 at 22:36
  • 1
    @davypough It would be better to define `INVERT` as a separate (inlined) function instead of using `LAMBDA`. Your `INVERTF` will expand to something like `(setq x ((lambda (bit) (declare (type bit bit)) (logxor bit 1)) x))` in every place where it's used. If you define the modify macro as `(define-modify-macro invertf () invert)`, it'll expand to `(setq x (invert x))`, where the call to `INVERT` can be inlined. – jkiiski Feb 05 '17 at 08:27
  • 1
    @tsikov It's likely counterproductive, but I have difficulty "thinking" in the functional programming style (eg, used Scheme for a while). Code seems more perspicuous and concise to me when places are simply modified. I haven't found much difference in debugging, particularly when using lots of declarations and optimizing debug. But I am aware there's a "pure blood" thread in the common-lisp community, based on much more experience than I have, to adhere to certain practices. I wish there was a forum to discuss common-lisp at a more abstract level to better understand the many nuances. – davypough Feb 05 '17 at 18:17
  • @jkiiski: Yes, I see the difference now, and it is worth changing my strategy. Thanks. – davypough Feb 05 '17 at 18:18