C++ 数値操作と計算の練習

はじめに

画像を拡大縮小するとき、グラフが表示範囲内に収まるようにするとき、カメラと注視点との距離を変えるとき、スライダの注目範囲を変えるときなどなどに、2倍 4倍 8倍であったり、10倍 100倍 1000倍であったりといった一定の規則で変化する数列が必要なことがあります

選択肢が固定の場合や、あまりに複雑な場合などは配列を使うのが簡単ですが、ここではいろいろな周期的な数列を計算で求めてみましょう

1, 2, 3, 4...

指定されたインデックスに +1 するだけです
これはそのままですね

実装例

inline int	zoom1_i( int	zoom ){
	return	zoom + 1;
}
int	main(){
	debug_printf( "1\n" );
	for( int	n = 0; n < 20; ++n ){
		debug_printf( "[%2d] %d\n", n, zoom1_i( n ));
	}
	return	0;
}

出力例

1
[ 0] 1
[ 1] 2
[ 2] 3
[ 3] 4
[ 4] 5
[ 5] 6
[ 6] 7
[ 7] 8
[ 8] 9
[ 9] 10
[10] 11
[11] 12
[12] 13
[13] 14
[14] 15
[15] 16
[16] 17
[17] 18
[18] 19
[19] 20

逆計算
逆の場合もそのままですね
+1 をしたので逆にする場合は -1 します

inline int	indexof_zoom1( int	zoom ){
	return	zoom - 1;
}
int	main(){
	debug_printf( "1_2\n" );
	bool	result = true;
	for( int	n = 0; n < 20; ++n ){
		int	zoom = zoom1_i( n );
		int	m = indexof_zoom1( zoom );
		if( m != n ){
			debug_printf( "NG: %d -> %d != %d\n", zoom, m, n );
			result = false;
			break;
		}
	}
	if( result ){
		debug_printf( "OK\n" );
	}
	return	0;
}

1, 2, 4, 8...

2倍 4倍 8倍というような倍率は画像の拡大などで使います
プログラミングでは、2倍づつという場合は 1ビットづつシフトするだけで済みます

実装例

inline int	zoom2_i( int	zoom ){
	return	1 << zoom;
}
int	main(){
	debug_printf( "2\n" );
	for( int	n = 0; n < 20; ++n ){
		debug_printf( "[%2d] %d\n", n, zoom2_i( n ));
	}
	return	0;
}

出力例

2
[ 0] 1
[ 1] 2
[ 2] 4
[ 3] 8
[ 4] 16
[ 5] 32
[ 6] 64
[ 7] 128
[ 8] 256
[ 9] 512
[10] 1024
[11] 2048
[12] 4096
[13] 8192
[14] 16384
[15] 32768
[16] 65536
[17] 131072
[18] 262144
[19] 524288

逆計算
2 の累乗の反対なので log2 を得るだけですが、整数用の log 関数はないので自分で書きましょう

inline int	log2_i( int	x ){
	int	result = 0;
	while( x >= 2 ){
		x >>= 1;
		++result;
	}
	return	result;
}
inline int	indexof_zoom2( int	zoom ){
	return	log2_i( zoom );
}
int	main(){
	debug_printf( "2_2\n" );
	bool	result = true;
	for( int	n = 0; n < 20; ++n ){
		int	zoom = zoom2_i( n );
		int	m = indexof_zoom2( zoom );
		if( m != n ){
			debug_printf( "NG: %d -> %d != %d\n", zoom, m, n );
			result = false;
			break;
		}
	}
	if( result ){
		debug_printf( "OK\n" );
	}
	return	0;
}

1, 10, 100, 1000...

10倍づつということはそのまま 10 の累乗です
累乗を求めるのは、浮動小数点数なら pow 関数がありますが整数にはありません
自分で書きましょう

実装例

inline int	pow10_i( int	e ){
	int	result = 1;
	while( e > 0 ){
		result *= 10;
		--e;
	}
	return	result;
}
inline int	zoom10_i( int	zoom ){
	return	pow10_i( zoom );
}
int	main(){
	debug_printf( "10\n" );
	for( int	n = 0; n < 10; ++n ){
		debug_printf( "[%2d] %d\n", n, zoom10_i( n ));
	}
	return	0;
}

出力例

10
[ 0] 1
[ 1] 10
[ 2] 100
[ 3] 1000
[ 4] 10000
[ 5] 100000
[ 6] 1000000
[ 7] 10000000
[ 8] 100000000
[ 9] 1000000000

逆計算
こちらも 10 の累乗の反対なので log10 を得るだけです

inline int	log10_i( int	x ){
	int	result = 0;
	while( x >= 10 ){
		x /= 10;
		++result;
	}
	return	result;
}
inline int	indexof_zoom10( int	zoom ){
	return	log10_i( zoom );
}
int	main(){
	debug_printf( "10_2\n" );
	bool	result = true;
	for( int	n = 0; n < 10; ++n ){
		int	zoom = zoom10_i( n );
		int	m = indexof_zoom10( zoom );
		if( m != n ){
			debug_printf( "NG: %d -> %d != %d\n", zoom, m, n );
			result = false;
			break;
		}
	}
	if( result ){
		debug_printf( "OK\n" );
	}
	return	0;
}

1, 2, 10, 20, 100, 200...

これは2つおきの 1, 2, 1, 2 という変化と、2つおきに 10倍という変化の組み合わせですから、まず指定のインデックスを2つづつに区切ります
2つづつということは、最下位1ビットとそれ以外の上位ビットとに分けます

そして最下位1ビットの値を 1, 2, 1, 2 に、上位ビットの値を 10 の累乗に対応させます

実装例

inline int	zoom_i( int	m, int	e ){
	return	m * pow10_i( e );
}
inline int	zoom12_i( int	zoom ){
	int	e = zoom >> 1;
	int	n = zoom & 1;
	int	m = n + 1;
	return	zoom_i( m, e );
}
int	main(){
	debug_printf( "12\n" );
	for( int	n = 0; n < 20; ++n ){
		debug_printf( "[%2d] %d\n", n, zoom12_i( n ));
	}
	return	0;
}

出力例

12
[ 0] 1
[ 1] 2
[ 2] 10
[ 3] 20
[ 4] 100
[ 5] 200
[ 6] 1000
[ 7] 2000
[ 8] 10000
[ 9] 20000
[10] 100000
[11] 200000
[12] 1000000
[13] 2000000
[14] 10000000
[15] 20000000
[16] 100000000
[17] 200000000
[18] 1000000000
[19] 2000000000

逆計算
0 を除いた余りを求めれば 1, 2 から 0, 1 を求めるだけなので -1 するだけですね

inline int	indexof_zoom12( int	zoom ){
	int	e = log10_i( zoom );
	int	m = zoom / pow10_i( e );
	int	n = m - 1;
	return	( e << 1 ) + n;
}
int	main(){
	debug_printf( "12_2\n" );
	bool	result = true;
	for( int	n = 0; n < 20; ++n ){
		int	zoom = zoom12_i( n );
		int	m = indexof_zoom12( zoom );
		if( m != n ){
			debug_printf( "NG: %d -> %d != %d\n", zoom, m, n );
			result = false;
			break;
		}
	}
	if( result ){
		debug_printf( "OK\n" );
	}
	return	0;
}

1, 5, 10, 50, 100, 500...

1, 2, 10, 20, 100, 200... の場合と同じですが、最下位1ビットの値を 1, 2 ではなく 1, 5 に対応させます

実装例

inline int	zoom15_i( int	zoom ){
	int	e = zoom >> 1;
	int	n = zoom & 1;
	int	m = ( n << 2 ) + 1;
	return	zoom_i( m, e );
}
int	main(){
	debug_printf( "15\n" );
	for( int	n = 0; n < 10; ++n ){
		debug_printf( "[%2d] %d\n", n, zoom15_i( n ));
	}
	return	0;
}

出力例

15
[ 0] 1
[ 1] 5
[ 2] 10
[ 3] 50
[ 4] 100
[ 5] 500
[ 6] 1000
[ 7] 5000
[ 8] 10000
[ 9] 50000

逆計算
1, 5 から 0, 1 を求めます
2ビットシフトするとちょうどよさそうです

inline int	indexof_zoom15( int	zoom ){
	int	e = log10_i( zoom );
	int	m = zoom / pow10_i( e );
	int	n = m >> 2;
	return	( e << 1 ) + n;
}
int	main(){
	debug_printf( "15_2\n" );
	bool	result = true;
	for( int	n = 0; n < 10; ++n ){
		int	zoom = zoom15_i( n );
		int	m = indexof_zoom15( zoom );
		if( m != n ){
			debug_printf( "NG: %d -> %d != %d\n", zoom, m, n );
			result = false;
			break;
		}
	}
	if( result ){
		debug_printf( "OK\n" );
	}
	return	0;
}

1, 2, 3, 10, 20, 30, 100, 200, 300...

これも基本的には 1, 2, 10, 20, 100, 200... の場合と同じですが、こちらは3つづつ分けます
3つづつということは、3で割った商と剰余とに分けます

実装例

inline int	zoom123_i( int	zoom ){
	int	e = zoom / 3;
	int	n = zoom % 3;
	int	m = n + 1;
	return	zoom_i( m, e );
}
int	main(){
	debug_printf( "123\n" );
	for( int	n = 0; n < 20; ++n ){
		debug_printf( "[%2d] %d\n", n, zoom123_i( n ));
	}
	return	0;
}

出力例

123
[ 0] 1
[ 1] 2
[ 2] 3
[ 3] 10
[ 4] 20
[ 5] 30
[ 6] 100
[ 7] 200
[ 8] 300
[ 9] 1000
[10] 2000
[11] 3000
[12] 10000
[13] 20000
[14] 30000
[15] 100000
[16] 200000
[17] 300000
[18] 1000000
[19] 2000000

逆計算
1, 2, 3 から 0, 1, 2 を求めます

inline int	indexof_zoom123( int	zoom ){
	int	e = log10_i( zoom );
	int	m = zoom / pow10_i( e );
	int	n = m - 1;
	return	( e * 3 ) + n;
}
int	main(){
	debug_printf( "123_2\n" );
	bool	result = true;
	for( int	n = 0; n < 20; ++n ){
		int	zoom = zoom123_i( n );
		int	m = indexof_zoom123( zoom );
		if( m != n ){
			debug_printf( "NG: %d -> %d != %d\n", zoom, m, n );
			result = false;
			break;
		}
	}
	if( result ){
		debug_printf( "OK\n" );
	}
	return	0;
}

1, 2, 3, 4, 10, 20, 30, 40, 100, 200, 300...

これも基本的には 1, 2, 10, 20, 100, 200... の場合と同じですが、こちらは4つづつ分けます
4つづつということは、最下位2ビットとそれ以外の上位ビットとに分けます

実装例

inline int	zoom1234_i( int	zoom ){
	int	e = zoom >> 2;
	int	n = zoom & 3;
	int	m = n + 1;
	return	zoom_i( m, e );
}
int	main(){
	debug_printf( "1234\n" );
	for( int	n = 0; n < 20; ++n ){
		debug_printf( "[%2d] %d\n", n, zoom1234_i( n ));
	}
	return	0;
}

出力例

1234
[ 0] 1
[ 1] 2
[ 2] 3
[ 3] 4
[ 4] 10
[ 5] 20
[ 6] 30
[ 7] 40
[ 8] 100
[ 9] 200
[10] 300
[11] 400
[12] 1000
[13] 2000
[14] 3000
[15] 4000
[16] 10000
[17] 20000
[18] 30000
[19] 40000

逆計算
1, 2, 3, 4 から 0, 1, 2, 3 を求めます

inline int	indexof_zoom1234( int	zoom ){
	int	e = log10_i( zoom );
	int	m = zoom / pow10_i( e );
	int	n = m - 1;
	return	( e << 2 ) + n;
}
int	main(){
	debug_printf( "1234_2\n" );
	bool	result = true;
	for( int	n = 0; n < 20; ++n ){
		int	zoom = zoom1234_i( n );
		int	m = indexof_zoom1234( zoom );
		if( m != n ){
			debug_printf( "NG: %d -> %d != %d\n", zoom, m, n );
			result = false;
			break;
		}
	}
	if( result ){
		debug_printf( "OK\n" );
	}
	return	0;
}

1, 2, 4, 8, 10, 20, 40, 80, 100, 200, 400...

4つづつなので、こちらも最下位2ビットとそれ以外の上位ビットとに分けます
最下位2ビットの値が 1, 2, 4, 8 の変化に対応し、上位ビットの値は 10 の累乗に対応します

実装例

inline int	zoom1248_i( int	zoom ){
	int	e = zoom >> 2;
	int	n = zoom & 3;
	int	m = 1 << n;
	return	zoom_i( m, e );
}
int	main(){
	debug_printf( "1248\n" );
	for( int	n = 0; n < 20; ++n ){
		debug_printf( "[%2d] %d\n", n, zoom1248_i( n ));
	}
	return	0;
}

出力例

1248
[ 0] 1
[ 1] 2
[ 2] 4
[ 3] 8
[ 4] 10
[ 5] 20
[ 6] 40
[ 7] 80
[ 8] 100
[ 9] 200
[10] 400
[11] 800
[12] 1000
[13] 2000
[14] 4000
[15] 8000
[16] 10000
[17] 20000
[18] 40000
[19] 80000

逆計算
1, 2, 4, 8 から 0, 1, 2, 3 を求めます
log2 でちょうどよさそうですね

inline int	indexof_zoom1248( int	zoom ){
	int	e = log10_i( zoom );
	int	m = zoom / pow10_i( e );
	int	n = log2_i( m );
	return	( e << 2 ) + n;
}
int	main(){
	debug_printf( "1248_2\n" );
	bool	result = true;
	for( int	n = 0; n < 20; ++n ){
		int	zoom = zoom1248_i( n );
		int	m = indexof_zoom1248( zoom );
		if( m != n ){
			debug_printf( "NG: %d -> %d != %d\n", zoom, m, n );
			result = false;
			break;
		}
	}
	if( result ){
		debug_printf( "OK\n" );
	}
	return	0;
}

1, 3, 5, 7, 10, 30, 50, 70, 100, 300, 500...

1, 2, 4, 8, 10, 20, 40, 80, 100, 200, 400... の場合と同じですが、こちらは下位2ビットの値を 1, 3, 5, 7 に対応させます
0, 1, 2, 3 を2倍して 0, 2, 4, 6、さらに +1 すれば 1, 3, 5, 7 になりました

実装例

inline int	zoom1357_i( int	zoom ){
	int	e = zoom >> 2;
	int	n = zoom & 3;
	int	m = ( n << 1 ) + 1;
	return	zoom_i( m, e );
}
int	main(){
	debug_printf( "1357\n" );
	for( int	n = 0; n < 20; ++n ){
		debug_printf( "[%2d] %d\n", n, zoom1357_i( n ));
	}
	return	0;
}

出力例

1357
[ 0] 1
[ 1] 3
[ 2] 5
[ 3] 7
[ 4] 10
[ 5] 30
[ 6] 50
[ 7] 70
[ 8] 100
[ 9] 300
[10] 500
[11] 700
[12] 1000
[13] 3000
[14] 5000
[15] 7000
[16] 10000
[17] 30000
[18] 50000
[19] 70000

逆計算
1, 3, 5, 7 から 0, 1, 2, 3 を求めます
1ビットシフトするだけでよさそうですね

inline int	indexof_zoom1357( int	zoom ){
	int	e = log10_i( zoom );
	int	m = zoom / pow10_i( e );
	int	n = m >> 1;
	return	( e << 2 ) + n;
}
int	main(){
	debug_printf( "1357_2\n" );
	bool	result = true;
	for( int	n = 0; n < 20; ++n ){
		int	zoom = zoom1357_i( n );
		int	m = indexof_zoom1357( zoom );
		if( m != n ){
			debug_printf( "NG: %d -> %d != %d\n", zoom, m, n );
			result = false;
			break;
		}
	}
	if( result ){
		debug_printf( "OK\n" );
	}
	return	0;
}

1, 2, 5, 10, 20, 50, 100, 200, 500...

UI の調整で 10倍 100倍 1000倍では変化が粗すぎるというような場合に使います

1, 2, 3, 10, 20, 30, 100, 200, 300... の場合と同じですが、こちらは 1, 2, 3 ではなく 1, 2, 5 とします
0, 1, 2 を3倍して 0, 3, 6、さらに -1 して -1, 2, 5、絶対値をとると 1, 2, 5 になりました

実装例

inline int	abs_i( int	n ){
	return	( n < 0 ) ? (-n) : n;
}
inline int	zoom125_i( int	zoom ){
	int	e = zoom / 3;
	int	n = zoom % 3;
	int	m = abs_i( n * 3 - 1 );
	return	zoom_i( m, e );
}
int	main(){
	debug_printf( "125\n" );
	for( int	n = 0; n < 20; ++n ){
		debug_printf( "[%2d] %d\n", n, zoom125_i( n ));
	}
	return	0;
}

出力例

125
[ 0] 1
[ 1] 2
[ 2] 5
[ 3] 10
[ 4] 20
[ 5] 50
[ 6] 100
[ 7] 200
[ 8] 500
[ 9] 1000
[10] 2000
[11] 5000
[12] 10000
[13] 20000
[14] 50000
[15] 100000
[16] 200000
[17] 500000
[18] 1000000
[19] 2000000

実はこの計算はこんなことをしなくてももっと簡単にできます
どうすればいいでしょうか

0, 1, 2 の 2 だけ2ビット目が立っていますから、これを利用すると簡単になります

inline int	zoom125_i( int	zoom ){
	int	e = zoom / 3;
	int	n = zoom % 3;
	int	m = n + ( n & 2 ) + 1;
	return	zoom_i( m, e );
}

逆計算
1, 2, 5 から 0, 1, 2 を求めます
これも 1ビットシフトするだけでよさそうですね

inline int	indexof_zoom125( int	zoom ){
	int	e = log10_i( zoom );
	int	m = zoom / pow10_i( e );
	int	n = m >> 1;
	return	( e * 3 ) + n;
}
int	main(){
	debug_printf( "125_2\n" );
	bool	result = true;
	for( int	n = 0; n < 20; ++n ){
		int	zoom = zoom125_i( n );
		int	m = indexof_zoom125( zoom );
		if( m != n ){
			debug_printf( "NG: %d -> %d != %d\n", zoom, m, n );
			result = false;
			break;
		}
	}
	if( result ){
		debug_printf( "OK\n" );
	}
	return	0;
}

1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300...

4つづつなので、こちらも最下位2ビットとそれ以外の上位ビットとに分けます
最下位2ビットの値が 1, 2, 3, 5 の変化に対応し、上位ビットの値は 10 の累乗に対応します
0, 1, 2, 3 に +1 すると 1, 2, 3, 4 で、4 だけ3ビット目が立っているので、これを利用します

実装例

inline int	zoom1235_i( int	zoom ){
	int	e = zoom >> 2;
	int	n = zoom & 3;
	int	m = n + (( n + 1 ) >> 2 ) + 1;
	return	zoom_i( m, e );
}
int	main(){
	debug_printf( "1235\n" );
	for( int	n = 0; n < 20; ++n ){
		debug_printf( "[%2d] %d\n", n, zoom1235_i( n ));
	}
	return	0;
}

出力例

1235
[ 0] 1
[ 1] 2
[ 2] 3
[ 3] 5
[ 4] 10
[ 5] 20
[ 6] 30
[ 7] 50
[ 8] 100
[ 9] 200
[10] 300
[11] 500
[12] 1000
[13] 2000
[14] 3000
[15] 5000
[16] 10000
[17] 20000
[18] 30000
[19] 50000

逆計算
1, 2, 3, 5 から 0, 1, 2, 3 を求めます
こちらも3ビット目を使えそうです

inline int	indexof_zoom1235( int	zoom ){
	int	e = log10_i( zoom );
	int	m = zoom / pow10_i( e );
	int	n = ( m - 1 ) - ( m >> 2 );
	return	( e << 2 ) + n;
}
int	main(){
	debug_printf( "1235_2\n" );
	bool	result = true;
	for( int	n = 0; n < 20; ++n ){
		int	zoom = zoom1235_i( n );
		int	m = indexof_zoom1235( zoom );
		if( m != n ){
			debug_printf( "NG: %d -> %d != %d\n", zoom, m, n );
			result = false;
			break;
		}
	}
	if( result ){
		debug_printf( "OK\n" );
	}
	return	0;
}

テーブル引きでも構わない

ここに挙げたものはどれもとても簡単なものですが、別に覚える必要はありません
計算できなくてもテーブルを引けば済むことです
計算で答を出せるのが偉くてテーブル引きは偉くないなんてことはありません
メモリが遅いといっても、計算する回数が少ないなら気にすることはありません

個別の計算方法というよりは基本的な考え方や着眼点などが身につくといいですね

Hacker's Delight という本にいろいろとおもしろい計算が載っています。おすすめです