web-dev-qa-db-ja.com

数を否定する最速の方法

私は今朝、ここで、多くのポジティブからネガティブへ、そしてネガティブからポジティブへと逆転する最も速い方法は何であるかを考えていました。もちろん、最も簡単な方法は次のとおりです。

int a = 10;
a = a*(-1);

または

int a = 10;
a = -a;

しかし、その後、私はそれをコマンドシフトとポインタを使用してこれを行うと考えました...コマンドシフト演算子とメモリを使用して、値の符号を変更することは本当に可能でしょうか?

32
Alexandre

最初の生成物:

    .file   "optimum.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .text
.globl _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    call    ___main
    movl    $10, 12(%esp) ;i = 10
    negl    12(%esp)      ;i = -i
    movl    $0, %eax
    leave
    ret

2番目のものは以下を生成します。

    .file   "optimum.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .text
.globl _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    call    ___main
    movl    $10, 12(%esp)   ;i = 10
    negl    12(%esp)        ;i = -i
    movl    $0, %eax
    leave
    ret

同じ出力!生成されるアセンブリコードに違いはありません。

--------------------------編集、OPはVC++ 2012を使用していると答え、インテルアーチ----------- --------

cl optimum.c /Fa optimum.asmを使用してコンパイル

; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01 

    TITLE   C:\Users\Dell\Downloads\TTH\TTH\TTH\optimum.c
    .686P
    .XMM
    include listing.inc
    .model  flat

INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES

PUBLIC  _main
; Function compile flags: /Odtp
_TEXT   SEGMENT
_a$ = -4                        ; size = 4
_argc$ = 8                      ; size = 4
_argv$ = 12                     ; size = 4
_main   PROC
; File c:\users\Dell\downloads\tth\tth\tth\optimum.c
; Line 4
    Push    ebp
    mov ebp, esp
    Push    ecx
; Line 5
    mov DWORD PTR _a$[ebp], 10          ; 0000000aH
; Line 6
    mov eax, DWORD PTR _a$[ebp]
    neg eax ;1 machine cycle!
    mov DWORD PTR _a$[ebp], eax
; Line 7
    xor eax, eax
; Line 8
    mov esp, ebp
    pop ebp
    ret 0
_main   ENDP
_TEXT   ENDS
END

そして、2番目のアプローチ(a = a * -1)

; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01 

    TITLE   C:\Users\Dell\Downloads\TTH\TTH\TTH\optimum.c
    .686P
    .XMM
    include listing.inc
    .model  flat

INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES

PUBLIC  _main
; Function compile flags: /Odtp
_TEXT   SEGMENT
_a$ = -4                        ; size = 4
_argc$ = 8                      ; size = 4
_argv$ = 12                     ; size = 4
_main   PROC
; File c:\users\Dell\downloads\tth\tth\tth\optimum.c
; Line 4
    Push    ebp
    mov ebp, esp
    Push    ecx
; Line 5
    mov DWORD PTR _a$[ebp], 10          ; 0000000aH
; Line 6
    mov eax, DWORD PTR _a$[ebp]
    imul    eax, -1 ;1 instruction, 3 machine/cycles :|
    mov DWORD PTR _a$[ebp], eax
; Line 7
    xor eax, eax
; Line 8
    mov esp, ebp
    pop ebp
    ret 0
_main   ENDP
_TEXT   ENDS
END
22
Aniket Inge

次のような読みやすいものを使用してください

a *= -1;

または

a = -a;

残りはオプティマイザーに任せます。

37
Armen Tsirunyan

他の答えは、読みやすさがより重要であることを正しく示しています。

  • 速度を忘れて、最も読みやすいイディオムを選択する必要があります。
  • ほとんどのコンパイラ(最適化が有効になっている)は、_a = -a_と_a *= -1_がまったく同じであり、どのように記述するかに関係なく、ターゲットCPUで最も効率的であると判断したasmを放出します。 (例 Godbolt compiler Explorer x86 gcc/MSVC/clangの場合、ARM gcc。)
    • ただし、MSVS 2012(デバッグモードのみ)はそれぞれに1つの命令を使用しますが、実際のimul命令を使用して、最近のIntel CPUでは_= -a_に1サイクル、_*= -1_に3サイクルかかります。
  • より速くしようとすると、読みにくくなり、簡単に遅くなる可能性があります。
  • 最適化する必要がある場合は、生成されたコードとパフォーマンスを分析することから始めてください。


ただし、実用的な利点には_*= -1_イディオムがあります。左側を1回書くだけで、1回だけ評価されます–そして読者は一度読むだけです!これは、LHSが長く、複雑で、高価である場合、または副作用がある可能性がある場合に関連します。

_(valid ? a : b)[prime_after(i++)] *= -1;
*look_up (input) *= -1;  // Where look_up may have side-effects
parity[state][(unsigned int)getc(stdin)] *= -1;
variable_with_a_long_explanatory_name *= -1;
_

そして、イディオムを採用すると、他の状況でもイディオムを使い続ける傾向があります。

6
PJTraill

また、0-n

Gccは、4つの場合すべてに対して「neg」命令を発行します。-n、0-n、n * -1、および〜n + 1

3
Jeremy

プロセッサが少なくともある程度能力があり、sizeof(int) == sizeof(Cpu_register)を持っていると仮定すると、「この番号を負にする」は単一の命令(通常negと呼ばれます)[まあ、値のロードと格納することもできますが、変数を他の目的に使用している場合は、ロード後も保持でき、後でのみ格納できます...]

-1による乗算は、おそらくa = -a;よりも遅くなりますが、ほとんどの有能なコンパイラーは、これらの両方を同等にできるはずです。

したがって、コードを明確に記述するだけで、残りは自分で処理する必要があります。数字を無効にすることは、ほとんどのプロセッサで難しい操作ではありません。異常なプロセッサを使用している場合は、コンパイラの出力を見て、それが何をするのかを確認してください。

3
Mats Petersson

高水準言語を使用したソリューション

このような質問は、インタビューや競合プログラミングの世界で人気があります。

ここに着いたのは、-または+演算子を使用せずに数値を否定するためのより多くの解決策を研究することです。

このため :

  1. 〜演算子を使用して数値を補う
  2. 次に、半加算器ロジックを使用して、手順1で取得した数値に1を加算します。
> int addNumbers(int x, int y)
>       {
>                    if(y==0)  return x; // carry is 0 
>                    return addNumbers(x^y,(x&y)<<1);
>         }

ここで、x ^ yはビットの加算を実行し、x&yはキャリー演算を処理します

1
Divyanshu Jimmy