Cを学習しているときに、基本的なことだけを行っていました。*演算子を使用せずに数値を7倍するという質問に出くわしました。基本的にはこんな感じ
(x << 3) - x;
今、基本的なビット操作操作について知っていますが、*演算子を使用せずに他の奇数と数字を乗算する方法を得ることができませんか?これに一般的なアルゴリズムはありますか?
鉛筆と紙を使用して10進数で乗算する方法を考えます。
12
x 26
----
72
24
----
312
乗算はバイナリでどのように見えますか?
0111
x 0101
-------
0111
0000
0111
-------
100011
何か気づいた? 「倍数表」を記憶する必要がある10進数での乗算とは異なり、バイナリで乗算する場合、リストの加数に書き出す前に常に用語の1つに0または1を乗算します。回数表は必要ありません。 2番目の用語の桁が1の場合、最初の用語に追加します。 0の場合、そうではありません。また、加数が次第に左にシフトする様子にも注意してください。
これがわからない場合は、紙の上でいくつかのバイナリ乗算を行います。完了したら、結果を10進数に戻し、正しいかどうかを確認します。いくつかの作業を終えると、シフトと加算を使用してバイナリ乗算を実装する方法を理解できると思います。
誰もが明らかなことを見落としています。乗算は含まれません:
10^(log10(A) + log10(B))
質問は言う:
*演算子を使用せずに数値に7を掛けます
これは*
を使用しません:
number / (1 / 7)
編集:
これはCでコンパイルされ、正常に動作します。
int number,result;
number = 8;
result = number / (1. / 7);
printf("result is %d\n",result);
整数の左シフトは、オーバーフローしない限り、2倍になります。近づいたら、必要に応じて追加または削除します。
int multiply(int multiplicand, int factor)
{
if (factor == 0) return 0;
int product = multiplicand;
for (int ii = 1; ii < abs(factor); ++ii) {
product += multiplicand;
}
return factor >= 0 ? product : -product;
}
*
なしの乗算が必要でした。
「*」演算子は簡単に回避できます。
mov eax, 1234h
mov edx, 5678h
imul edx
「*」が見えない。もちろん、その精神に浸りたい場合は、信頼できる古いシフトを使用してアルゴリズムを追加することもできます。
mult proc
; Multiplies eax by ebx and places result in edx:ecx
xor ecx, ecx
xor edx, edx
mul1:
test ebx, 1
jz mul2
add ecx, eax
adc edx, 0
mul2:
shr ebx, 1
shl eax, 1
test ebx, ebx
jnz mul1
done:
ret
mult endp
もちろん、最新のプロセッサでは、すべての(?)に乗算命令がありますが、 PDP-11 が光沢があり、新しい場合は、このようなコードが実際に使用されていました。
数学的に言えば、乗算は加算に対して分配します。基本的に、これは次のことを意味します。
x *(a + b + c ...)=(x * a)+(x * b)+(x * c)...
実数(あなたの場合は_7
_)は、一連の加算として表すことができます(減算は実際には加算が間違った方向に進むため、8 + (-1)
など)。これにより、単一の乗算ステートメントを同等の一連の乗算ステートメントとして表すことができ、同じ結果が得られます。
_x * 7
= x * (8 + (-1))
= (x * 8) + (x * (-1))
= (x * 8) - (x * 1)
= (x * 8) - x
_
bitwise shift演算子は、基本的に数値を2の累乗で乗算または除算するだけです。方程式がそのような値のみを処理している限り、ビットシフトを使用して乗算演算子のすべての出現を置き換えることができます。
(x * 8)-x =(x * 23)-x =(x << 3)-x
他の整数でも同様の戦略を使用でき、奇数でも偶数でも違いはありません。
x*8-x = x*(8-1) = x*7
と同じです
奇数または偶数の任意の数は、2の累乗の合計として表すことができます。例えば、
1 2 4 8
------------------
1 = 1
2 = 0 + 2
3 = 1 + 2
4 = 0 + 0 + 4
5 = 1 + 0 + 4
6 = 0 + 2 + 4
7 = 1 + 2 + 4
8 = 0 + 0 + 0 + 8
11 = 1 + 2 + 0 + 8
したがって、シフトと加算の正しいセットを実行することにより、xに任意の数を掛けることができます。
1x = x
2x = 0 + x<<1
3x = x + x<<1
4x = 0 + 0 + x<<2
5x = x + 0 + x<<2
11x = x + x<<1 + 0 + x<<3
結局のところ、正の整数による乗算は次のように実行できます。
int multiply(int a, int b) {
int ret = 0;
for (int i=0; i<b; i++) {
ret += b;
}
return ret;
}
効率的?ほとんどない。しかし、それは正しいです(intの制限を考慮するなど)。
したがって、左シフトを使用することは、2を乗算するためのショートカットにすぎません。ただし、b
の下で最大の2のべき乗になったら、必要な回数a
を追加するだけです。 :
int multiply(int a, int b) {
int ret = a;
int mult = 1;
while (mult <= b) {
ret <<= 1;
mult <<= 1;
}
while (mult < b) {
ret += a;
}
return ret;
}
またはそれに近いもの。
別の言い方をすると、7を掛けます。
b
を3回追加します。ある夜、私は非常に退屈していることがわかり、これを調理しました:
#include <iostream>
typedef unsigned int uint32;
uint32 add(uint32 a, uint32 b) {
do {
uint32 s = a ^ b;
uint32 c = a & b;
a = s;
b = c << 1;
} while (a & b)
return (a | b)
}
uint32 mul(uint32 a, uint32 b) {
uint32 total = 0;
do {
uint32 s1 = a & (-(b & 1))
b >>= 1; a <<= 1;
total = add(s1, total)
} while (b)
return total;
}
int main(void) {
using namespace std;
uint32 a, b;
cout << "Enter two numbers to be multiplied: ";
cin >> a >> b;
cout << "Total: " << mul(a,b) << endl;
return 0;
}
上記のコードは、できる限りシンプルに保つように努めたため、非常に自明です。 CPUがこれらの操作を実行する方法とほぼ同じように機能するはずです。私が知っている唯一のバグは、a
が32,767を超えることは許可されておらず、b
がa
をオーバーフローさせるほど大きくできないことです(つまり、乗算オーバーフローは処理されないため、64ビットの結果は不可能です。入力が適切であればreinterpret_cast<>
。
O(log(b))メソッド
public int multiply_optimal(int a, int b) {
if (a == 0 || b == 0)
return 0;
if (b == 1)
return a;
if ((b & 1) == 0)
return multiply_optimal(a + a, b >> 1);
else
return a + multiply_optimal(a + a, b >> 1);
}
再帰コードは次のように機能します。
規範事例:
いずれかの数値が0の場合、製品は0です。
b = 1の場合、product = a。
Bが偶数の場合:
abは2a(b/2)と書くことができます
2a(b/2)=(a + a)(b/2(a + a))==(b >> 1)where '>>'算術右シフト演算子Javaで。
Bが奇数の場合:
abはa + a(b-1)と書くことができます
a + a(b-1)= a + 2a(b-1)/ 2 = a +(a + a)(b-1)/ 2 = a +(a + a)((b-1 )>> 1)
bは奇数であるため(b-1)/ 2 = b/2 = b >> 1
したがって、ab = a +(2a *(b >> 1))
注:各再帰呼び出しbは半分になります=> O(log(b))
unsigned int Multiply(unsigned int m1, unsigned int m2)
{
unsigned int numBits = sizeof(unsigned int) * 8; // Not part of the core algorithm
unsigned int product = 0;
unsigned int mask = 1;
for(int i =0; i < numBits; ++i, mask = mask << 1)
{
if(m1 & mask)
{
product += (m2 << i);
}
}
return product;
}
@Wang、それは良い一般化です。しかし、これは少し速いバージョンです。ただし、オーバーフローは発生せず、aは負ではありません。
int mult(int a, int b){
int p=1;
int rv=0;
for(int i=0; a >= p && i < 31; i++){
if(a & p){
rv += b;
}
p = p << 1;
b = b << 1;
}
return rv;
}
最大で1 + log_2(a)回ループします。 a> bのときにaとbを入れ替えると、より速くなる可能性があります。
unsigned int Multiply( unsigned int a, unsigned int b )
{
int ret = 0;
// For each bit in b
for (int i=0; i<32; i++) {
// If that bit is not equal to zero
if (( b & (1 << i)) != 0) {
// Add it to our return value
ret += a << i;
}
}
return ret;
}
それは投稿の主題ではないので、私はサインビットを避けました。これは、基本的に Wayne Conrad が言ったことの実装です。 別の問題は、より低レベルの数学演算を試してみたいということです。Project Euler is cool!
被乗数が負の場合、Shiftとaddは機能しません(符号拡張があっても)。符号付き乗算は ブースエンコーディング を使用して実行する必要があります。
[〜#〜] lsb [〜#〜] から始めて、0から1への変更は-1です。 1から0への変更は1、それ以外の場合は0です。LSBの下には暗黙の追加ビット0もあります。
たとえば、番号5(0101)は(1)(-1)(1)(-1)としてエンコードされます。これが正しいことを確認できます。
5 = 2 ^ 3-2 ^ 2 + 2 -1
このアルゴリズムは、2の補数形式の負の数でも機能します。
4ビット2の補数の-1は1111です。ブースアルゴリズムの使用:(1)(0)(0)(0)(-1)。左端のビット1にスペースがないため、次のようになります:(0) (0)(0)(-1)これは-1です。
/* Multiply two signed integers using the Booth algorithm */
int booth(int x, int y)
{
int prev_bit = 0;
int result = 0;
while (x != 0) {
int current_bit = x & 0x1;
if (prev_bit & ~current_bit) {
result += y;
} else if (~prev_bit & current_bit) {
result -= y;
}
prev_bit = current_bit;
x = static_cast<unsigned>(x) >> 1;
y <<= 1;
}
if (prev_bit)
result += y;
return result;
}
上記のコードはオーバーフローをチェックしません。以下はわずかに変更されたバージョンで、2つの16ビット数を乗算し、32ビット数を返すため、オーバーフローすることはありません。
/* Multiply two 16-bit signed integers using the Booth algorithm */
/* Returns a 32-bit signed integer */
int32_t booth(int16_t x, int16_t y)
{
int16_t prev_bit = 0;
int16_t sign_bit = (x >> 16) & 0x1;
int32_t result = 0;
int32_t y1 = static_cast<int32_t>(y);
while (x != 0) {
int16_t current_bit = x & 0x1;
if (prev_bit & ~current_bit) {
result += y1;
} else if (~prev_bit & current_bit) {
result -= y1;
}
prev_bit = current_bit;
x = static_cast<uint16_t>(x) >> 1;
y1 <<= 1;
}
if (prev_bit & ~sign_bit)
result += y1;
return result;
}
import Java.math.BigInteger;
public class MultiplyTest {
public static void main(String[] args) {
BigInteger bigInt1 = new BigInteger("5");
BigInteger bigInt2 = new BigInteger("8");
System.out.println(bigInt1.multiply(bigInt2));
}
}
Java:
すべての数字を2のべき乗に分割できるという事実を考慮してください。
1 = 2 ^ 0
2 = 2 ^ 1
3 = 2 ^ 1 + 2 ^ 0
...
Xを取得する場所:x = n * m
したがって、次の手順を実行することでそれを実現できます。
1. while m is greater or equal to 2^pow:
1.1 get the biggest number pow, such as 2^pow is lower or equal to m
1.2 multiply n*2^pow and decrease m to m-2^pow
2. sum the results
再帰を使用した実装例:
long multiply(int n, int m) {
int pow = 0;
while (m >= (1 << ++pow)) ;
pow--;
if (m == 1 << pow) return (n << pow);
return (n << pow) + multiply(n, m - (1 << pow));
}
私は前回の就職面接でこの質問を受け、この回答は受け入れられました。
編集:正数の解
ログ機能を使用できる場合:
public static final long multiplyUsingShift(int a, int b) {
int absA = Math.abs(a);
int absB = Math.abs(b);
//Find the 2^b which is larger than "a" which turns out to be the
//ceiling of (Log base 2 of b) == numbers of digits to shift
double logBase2 = Math.log(absB) / Math.log(2);
long bits = (long)Math.ceil(logBase2);
//Get the value of 2^bits
long biggerInteger = (int)Math.pow(2, bits);
//Find the difference of the bigger integer and "b"
long difference = biggerInteger - absB;
//Shift "bits" places to the left
long result = absA<<bits;
//Subtract the "difference" "a" times
int diffLoop = Math.abs(a);
while (diffLoop>0) {
result -= difference;
diffLoop--;
}
return (a>0&&b>0 || a<0&&b<0)?result:-result;
}
ログ機能を使用できない場合:
public static final long multiplyUsingShift(int a, int b) {
int absA = Math.abs(a);
int absB = Math.abs(b);
//Get the number of bits for a 2^(b+1) larger number
int bits = 0;
int bitInteger = absB;
while (bitInteger>0) {
bitInteger /= 2;
bits++;
}
//Get the value of 2^bit
long biggerInteger = (int)Math.pow(2, bits);
//Find the difference of the bigger integer and "b"
long difference = biggerInteger - absB;
//Shift "bits" places to the left
long result = absA<<bits;
//Subtract the "difference" "a" times
int diffLoop = absA;
while (diffLoop>0) {
result -= difference;
diffLoop--;
}
return (a>0&&b>0 || a<0&&b<0)?result:-result;
}
私はこれがより効率的であることがわかりました:
public static final long multiplyUsingShift(int a, int b) {
int absA = Math.abs(a);
int absB = Math.abs(b);
long result = 0L;
while (absA>0) {
if ((absA&1)>0) result += absB; //Is odd
absA >>= 1;
absB <<= 1;
}
return (a>0&&b>0 || a<0&&b<0)?result:-result;
}
そしてさらに別の方法。
public static final long multiplyUsingLogs(int a, int b) {
int absA = Math.abs(a);
int absB = Math.abs(b);
long result = Math.round(Math.pow(10, (Math.log10(absA)+Math.log10(absB))));
return (a>0&&b>0 || a<0&&b<0)?result:-result;
}
C#の場合:
private static string Multi(int a, int b)
{
if (a == 0 || b == 0)
return "0";
bool isnegative = false;
if (a < 0 || b < 0)
{
isnegative = true;
a = Math.Abs(a);
b = Math.Abs(b);
}
int sum = 0;
if (a > b)
{
for (int i = 1; i <= b; i++)
{
sum += a;
}
}
else
{
for (int i = 1; i <= a; i++)
{
sum += b;
}
}
if (isnegative == true)
return "-" + sum.ToString();
else
return sum.ToString();
}
これは正数に対する最も単純なC99/C11ソリューションです。
unsigned multiply(unsigned x, unsigned y) { return sizeof(char[x][y]); }
箱から出した別の答え:
BigDecimal a = new BigDecimal(123);
BigDecimal b = new BigDecimal(2);
BigDecimal result = a.multiply(b);
System.out.println(result.intValue());
再帰を利用することにより、2つの整数を指定された制約で乗算できます。
Xとyを乗算するには、x yを再帰的に追加します。
#include<stdio.h>
/* function to multiply two numbers x and y*/
int multiply(int x, int y)
{
/* multiplied with anything gives */
if(y == 0)
return 0;
/* Add x one by one */
if(y > 0 )
return (x + multiply(x, y-1));
/* the case where y is negative */
if(y < 0 )
return -multiply(x, -y);
}
int main()
{
printf("\n %d", multiply(5, -11));
getchar();
return 0;
}
ループします。ループを7回実行し、7を掛ける数だけ繰り返します。
擬似コード:
total = 0
multiply = 34
loop while i < 7
total = total + multiply
endloop
public static void main(String[] args) {
System.out.print("Enter value of A -> ");
Scanner s=new Scanner(System.in);
double j=s.nextInt();
System.out.print("Enter value of B -> ");
Scanner p=new Scanner(System.in);
double k=p.nextInt();
double m=(1/k);
double l=(j/m);
System.out.print("Multiplication of A & B=> "+l);
}
Nを7で乗算する数値とします。
N x 7 = N + N + N + N + N + N + N
N x 7 = N + N + N + N + N + N + N + (N - N)
N x 7 = (N + N + N + N + N + N + N + N) - N
N x 7 = 8xN - N
知っているように、任意の数を左に1ビットシフトすると、2が乗算されます。したがって、任意の数に8を乗算すると、右に3ビットシフトします
N x 7 = (N << 3) - N
ここでこの説明を見つけました http://www.techcrashcourse.com/2016/02/c-program-to-multiply-number-by-7-bitwise-operator.html
それが役に立てば幸い。
正数のJavaScriptアプローチ
function recursiveMultiply(num1, num2){
const bigger = num1 > num2 ? num1 : num2;
const smaller = num1 <= num2 ? num1 : num2;
const indexIncrement = 1;
const resultIncrement = bigger;
return recursiveMultiplyHelper(bigger, smaller, 0, indexIncrement, resultIncrement)
}
function recursiveMultiplyHelper(num1, num2, index, indexIncrement, resultIncrement){
let result = 0;
if (index === num2){
return result;
}
if ((index+indexIncrement+indexIncrement) >= num2){
indexIncrement = 1;
resultIncrement = num1;
} else{
indexIncrement += indexIncrement;
resultIncrement += resultIncrement;
}
result = recursiveMultiplyHelper(num1, num2, (index+indexIncrement), indexIncrement, resultIncrement);
result += resultIncrement;
console.log(num1, num2, index, result);
return result;
}
public static int multiply(int a, int b)
{
int temp = 0;
if (b == 0) return 0;
for (int ii = 0; ii < abs(b); ++ii) {
temp = temp + a;
}
return b >= 0 ? temp : -temp;
}
public static int abs(int val) {
return val>=0 ? val : -val;
}
package com.amit.string;
// Here I am passing two values, 7 and 3 and method getResult() will
// return 21 without use of any operator except the increment operator, ++.
//
public class MultiplyTwoNumber {
public static void main(String[] args) {
int a = 7;
int b = 3;
System.out.println(new MultiplyTwoNumber().getResult(a, b));
}
public int getResult(int i, int j) {
int result = 0;
// Check for loop logic it is key thing it will go 21 times
for (int k = 0; k < i; k++) {
for (int p = 0; p < j; p++) {
result++;
}
}
return result;
}
}