質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

4回答

8499閲覧

double型の変数を右シフトしたい

s_clalis

総合スコア17

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2019/06/23 13:57

編集2019/06/23 14:01

前提

  1. double型の変数ddouble d = 30788.0と宣言する。
  2. 適宜型変換などを行う。
  3. 0111 1000 0100 0100のような数値とみなし8bit右シフトを行う。
  4. 結果として120を得る。
  • 環境はVisual Studio 2019(v142)で恐らく、C++14以上、C++20/C++2a以下
  • コンパイラオプション/JMC /permissive- /GS /W3 /Zc:wchar_t /ZI /Gm- /Od /sdl /Fd"x64\Debug\vc142.pdb" /Zc:inline /fp:precise /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /RTC1 /Gd /MDd /FC /Fa"x64\Debug\" /EHsc /nologo /Fo"x64\Debug\" /Fp"x64\Debug\dsp2-2.pch" /diagnostics:column ("dsp2-2"はプロジェクト名)

問題点

  • 3番目の段階で不定の値が入るため、2番目の段階で変換に失敗している可能性。
  • ポインタなどを用いてうまいことすれば、できるかもしれない。(でも、double型のポインタと小数点についての理解が浅いこともあるので避けたい)

 そこで、折角C++なのでunion DoubleData{double doubledata; uint_fast64_t uintf64t;}reinterpret_cast<int>(static_cast<double*>(d))std::bitsetなどを用いて処理したい。

因みに、大本の目的はbitmap形式向けDCT後の、BGR->YCbCr変換のための丸め操作になります。(i.e.Y' = (Y' + 128) >> 8)

以下試し書きしたコードになります。

C++

1#include <iostream> 2 3int main(int argc, char* argv[]) 4{ 5 double d = 30788.0; 6 7 int y = reinterpret_cast<int>(static_cast<double*>(&y)) >> 8; 8 cout << y << endl; // 値は不定で-1E+9オーダーのものが出る 9 10 return 0; 11}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

ベストアンサー

reinterpret_cast<int>(static_cast<double*>(&y))で得られる結果は、yアドレス値であって、yの値は得られません。

折角C++なので

最終的に得たいのが整数なのであれば、単に「整数にキャストしてからシフト」だけでいい気がします。「C++縛り」でややこしいコードを書くほうが本末転倒なのではないでしょうか。

C++

1double d = 30788.0; 2int y = ((int) d) >> 8; 3int y = static_cast<int>(d) >> 8;

投稿2019/06/23 14:06

maisumakun

総合スコア145123

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2019/06/23 14:07

なお、frexpとldexpのような関数を使えば、浮動小数点数に対して2の累乗だけずらす演算を明示的に行うことができます。
s_clalis

2019/06/23 15:18

迅速なご回答ありがとうございます。 > 「C++縛り」でややこしいコードを書くほうが本末転倒なのではないでしょうか。 確かにその通りですね。昔からの癖で何とかしたいです… cmathのfrexp, ldexpについては初めて知りました! これからは、もっと標準ライブラリを把握できるよう心掛けます… 問題の解決に繋がりましたので、ベストアンサーとさせていただきます。
guest

0

解決済みですが...

目的はbitmap形式向けDCT後の、BGR->YCbCr変換

ご質問の処理は

・得たい値は浮動小数点数
・限界まで高速にしたい
・処理系がIEEE754を用いていること前提
・ビットパターンをバリバリに意識する
・この丸め論理の定義域・値域に特定の前提を置ける(NaNであったり負の値ではないなど)

といった前提を置きポータビリティーはある程度犠牲でいいぐらいの勢いなのかなと想像しました。
浮動小数点数演算がソフトウェアで実装されていた時代を経験している自分はつい「浮動小数点数演算は遅い」という意識がぬぐい切れず今だに整数演算でこちょこちょやった方が早いかもなんて考えが浮かびます。そして大抵の場合ハードウェア(プロセッサーに搭載されている浮動小数点演算ユニット的なもの)に素直に任せた方がずっと早いことを再認識するハメになります・・・orz

でもおもしろそうだったので実際に比較してみました。

C++

1#include <cassert> 2#include <cstdint> 3#include <cinttypes> 4#include <cmath> 5#include <chrono> 6#include <cstdio> 7 8double f1(double y) { 9 auto iy = static_cast<std::int64_t>(y); 10 return (iy + 128) >> 8; 11} 12 13double f2(double y) { 14 int exp; 15 double normalized = std::frexp(y + 128, &exp); 16 double y2 = std::ldexp(normalized, exp - 8); 17 return floor(y2); 18} 19 20double f3(double y) { 21 int exp; 22 double normalized = std::frexp(y, &exp); 23 double y2 = std::ldexp(normalized, exp - 8); 24 return y2; 25} 26 27double f4(double y) { 28 assert(!std::isnan(y) && !std::isinf(y) && y >= 0); 29 30 auto iy = *reinterpret_cast<std::int64_t*>(&y); 31 // 8bit右シフト(256で除算)した結果の指数部を求める 32 // 非負を仮定しているので符号ビットのマスクは省略 33 auto mantissa = iy & INT64_C(0x000F'FFFF'FFFF'FFFF); 34 auto exp = (int)(iy >> 52) - 8; 35 if (exp <= 0x3ff - 1) { 36 // 値が0.5以下なら結果は0か1 37 return exp == 0x3ff - 1 ? 1.0 : 0.0; 38 } else if (exp < 0x3ff + 52) { 39 // 仮数分に小数以下の値を含む 40 // exp 1.0の桁 41 // -------- --------------- 42 // 0x3ff 0x0010_..._0000 43 // 0x3ff+1 0x0008_..._0000 44 // 0x3ff+52 0x0000_..._0001 45 auto pos = 0x3ff + 51 - exp; // 0.5のビット位置 46 auto one_half = INT64_C(1) << pos; 47 mantissa += one_half; 48 mantissa &= ~((one_half << 1) - 1); // 小数部のビットを落とす 49 if (mantissa & INT64_C(0x0010'0000'0000'0000)) { 50 // 桁上がりしたら仮数部と指数部を補正 51 mantissa = (mantissa & INT64_C(0x000F'FFFF'FFFF'FFFF)) >> 1; 52 exp++; 53 } 54 } 55 double result; 56 *reinterpret_cast<int64_t*>(&result) = static_cast<uint64_t>(exp) << 52 57 | mantissa; 58 return result; 59} 60 61#define LOOP_COUNT 10000000 62volatile double z; 63 64double timeit(double (*fp)(double), double arg) { 65#if !defined(NDEBUG) 66 assert(false); // デバッグモードで性能測定しないように... 67#endif 68 auto t0 = std::chrono::system_clock::now(); 69 for (int i = 0; i < LOOP_COUNT; i++) { 70 z = fp(arg); 71 } 72 auto t1 = std::chrono::system_clock::now(); 73 auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0).count(); 74 return elapsed / LOOP_COUNT; 75} 76 77using namespace std; 78 79int main() { 80 double ys[] = { 81 256 * 0.499, 256 * 0.5, 82 (INT64_C(0x000F'FFFF'FFFF'FFFF)) * 256.0, 83 (INT64_C(0x000F'FFFF'FFFF'FFFF) + 0.5) * 256.0, 84 }; 85 for (int i = 0; i < sizeof(ys) / sizeof(ys[0]); i++) { 86 double y = ys[i]; 87 printf("------------------------------------\n"); 88 printf("y = %20lf\n", y); 89 printf(" f1 -> %20lf %3.0lf ns\n", f1(y), timeit(f1, y)); 90 printf(" f2 -> %20lf %3.0lf ns\n", f2(y), timeit(f2, y)); 91 printf(" f3 -> %20lf %3.0lf ns\n", f3(y), timeit(f3, y)); 92 printf(" f4 -> %20lf %3.0lf ns\n", f4(y), timeit(f4, y)); 93 } 94 return 0; 95}

本件で注意が必要なのは「四捨五入の論理」だと思います。また浮動小数点数の演算を避けるためにfrexp, ldexpのようなものを用いたとき「どのくらいオーバーヘッドがあるか」を知っておくことも無駄ではないでしょう。

上記では4つぐらい方法を試してみてます。

f1は浮動小数点数を整数化して整数演算として四捨五入&256での除算を行う方法。
f2はfrexp/ldexpと浮動小数点数の加算、floorによる切り捨てを行ったもの。
f3は四捨五入の論理を放棄して単にfrexp/ldexpにより256で割るだけのもの。
f4はポータビリティー無視でIEEE754のフォーマットを直接意識したもの。

自分のPC(Intel core i5-8400 2.8GHz)を用いて実験してみますと

bash

1$ g++ -DNDEBUG t.cpp 2$ ./a.out 3------------------------------------ 4y = 127.744000 5 f1 -> 0.000000 2 ns 6 f2 -> 0.000000 17 ns 7 f3 -> 0.499000 13 ns 8 f4 -> 0.000000 3 ns 9------------------------------------ 10y = 128.000000 11 f1 -> 1.000000 2 ns 12 f2 -> 1.000000 18 ns 13 f3 -> 0.500000 13 ns 14 f4 -> 1.000000 3 ns 15------------------------------------ 16y = 1152921504606846720.000000 17 f1 -> 4503599627370495.000000 2 ns 18 f2 -> 4503599627370495.000000 20 ns 19 f3 -> 4503599627370495.000000 13 ns 20 f4 -> 4503599627370495.000000 7 ns 21------------------------------------ 22y = 1152921504606846848.000000 23 f1 -> 4503599627370496.000000 2 ns 24 f2 -> 4503599627370496.000000 17 ns 25 f3 -> 4503599627370495.500000 13 ns 26 f4 -> 4503599627370496.000000 8 ns

結論を言えば平易な論理(f1)が一番早かったです。

frexp/ldexpのアセンブリコードを見ますとfrexp/ldexpともに関数呼び出しになっていました。f1とf3を比較しますと、浮動小数点数<->整数変換より関数呼び出し(分岐の)オーバーヘッドの方が大きいということなのかも知れません。またfrexp/ldexpの関数呼び出しオーバーヘッドを避ける意図で書いた論理(f4)は、整数演算しかしていないとはいえ、そこそこの命令数ですし分岐も含まれているせいか平易な論理(f1)にはかないませんでした。

frexp/ldexpを用い頑張って論理を書こうとするより、やはりmaisumakunさん回答のアドバイス

単に「整数にキャストしてからシフト」だけでいい

が得策のようです。f1では整数型として得た結果を再度浮動小数点数にキャストしてますが、それでもハードウェアにまかせた方が早かったので。

投稿2019/06/23 23:19

KSwordOfHaste

総合スコア18392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ikadzuchi

2019/06/24 00:45

元が右シフトのつもりだったということは四捨五入は考えず単純に -(8*2^52), &FFF0.... でよいのではないでしょうか。
KSwordOfHaste

2019/06/24 03:11

質問にY' = (Y' + 128) >> 8 という(多分整数前提の演算)がありそれを数値に対する演算と解釈するなら ・>>8は256で割ること ・128を256で割ると0.5、右シフトはあふれた桁を捨てる仕様である 以上より「256で割った結果を四捨五入」と解釈できるかと思います。 浮動小数点数IEEE754では 指数部×仮数部×2^(指数部-0x3ff) // ただし指数部は1 or -1とみなす となります。2^nを乗じたり割ったりするにはexpにnを加えたり引いたりすることで仮数部をさわらずに実現できますが前述したように四捨五入をするにあたり小数点位置の一つ右(要するに0.5)を加えた上で小数部のビット列を全てクリアする配慮が必要・・・という考えでf4の論理を作っております。
ikadzuchi

2019/06/24 14:25

おっと、本当ですね。すみません見逃していました。 四捨五入するとなると、doubleの時点で128を足してから-(8*2^52), &FFF0...ですかね。 (intに読み替えずdoubleのまま1/256を掛けて丸めるほうが速いでしょうけど)
KSwordOfHaste

2019/06/25 02:48 編集

自分の回答動機は「frexp/ldexpや浮動小数点数<->整数変換(つまり浮動小数点の演算)を使わずに頑張って論理を組もうとしてもmaisumakunさんの回答より早くなりそうにない」ことを示すことにありました。 (素朴な実験方法ではありますが)f2,f3の結果を見ると、そもそもfrexp/ldexpを使った時点で一桁遅くなるので、「frexp/ldexpのような関数呼び出しを排除する」かつ「浮動小数点演算を一切使わない」ようにすれば「f1の実装よりも早くできそうか」を見ることを主眼に考えました。f4で0.5あるいは128の加算や仮数部の小数点以下の除去を整数演算のみでやろうとしているのはそのためです。
ikadzuchi

2019/06/25 04:53

なるほどそうでしたか。「浮動小数点演算を一切使わない」などの余計な条件をつけずに「f1の実装よりも早くできそうか」を考えているのかと思ったもので。
guest

0

3番目の段階で不定の値が入るため、2番目の段階で変換に失敗している可能性。

いえ、多分あなたの認識が間違っているだけです。
double型の30788.0のビットパターンをどうシフトしても120は得られません。
int型に変換すればシフトで(int型の)120は得られます。

投稿2019/06/24 00:42

ikadzuchi

総合スコア3047

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

s_clalis

2019/06/29 06:35

回答ありがとうございます。 根本的にdoubleのビットパターンを理解していませんでした… もう一度浮動小数点型を詳しく調べてみます!
guest

0

右シフトする意図が不明ですが、1/2するだけなら単純に2で割ればいいだけの話ですね

投稿2019/06/23 14:57

y_waiwai

総合スコア87719

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問