0

たとえば、1000 0000 0000 0000 は 16 ビットであるため、短いビットのセットがあります。算術シフトを使用して、MSB を使用して残りのビットを割り当てたいと思います。

1111 1111 1111 1111

0000 0000 0000 0000 から始めた場合:

算術シフト後、私はまだ持っているでしょう: 0000 0000 0000 0000

アセンブリに頼らずにこれを行うにはどうすればよいですか? インテルの組み込みガイドを見たところ、AVX 拡張機能を使用してこれを行う必要があるように見えますが、短いデータ型よりも大きなデータ型が見られます。

4

2 に答える 2

3

mattnewport が彼の回答で述べているように、「実装定義」の動作の可能性はありますが、C コードは効率的に仕事を行うことができます。この回答は、効率的なコード生成を維持しながら、実装定義の動作を回避する方法を示しています。

問題は 16 ビット オペランドのシフトに関するものであるため、最初にオペランドを 32 ビットに符号拡張することで、符号拡張するかゼロ フィルを行うかの実装定義の決定に関する問題を回避できます。次に、32 ビット値を符号なしとして右にシフトし、最終的に切り捨てて 16 ビットに戻すことができます。

Mattnewport のコードは、シフトする前に、実際には 16 ビットのオペランドを int (コンパイラ モデルに応じて 32 ビットまたは 64 ビット) に符号拡張します。これは、言語仕様 (C99 6.5.7 ビットごとのシフト演算子) が最初のステップを必要とするためです:整数昇格はオペランドのそれぞれで実行されます。同様に、mattnewport のコードの結果は int です。結果の型は昇格された左オペランドの型だからです。このため、実装定義の動作を回避するコード バリエーションは、mattnewport の元のコードと同じ数の命令を生成します。

実装定義の動作を回避するために、signed int への暗黙的な昇格は unsigned int への明示的な昇格に置き換えられます。これにより、同じコード効率を維持しながら、実装定義の動作の可能性が排除されます。

このアイデアは、32 ビットのオペランドをカバーするように拡張でき、64 ビットのネイティブ整数サポートが存在する場合に効率的に機能します。次に例を示します。

// use standard 'll' for long long print format
#define __USE_MINGW_ANSI_STDIO 1
#include <stdio.h>
#include <stdint.h>

// Code provided by mattnewport
int16_t aShiftRight16x (int16_t val, int count)
    {
    return val >> count;
    }

// This variation avoids implementation defined behavior
int16_t aShiftRight16y (int16_t val, int count)
    {
    uint32_t uintVal = val;
    uint32_t uintResult = uintVal >> count;
    return (int16_t) uintResult;
    }

// A 32-bit arithmetic right shift without implementation defined behavior
int32_t aShiftRight32 (int32_t val, int count)
    {
    uint64_t uint64Val = val;
    uint64_t uint64Result = uint64Val >> count;
    return (int32_t) uint64Result;
    }

int main (void)
    {
    int16_t val16 = 0x8000;
    int32_t val32 = 0x80000000;
    int count;

    for (count = 0; count <= 15; count++)
        printf ("%04hX %04hX %08X\n", aShiftRight16x (val16, count),
                                      aShiftRight16y (val16, count),
                                      aShiftRight32  (val32, count));
    return 0;
    }

gcc 4.8.1 x64 コード生成は次のとおりです。

  0000000000000030 <aShiftRight16x>:
    30: 0f bf c1                movsx  eax,cx
    33: 89 d1                   mov    ecx,edx
    35: d3 f8                   sar    eax,cl
    37: c3                      ret    

  0000000000000040 <aShiftRight16y>:
    40: 0f bf c1                movsx  eax,cx
    43: 89 d1                   mov    ecx,edx
    45: d3 e8                   shr    eax,cl
    47: c3                      ret    

  0000000000000050 <aShiftRight32>:
    50: 48 63 c1                movsxd rax,ecx
    53: 89 d1                   mov    ecx,edx
    55: 48 d3 e8                shr    rax,cl
    58: c3                      ret    

MS Visual Studio x64 コード生成は次のとおりです。

  aShiftRight16x:
    00: 0F BF C1           movsx       eax,cx
    03: 8B CA              mov         ecx,edx
    05: D3 F8              sar         eax,cl
    07: C3                 ret
  aShiftRight16y:
    10: 0F BF C1           movsx       eax,cx
    13: 8B CA              mov         ecx,edx
    15: D3 E8              shr         eax,cl
    17: C3                 ret
  aShiftRight32:
    20: 48 63 C1           movsxd      rax,ecx
    23: 8B CA              mov         ecx,edx
    25: 48 D3 E8           shr         rax,cl
    28: C3                 ret

プログラム出力:

8000 8000 80000000
C000 C000 C0000000
E000 E000 E0000000
F000 F000 F0000000
F800 F800 F8000000
FC00 FC00 FC000000
FE00 FE00 FE000000
FF00 FF00 FF000000
FF80 FF80 FF800000
FFC0 FFC0 FFC00000
FFE0 FFE0 FFE00000
FFF0 FFF0 FFF00000
FFF8 FFF8 FFF80000
FFFC FFFC FFFC0000
FFFE FFFE FFFE0000
FFFF FFFF FFFF0000
于 2013-10-19T04:35:46.537 に答える
2

このために組み込み関数を探している理由がわかりません。通常の C++ 右シフトを使用しないのはなぜですか? この動作は実装で定義されていますが、Intel プラットフォームでは常に符号拡張されます。

int16_t val = 1 << 15; // 1000 0000 0000 0000 
int16_t shiftVal = val >> 15; // 1111 1111 1111 1111
于 2013-10-18T19:36:49.037 に答える