ID登録せずに、無料で質問できる♪ 教えて!gooアプリ>>

技術者向けでは基本的すぎて笑われてしまうかも知れません。が、プロの方で詳しい方にお聞きしたかったのです。例えば、プラスの数値を算術右シフトすれば
「あふれたビットを破棄するのは、その範囲で表現できないから切り捨てる。
たとえば3を2で割ると1.5ですが、
1右シフトすると1になりますが、
0.5を保持する桁がないからですね。
逆に桁あふれも同じ意味ですね。」という回答を見たのですが、確かにプラス部(負数でない部分)については理解出来るのですが、これがマイナスの整数(電算機上では負数)の場合、例えば「-1.5」であれば「-2」になるということでした。なぜ単純に「-0.5」部分の切り捨てで済まないのでしょうか?

このQ&Aに関連する最新のQ&A

A 回答 (5件)

No.1です。



>どうしても分からないのは、(中略)つまり絶対値で0.5の差が出る理由です。
くどくなりますが、すべては『2の補数を使用するから』というところに集約します。

以下は4bitで説明します。
4bitの場合、表せる範囲は「0000」~「1111」の16種類です。
正負の数を「1の補数」で表現した場合
 0000:0   1111:0(本来は使用しないが扱い上は0)
 0001:+1  1110:-1
 0010:+2  1101:-2
 0011:+3  1100:-3
 0100:+4  1011:-4
 0101:+5  1010:-5
 0110:+6  1001:-6
 0111:+7  1000:-7
となります。「-7~0~+7」までの「15」の数値を表します。
対して「2の補数」は
 0000:0   1111:-1
 0001:+1  1110:-2
 0010:+2  1101:-3
 0011:+3  1100:-4
 0100:+4  1011:-5
 0101:+5  1010:-6
 0110:+6  1001:-7
 0111:+7  1000:-8
となります。「-8~0~7」までの「16」の数値を表します。
つまり、+(0も含む)と-で表現できる範囲が1つずれているのです。

あえて誤解するように書けば、「0」が中心ではなく「-0.5」がデータ的な中心です。
絶対値は、データの中心である「-0.5からの相対位置」になりますから「+1」の絶対値なら「1.5」、「-1」の絶対値は「0.5」、絶対値の差は「1」となります。
この値を「プラスとマイナスだから/2」とすると「0.5」になります。
それによって発生する問題です。

正直なところ、なんとか理屈づけて納得しようとしても難しいので「こういうものだ」と割り切ったほうがいいと思います。
    • good
    • 0
この回答へのお礼

ほんとうにご丁寧に分かりやすい解説有難うございました。SilverThaw
様の親切さにただ感涙するのみです。感謝いたします。

お礼日時:2009/05/22 20:50

No.1です。


>(1)n進数の補数にnの補数とn-1の補数の二つがある理由が分かりません。
正直、私も知りたいです。
このおかげで私も過去に「2の補数」のつもりで作っていたら、実は「1の補数」でイタイ目を見たことがありますので(単に、仕様の確認漏れだけなのですが)
個人的な考えですが、
・単純にわかりやすくビットを反転しただけ(+1の処理がいらない、回路も小さくなる)
・「0000 0000」を起点として「0000 0001」「0000 0010」……と、「1111 1111」「1111 1110」……と進んでいくのがわかりやすいから
ではないかと考えたりしてますが。

>(2)一番分からないのは、このシステムで100を2進数で8ビットで表したものを(後略)
結論から書くと「10進数」ではなく「2進数(2の補数表現)」で処理しているためです。
単純に10進数で算出した際の小数点を切り捨てているわけではありません。
「2進数(2の補数)」で考えるとすっきりします。

今までと同じく8bit表現で記載します。
「+100」は「0110 0100」、「-100」は「2の補数」では「1001 1100」で表されます(算出方法はNo.3参照)。
これを「右に3ビット算術シフトする」と
「0110 0.100」(+100)は「0000 1100」=「12」
「1001 1.100」(-100)は「1111 0011」
となります(上記はわかりやすくするために、シフトした際に一番下位のbitの位置に「.」をつけています)。
この「1111 0011」は「1111 1111」(-1)から数えて13番目であり「-13」となります。
(尚、マイナス値からの絶対値を求める場合も、「bitを反転して+1」は同じです。)

この回答への補足

ご丁寧に毎回詳しいご回答を本当に有難うございます。ただ悲しいですけど、自分には向かないのか・・・どうしても分からないのは、(1)+100を2進数で8ビットで表したものを右に3ビット算術シフトする場合は+12になり(2)-100を同じように処理した場合、-13になる、つまり絶対値で0.5の差が出る理由です。甘えすぎかも知れませんが、もし、可能でしたら、是非ご教授下さい。

補足日時:2009/05/22 18:33
    • good
    • 0

No.1です。



>(前略)(残ったビットを10進数表示した場合の絶対値が、正負でことなる理由が分からないのです)もしよろしければご教授願います。
結論から書くと「マイナス値を表す手法が『2の補数』基準」で考えているからです。

まず、「2進数」は理解されていますか?
また、「マイナスを表すための方法」については?
ここが理解できていないと難しいです。

以下は8bitの2進数で説明します。
10進数の「+3」は2進数では「0000 0011」と表します。
これを右に1bitシフトすれば「0000 0001」となります。
一番下位ののbitが追い出されるため、結果は「1」となります。

対して「-3」は「2の補数」で表すと「1111 1101」となります。
「2の補数」はマイナス値を表す方法の一つで、正の値のbitの0/1を逆転した値「+1」することで値を表します。
これにより、マイナス値の場合は必ず最上値のbitが「1」になります。
「-3」の場合は「0000 0011」をbit反転して「1111 1100」、それに「+1」して「1111 1101」となります。
この値を右に右に1bitシフトすれば、桁あふれし「1111 1110」となります。
「2の補数では」この値は「-2」です。

ちなみに、マイナス値の手法には「1の補数」というものも存在します。
この場合「-3」の値は「1111 1100」。
右に1bitシフトした値は「1111 1110」となり、結果は「-1」となります。

この回答への補足

ご丁寧に詳しい補足説明有難うございます。確かにまだ理解が曖昧な部分あると思います。もし更に教えていただけるなら(1)n進数の補数にnの補数とn-1の補数の二つがある理由が分かりません。(2)一番分からないのは、このシステムで100を2進数で8ビットで表したものを右に3ビット算術シフトする場合と-100をこの補数表現で同様の行為(右に3ビット算術シフトする)を行った場合とで+と-の符号を除いた絶対値に違いが出ることです。前者は10進数表記では12.5から小数点以下の0.5(2進小数点以下の1ビット)を切り捨て、12?後者もやはりこの算術シフトによる右の桁の切り捨てで小数点以下のビットは消えるはずなのに-13?(絶対値13)?+と-では0と1は反転するのは分かるのですが、この計算結果になるロジックが分からないのです?

補足日時:2009/05/21 21:59
    • good
    • 0

「負の数の丸め」は表現として微妙なところがあります.


たとえば「-1.5 を整数に丸める」ときに「-2 とする」のか「-1 とする」のかという選択があります. 実際には正の場合にも選択肢が考えられます. ということで, 丸めの方法には
・負の無限大への丸め (小さい方に丸める)
・正の無限大への丸め (大きい方に丸める)
・0 への丸め (絶対値の小さい方に丸める)
・最近値への丸め (最も近い値に丸める: 中間のときは偶数にするのがきっと正しい)
などがあります.
今の場合は「負の無限大への丸め」となるので -1.5 が -2 になります.
解釈によっては「-1.5 を -2 にするのは -0.5 の部分を切り捨てたんだ」とも見なせますけどね.

この回答への補足

すみません。丸めというよりは単純に算術右シフトした場合に、小数点以下にあたるのビットを切り捨てねばならない場合のことです。

補足日時:2009/05/21 19:17
    • good
    • 0

「(-3)÷2」とした場合、8bitでの-3の「2の補数」は2進数で表すと「1111 1101」。


「2で割る」ことにより、右に1bitシフトした値は「1111 1110」。
これは「-2」にあたるから。

この回答への補足

早速のご回答有難うございます。ただ小数点以下を表示しないビット列で右算術シフトした場合でビットが一つ溢れた場合、プラス部なら0.5にあたるビットの切り捨てとかで理解出来るのですけれども、負数で
はなぜ単純に-0.5に当たるビットの切り捨てとならないのでしょうか?(残ったビットを10進数表示した場合の絶対値が、正負でことなる理由が分からないのです)もしよろしければご教授願います。

補足日時:2009/05/21 19:08
    • good
    • 0

このQ&Aに関連する人気のQ&A

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人が検索しているワード

このQ&Aと関連する良く見られている質問

Q除算を使わずに10で割りたい。

加算、減算、シフト、論理演算、ループ、等を使って
除算を使わずに10で割る方法は無いものでしょうか?
また、近似値を求めてから誤差を修正する方法でも構いません。

ただし、ループで10を引き続けてカウントする方法は除外させてください。

言語はCでお願いします。

Aベストアンサー

ANo.15 は「引き戻し法」ですね。いわゆる普通の「割り算の筆算」をそのまま2進数ベースで実装したものです。
(10進の割り算の場合は、各桁で「1倍~9倍のどこまで引けるか」を計算しますが、2進数なので、「引ける(その桁は1)か引けない(その桁は0)か」の2択の判定になります。

それをもうちょっと進めた(計算量を減らした)方法に「引き放し法」というのもあります。「引けるかどうか」チェックしてから「引く」のは二度手間なので、「引いてしまってから、結果によって次の処理を変える(負になったら、次のステップでは足すようにする)」というものです。

でも、この手の処理は「CPUの内部でハードウェアで実装されている」機能ですから、それをソフトウェアで実装するのは遅いだけでメリットはありません。
多倍長演算が例に挙げられてますけど、多倍長の2進数で実装する場合でも、
個々の演算は通常の加減乗除の組合せで実装するのが普通です。
例えば、32bitCPUで256bitの数値演算をしたいのであれば、
「4294967296進数で8桁の数値」を扱うようにすればいいんです。

簡単な例ですが、以下、64bitの数値を16bit×4桁と考えて10で割るコードです。除数の方も多倍長になるとやることは格段に複雑になりますが、演算の基本方針は同じです。


#include <stdio.h>
int main(int argc, char *argv[])
{
union {
unsigned long long ulonglong;
unsigned short ushort[4];
} x, y;
unsigned int div, mod;

x.ulonglong = 9876543210LL;
printf("%lld = %d * (1<<48) + %d * (1<<32) + %d * (1<<16) + %d\n", x.ulonglong, x.ushort[3], x.ushort[2], x.ushort[1], x.ushort[0]);
/* x86 CPUは Little Endian なので、ushort[3]が上位でushort[0]が下位 */

div = x.ushort[3] / 10;
mod = x.ushort[3] % 10;
y.ushort[3] = div;

div = (mod * 65536 + x.ushort[2]) / 10;
mod = (mod * 65536 + x.ushort[2]) % 10;
y.ushort[2] = div;

div = (mod * 65536 + x.ushort[1]) / 10;
mod = (mod * 65536 + x.ushort[1]) % 10;
y.ushort[1] = div;

div = (mod * 65536 + x.ushort[0]) / 10;
mod = (mod * 65536 + x.ushort[0]) % 10;
y.ushort[0] = div;

printf("%lld / 10 = %lld mod %d\n", x.ulonglong, y.ulonglong, mod);

}

ANo.15 は「引き戻し法」ですね。いわゆる普通の「割り算の筆算」をそのまま2進数ベースで実装したものです。
(10進の割り算の場合は、各桁で「1倍~9倍のどこまで引けるか」を計算しますが、2進数なので、「引ける(その桁は1)か引けない(その桁は0)か」の2択の判定になります。

それをもうちょっと進めた(計算量を減らした)方法に「引き放し法」というのもあります。「引けるかどうか」チェックしてから「引く」のは二度手間なので、「引いてしまってから、結果によって次の処理を変える(負になったら、次の...続きを読む