

数値を16bitにキャストして正負判断するコードを考えています。

(short) n

lispではどのように書くのでしょうか？


CC BY-SA 3.0
2015-04-26T05:28:35.923
9559
2015-10-06T18:15:00.853
9493
question
common-lisp
型変換はどう書くの？


それほど詳しくはないので間違ってたらすみません。  
Common Lisp ではそもそも１６ビット型の整数というのがありません。  
（整数型が自然に切り替わる）  
強いて言うなら`FIXNUM`がそうかもしれませんけども、  
`coerce` のような変換関数を使うにしてもそもそもが`FIXNUM`の範囲の数値でなければ変換できません。（変換に失敗する）  
だから正負判定がしたいのであれば  
単に

[code]

    (minusp n) ;負の数の時Ｔ
    (plusp n) ;正の数の時Ｔ
    (>= n 0) ; ０以上の時Ｔ
    
[/code]

のようにします。


CC BY-SA 3.0
2015-04-26T07:37:57.447
9562
2015-04-26T07:37:57.447
5044
9559
answer


BLUEPIXYさんの回答に補足です。

変数が特定の範囲の整数であること、は型宣言できます。例えば:

[code]

    (defun (foo x)
      (declare (type (integer -32768 32767) x))
      ...)
    
[/code]

こうすると、...の中ではコンパイラはxを-32768～32767の間の整数であるとしてコードを出すことが可能になります(但し、負数に2の補数表現を使っているとは限らないので、これが16bitに収まるかどうかは保証されません)

けれども、上記のコードでfooに正しい範囲の値を渡す責任はプログラマにあります。コンパイラは勝手に変換してくれません。`(foo
1000000)`とかやったらどうなるかは処理系次第です。

そもそも元の値が16bitを越えていた時どうすべきか、にはいくつも解釈があり得ます(modulo
2^16として扱うか、例外を投げるか、クリップするか)。従って、プログラマが自分で決めて書いてやる必要があります。型宣言は主に、コンパイラが良いコードを出すためのヒントとして作用します。(例えば、16bit整数の配列に数値を出し入れする時に、型チェックが省略されるなど)。

そういう性能重要なコードを書いているのでなければ、数値の変換は暗黙に行われるので、プログラマからは「整数の型のバリエーション」を気にする必要はほとんどありません。正負の判定なら既に出ている回答のように単なる数値として符号を調べるだけでOKです。

以下は少し脇道に逸れます。単なる数値としてだけ符号を調べるなら、例えば次のようなコードが考えられます。速くしたいのでdeclare
optimizeをつけてみました。

[code]

    (defun sign1 (x)
      (declare (optimize (safety 0) (debug 0) (speed 3)))
      (cond ((< x 0) -1)
            ((> x 0) 1)
            (t 0)))
    
[/code]

これをSBCLでコンパイルして逆アセンブルすると:

[code]

    ; DISASSEMBLY FOR SIGN1
    ; 02AC52C2:       48895DF8         MOV [RBP-8], RBX           ; NO-ARG-PARSING ENTRY POINT
    ;      2C6:       31FF             XOR EDI, EDI
    ;      2C8:       488BD3           MOV RDX, RBX
    ;      2CB:       488D0C2530040020 LEA RCX, [#X20000430]      ; GENERIC-<
    ;      2D3:       FFD1             CALL RCX
    ;      2D5:       488B5DF8         MOV RBX, [RBP-8]
    ;      2D9:       7C24             JL L1
    ;      2DB:       31FF             XOR EDI, EDI
    ;      2DD:       488BD3           MOV RDX, RBX
    ;      2E0:       488D0C2566040020 LEA RCX, [#X20000466]      ; GENERIC->
    ;      2E8:       FFD1             CALL RCX
    ;      2EA:       BA00000000       MOV EDX, 0
    ;      2EF:       41BB02000000     MOV R11D, 2
    ;      2F5:       490F4FD3         CMOVNLE RDX, R11
    ;      2F9: L0:   488BE5           MOV RSP, RBP
    ;      2FC:       F8               CLC
    ;      2FD:       5D               POP RBP
    ;      2FE:       C3               RET
    ;      2FF: L1:   48C7C2FEFFFFFF   MOV RDX, -2
    ;      306:       EBF1             JMP L0
    
[/code]

大小比較に`GENERIC-<`のような外部関数をいちいち呼び出していますね。引数に何が渡ってくるかわからないので、どんな値が来ても対応できるようにしなければならないからです。

これを、xがfixnumであると宣言すれば：

[code]

    (defun sign2 (x)
      (declare (optimize (safety 0) (debug 0) (speed 3))
               (fixnum x))
      (cond ((< x 0) -1)
            ((> x 0) 1)
            (t 0)))
    
[/code]

こうなります(xを-32768〜32767の範囲と宣言してもコードは同じでした)：

[code]

    ; disassembly for SIGN2
    ; 02B3F91F:       4883FA00         CMP RDX, 0                 ; no-arg-parsing entry point
    ;       23:       7C19             JL L1
    ;       25:       4883FA00         CMP RDX, 0
    ;       29:       BA00000000       MOV EDX, 0
    ;       2E:       41BB02000000     MOV R11D, 2
    ;       34:       490F4FD3         CMOVNLE RDX, R11
    ;       38: L0:   488BE5           MOV RSP, RBP
    ;       3B:       F8               CLC
    ;       3C:       5D               POP RBP
    ;       3D:       C3               RET
    ;       3E: L1:   48C7C2FEFFFFFF   MOV RDX, -2
    ;       45:       EBF1             JMP L0
    
[/code]

xの比較がマシンインストラクションで行われました。が、変換のコードはどこにもありません。コンパイラは、プログラマが正しくfixnumのみを渡してくれることを期待しています。

もしsign2の呼出側でその保証が出来ないのであれば、sign関数側でチェックを入れる必要があります。例えばfixnumの範囲外をエラーにするなら:

[code]

    (defun sign3 (x)
      (unless (sb-int:fixnump x)
        (error "out of range!"))
      (locally (declare (optimize (safety 0) (debug 0) (speed 3))
               (fixnum x))
        (cond ((< x 0) -1)
              ((> x 0) 1)
              (t 0))))
    
[/code]

コードは大きくなりますが、fixnumを渡した場合はmov, cmpとjneを通るだけです。

[code]

    ; disassembly for SIGN3
    ; 02C3DB28:       40F6C601         TEST SIL, 1                ; no-arg-parsing entry point
    ;       2C:       752E             JNE L2
    ;       2E:       488BD6           MOV RDX, RSI
    ;       31:       4883FA00         CMP RDX, 0
    ;       35:       7D0D             JNL L1
    ;       37:       48C7C2FEFFFFFF   MOV RDX, -2
    ;       3E: L0:   488BE5           MOV RSP, RBP
    ;       41:       F8               CLC
    ;       42:       5D               POP RBP
    ;       43:       C3               RET
    ;       44: L1:   488BD6           MOV RDX, RSI
    ;       47:       4883FA00         CMP RDX, 0
    ;       4B:       BA00000000       MOV EDX, 0
    ;       50:       41BB02000000     MOV R11D, 2
    ;       56:       490F4FD3         CMOVNLE RDX, R11
    ;       5A:       EBE2             JMP L0
    ;       5C: L2:   488975F8         MOV [RBP-8], RSI
    ;       60:       488D5C24F0       LEA RBX, [RSP-16]
    ;       65:       4883EC18         SUB RSP, 24
    ;       69:       488B1560FFFFFF   MOV RDX, [RIP-160]         ; "out of range!"
    ;       70:       488B0561FFFFFF   MOV RAX, [RIP-159]         ; #<FDEFINITION object for ERROR>
    ;       77:       B902000000       MOV ECX, 2
    ;       7C:       48892B           MOV [RBX], RBP
    ;       7F:       488BEB           MOV RBP, RBX
    ;       82:       FF5009           CALL QWORD PTR [RAX+9]
    ;       85:       90               NOP
    ;       86:       488B75F8         MOV RSI, [RBP-8]
    ;       8A:       CC0A             BREAK 10                   ; error trap
    ;       8C:       02               BYTE #X02
    ;       8D:       18               BYTE #X18                  ; INVALID-ARG-COUNT-ERROR
    ;       8E:       54               BYTE #X54                  ; RCX
    
[/code]


CC BY-SA 3.0
2015-04-26T11:11:23.883
9567
2015-04-26T11:41:17.983
2015-04-26T11:41:17.983
5258
5258
9559
answer


キャストのことは考慮しないとすれば、正負判断するには、専用の関数である`signum`が使えます。

  * <http://l1sp.org/cl/signum>

(signum -12341234)  
;=> -1

(signum 0)  
;=> 0

(signum 12341234)  
;=> 1

キャストについてですが、効率のためということであれば、shiroさんとBLUEPIXYさんが既に回答されているように、`fixnum`(64bit処理系であれば、60bit程度)以下にする必然性は殆どありません。

もし、C等でのキャストの動作を模倣したいということであれば自作することになると思います。

[code]

    (defun signum-short (x)
      (cond ((logbitp 15 x) -1)
            ((zerop (ldb (byte 16 0) x)) 0)
            (T 1)))
    
    
    (signum-short 1)
    ;=>  1
    
    
    (signum-short -1)
    ;=>  -1
    
    
    (signum-short 32767)
    ;=>  1
    
    
    (signum-short -32768)
    ;=>  -1
    
    
    (signum-short 32768)
    ;=>  -1
    
    
    (signum-short 0)
    ;=>  0
    
    
    (signum-short 65536)
    ;=>  0
    
    
    (signum-short -65536)
    ;=>  0
    
[/code]

勿論、動作を同じにしたからといって最適化するとCと同じコードが出てくるわけではありません

## ※追記

単に16bitでマスクしたいという場合は、下記のように書けます。

[code]

    (defun mask-16 (int)
      (cond ((logbitp 15 int)
             (dpb int (byte 16 0) -1))
            (t
             (ldb (byte 16 0) int))))
    
    
    (mask-16 0)
    ;=>  0
    
    (mask-16 65536)
    ;=>  0
    
    (mask-16 #b1111111111111111111111111111111111111)
    ;=>  -1
    
    (mask-16 -32768)
    ;=>  -32768
    
    (mask-16 32768)
    ;=>  -32768
    
[/code]


CC BY-SA 3.0
2015-04-26T23:41:31.720
9577
2015-04-27T21:30:20.917
2015-04-27T21:30:20.917
3510
3510
9559
answer