読者です 読者をやめる 読者になる 読者になる

浮動小数点数を2の乗数で割る

C++

a/bをする場合、bが2の乗数であれば「aの指数 - log2(b)」で除算ができます。

#include <iostream>
#include <bitset>
#include <cstdint>
#include <cassert>
#include <cmath>

union SingleFloat {
    float value;
    struct {
        int fraction : 23;
        int exponent : 8;
        bool sign : 1;
    } parts;
};

SingleFloat divide(SingleFloat a, std::uint32_t b)
{
    // 2の乗数のみ受け付ける
    assert(std::bitset<32>(b).count() == 1);

    SingleFloat result;
    result.parts.fraction = a.parts.fraction;
    result.parts.exponent = a.parts.exponent - std::log2(b);
    result.parts.sign = a.parts.sign;
    return result;
}

SingleFloat make_by_float(float x)
{
    SingleFloat result;
    result.value = x;
    return result;
}

int main()
{
    // 8/4 == 2
    std::cout << divide(make_by_float(8.0f), 4).value << std::endl;

    // 9/4 == 2.25
    std::cout << divide(make_by_float(9.0f), 4).value << std::endl;

    // 24/8 == 3
    std::cout << divide(make_by_float(24.0f), 8).value << std::endl;
}

出力:

2
2.25
3

ここでは、aが0、非正規化数、無限大、NaNの場合については考慮していません。

追記(2016/12/27 17:45)

もうちょっと真面目に実装し、上述した未考慮のケースに対応したコードはこちら:

#include <iostream>
#include <bitset>
#include <cstdint>
#include <cassert>
#include <cmath>

union SingleFloat {
    float value;
    struct {
        int fraction : 23;
        int exponent : 8;
        bool sign : 1;
    } parts;
};

SingleFloat make_by_float(float x)
{
    SingleFloat result;
    result.value = x;
    return result;
}

SingleFloat divide(SingleFloat a, std::uint32_t b)
{
    // 2の乗数のみ受け付ける
    assert(std::bitset<32>(b).count() == 1);

    if (std::isnan(a.value))
        return make_by_float(a.value);

    if (std::isinf(a.value))
        return make_by_float(a.value);

    int exponent_x = a.parts.exponent;
    int exponent_y = std::log2(b);
    if (exponent_x <= exponent_y) {
        // 0や非正規化数のように指数が小さいaをなにかの値で割るとアンダーフローするので、0を返す
        return make_by_float(0.0f);
    }
    else {
        SingleFloat result;
        result.parts.fraction = a.parts.fraction;
        result.parts.sign = a.parts.sign;
        result.parts.exponent = exponent_x - exponent_y;
        return result;
    }
}

int main()
{
    std::cout << " 8/4 = " << divide(make_by_float(8.0f), 4).value << std::endl;
    std::cout << " 9/4 = " << divide(make_by_float(9.0f), 4).value << std::endl;
    std::cout << "24/8 = " << divide(make_by_float(24.0f), 8).value << std::endl;

    std::cout << " 0/2 = " << divide(make_by_float(0.0f), 2).value << std::endl;
    std::cout << "-0/2 = " << divide(make_by_float(-0.0f), 2).value << std::endl;

    using limits = std::numeric_limits<float>;
    std::cout << "denorm_min/2 = " << divide(make_by_float(limits::denorm_min()), 2).value << std::endl;
    std::cout << "NaN/2 = " << divide(make_by_float(-limits::quiet_NaN()), 2).value << std::endl;
    std::cout << "inf/2 = " << divide(make_by_float(limits::infinity()), 2).value << std::endl;
    std::cout << "-inf/2 = " << divide(make_by_float(-limits::infinity()), 2).value << std::endl;
}

出力:

 8/4 = 0
 9/4 = 0
24/8 = 0
 0/2 = 0
-0/2 = 0
denorm_min/2 = 0
NaN/2 = -nan
inf/2 = inf
-inf/2 = -inf

参照

Boost 1.63.0がリリースされました

C++

Boost 1.63.0がリリースされました。リリースノートはいつものように、翻訳・情報補完したものをboostjpサイトで公開しています。

このバージョンは、まだリリースされていないVisual C++ 2017には対応していませんのでご注意ください。

前バージョンである1.62.0のリリースが遅れたこともあり、1.63.0がリリースされるまでの期間は短めになっていました。そのため、小規模な修正版になっています。

Nが2の何乗かを調べる

C++

C++11から標準数学ライブラリに入ったstd::log2()関数を使えば、Nが2の何乗かを取得できます。

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::log2(4.0) << std::endl;
    std::cout << std::log2(8.0) << std::endl; // 8は2の3乗
    std::cout << std::log2(16.0) << std::endl; // 16は2の4乗
    std::cout << std::log2(32.0) << std::endl;
    std::cout << std::log2(64.0) << std::endl;
    std::cout << std::log2(128.0) << std::endl;
    std::cout << std::log2(256.0) << std::endl;

    std::cout << std::log2(24.0) << std::endl;
    int x = 24;
    if (x <= 0 || (x & (x - 1)) != 0) { // xが2の乗数か判定
        std::cout << "24 is not power of 2" << std::endl;
    }
}

出力:

2
3
4
5
6
7
8
4.58496
24 is not power of 2

修正履歴

  • 2016/12/26 19:20 : 2の乗数かの判定に、std::modf()を使用していましたが、
double integral_part;
if (std::modf(std::log2(24.0), &integral_part) != 0) {
    std::cout << "24 is a not power of 2" << std::endl;
}

@haxeさんに教えていただいて、以下のように修正しました。

int x = 24;
if ((x & (x - 1)) != 0) {
    std::cout << "24 is not power of 2" << std::endl;
}
  • 2016/12/26 21:06 : uskzさんからの指摘を受け、例としてxstd::numeric_limits::min()の場合に2の乗数と判定されてしまうため、暫定対処として正の値かどうかのチェックを追加しました。
  • 2016/12/26 23:24 : uskzさんからの指摘を受け、2の乗数と判定するコードに0を許可していたところを、許可しないよう修正しました。

C++11 標準ストリームへの出力はスレッドセーフ

C++

本記事の本題とする、「一回の書き込みにすれば排他制御は必要ない」という部分が誤りであったため、本記事を取り下げます。

C++11から標準ライブラリに、並行プログラミングのための各種機能が入った影響は、入出力のライブラリにもあります。スレッドの存在を前提とした規定が追加されています。

[iostream.objects.overview] 27.4.1 p4には、以下のようにあります:

Concurrent access to a synchronized (27.5.3.4) standard iostream object’s formatted and unformatted input (27.7.2.1) and output (27.7.3.1) functions or a standard C stream by multiple threads shall not result in a data race (1.10). [ Note: Users must still synchronize concurrent use of these objects and streams by multiple threads if they wish to avoid interleaved characters. —end note ]

同期された標準iostreamオブジェクトの書式化された・されていない入力と出力の関数、および標準Cストリームに対する複数スレッドによる並行アクセスは、データ競合を引き起こさない。[注: 交互的な文字を避けるには、これらのオブジェクトとストリームの並行使用を複数のスレッドで同期させておく必要がある。]

最初の一文での「同期」とは、並行プログラミングでの同期ではなく、標準入力と標準出力の同期のことを指します。デフォルトで同期されます。パフォーマンスのために同期を意図的に外すこともあります。

注釈のところにある「交互的な文字」とは、スレッド1がos << 1 << 2;、スレッド2がos << 3 << 4;と書き込む場合に、出力順が13241234のように不定になり、スレッド間の出力が混在することを指します。複数スレッドからの並行アクセスによってiostreamのオブジェクトがおかしな状態になったりはしませんが、複数回の書き込みでの順序保証はありません。

結果が交互的になることを避けたい場合には、os << 1 << 2;os << 3 << 4;をそれぞれ一度で書き込む必要があります。そのためには、文字列フォーマットの関数(Boost.Formatや、fmtlibなど)や、文字列ストリームなどを使用することで解決します。

void thread1()
{
    // 解決策1 : 文字列フォーマット
    os << fmt::format("{0}{1}", 1, 2);
}

void thread2()
{
    // 解決策2 : 文字列ストリーム
    std::stringstream ss;
    ss << 3 << 4;
    os << ss.str();
}

このようにすることで、出力は必ず12343412のどちらかになります。もちろん、ミューテックスを使用して一連の書き込みの順序を保証してもかまいません。

参照

C++1z if constexpr文

C++

C++1zから、コンパイル時条件によって分岐するif constexpr文が導入されます。これにより、再帰やヘルパ関数を書かなくて済むケースが多くなります。

D言語にあるstatic if文のようなものです。

template <class T, class... Rest>
void g(T&& p, Rest&&... rs)
{
    if constexpr (sizeof...(rs) > 0) {
        g(rs...); // rs...が空のときのオーバーロードが不要
    }
}

elseの方にはconstexprは必要ありません。 テンプレート内で条件分岐した場合は、到達しなかったブロックはインスタンス化されません。

ただし、条件式内でのコンパイル時条件については全てインスタンス化され、短絡評価によって後ろの方の条件がインスタンス化されないことを期待するようなコードは書けないので注意してください。

if constexpr (has_value_type_v<T> && typename T::value_type())

このような条件式は、else節に進む場合にコンパイルエラーになります。このような条件を書きたい場合は、条件分岐を入れ子にする必要があります。

if constexpr (has_value_type_v<T>) {
    if constexpr (typename T::value_type()) {
        …
    }
}

なお、constexpr ifでなくif constexprになっているのは、前者を採用すると構文として、else ifを書くときにconstexprが二重に必要になってしまうのを避けるためです。

// 採用された構文
if constexpr (cond) {
}
else if constexpr (cond) {
}
else {
}
// 却下された構文
constexpr if (cond) {
}
constexpr else constexpr if (cond) {
}
constexpr else {
}

最初に考えられていたstatic if宣言と違って、if constexpr文はスコープを導入します。また、型や関数を定義する条件分岐には使用できません。

// こういうことはできない。
// 条件によって関数や型の宣言・定義を変えるようなことはできないし、
// クラススコープでif constexpr文は使用できない
struct X {
    if constexpr (cond) {
        void f();
        using int32 = int;
    }
    else {
        void g();
    }
};

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z 非推奨だった古い例外仕様を削除

C++

C++11でnoexceptが導入されたことによって非推奨になっていたthrowキーワードによる関数の例外仕様が、C++1zで削除されます。

throwキーワードによる例外仕様とは、void f() throw(std::runtime_error)のように、その関数がどの種類の例外を送出する可能性があるかを列挙する機能です。Javaの検査例外に近いものです。これはジェネリックプログラミングと相性が悪いことで問題視されていました。

C++1z以降、throwキーワードを使用した例外仕様のコードはコンパイルが通らなくなりますのでご注意ください。throw()noexceptと等価な効果を持つ機能として、非推奨のまま残ります。例外送出のthrowは変わりません。

// C++11から非推奨だがC++14まで合法。C++1zからコンパイルエラー
//void f() throw(std::runtime_error);

// C++11からは、noexceptを付けないことにより
// 「なんらかの例外が送出される可能性がある」ことを表現するか、
void f();

// コンパイル時の条件式によって、なんらかの例外が送出される可能性の有無を指定する
void f() noexcept(cond);

throwキーワードによる例外仕様の削除にともない、関連して非推奨だった以下の機能が削除されます:

これらの機能は、例外仕様で指定されていない例外が送出された場合に発生するエラーを検知するためのものですが、noexceptキーワードによる例外仕様では、例外は送出されるかされないかのみで、なんの例外が送出される可能性があるかは扱わないため、この種のエラーは発生しません。

参照

修正履歴

  • コメント欄での指摘を受け、throw()を使用したコードがC++1zからコンパイルエラーになる記述を削除。throw()が非推奨なまま残ることを記載

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z 例外仕様を型システムの一部にする

C++

C++1zからは、noexceptによる関数の例外仕様が、型の一部として扱われるようになります。

互換性のためにnoexceptな関数ポインタから非noexceptな関数ポインタには変換できます。しかし、非noexceptな関数ポインタからnoexceptな関数ポインタには、変換できません。

void f() noexcept;
void g();

void(*p1)() noexcept = f;   // OK
void(*p2)() = f;            // OK : noexceptから非noexceptへの変換
//void(*p3)() noexcept = g; // コンパイルエラー : 非noexceptからnoexceptに変換できない

これは、ラムダ式も同様になります。

void(*p1)() noexcept = []() noexcept {}; // OK
void(*p2)() = []() noexcept {};          // OK
//void(*p3)() noexcept = [](){};         // コンパイルエラー

noexceptの違いによって関数をオーバーロードすることはできません。(戻り値の型と同様)

この仕様による破壊的な影響として、C++14まで有効だった以下のコードは、C++1zではコンパイルエラーになります:

void g1() noexcept;
void g2();

template<class T>
int f(T*, T*);

int x = f(g1, g2); // コンパイルエラー : g1とg2の型が一致しない (関数テンプレートの推論失敗)

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++11 最近接偶数への丸め

C++

C++11では<cmath>にいろいろ関数が追加されていますが、丸め演算も増えています。

C++11で最近接偶数への丸めを使いたい場合は、std::nearbyint() (near by int)という関数がありますので、それを使いましょう。

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::nearbyint(2.5) << std::endl; // 2が出力される
}

四捨五入も最近接偶数への丸めも、どちらも大きなくくりでは「round to nearest integer (最も近い整数への丸め)」に該当します。この2つの丸め方式は、中間値(0.5)の場合に動作が異なります:

  • 四捨五入は中間値の場合に、ゼロから遠い方向に丸められる (2.5だったら3になる)
  • 最近接偶数への丸めは中間値の場合に、偶数方向に丸められる (2.5だったら2になる)

最近接偶数への丸めの必要性は、「「銀行家の丸め」とは何か - 会計SEのメモ」このあたりの記事がわかりやすいでしょう。

std::nearbyint()関数の実際の動作として、四捨五入になるか最近接偶数への丸めになるかは、ISO/IEC 60559 (IEEE 754)規格の推奨によって決まります。この規格ではround to nearestの丸め方式として最近接偶数への丸めを推奨しているため、この規格に準拠した環境では、std::nearbyint()関数は最近接偶数への丸めとして動作します。

補足として、

  • std::nearbyint()関数は、スレッドローカルな丸めモード状態に依存して丸め方式が変わるので注意
  • 似た動作の関数としてstd::rint()というものもあるが、丸めが発生したときに浮動小数点例外が発生するので使いにくそう。ISO/IEC 60559 (IEEE 754)規格で、std::nearbyint()関数とstd::rint()関数両方の動作が定義されているらしい

その他C++11での丸め方式

  • 昔からあるstd::ceil()std::floor()は切り上げ、切り下げと呼ばれるもので、より正確な動作の説明としては「正の無限大方向への丸め」「負の無限大方向への丸め」と言う
  • C++11から追加されたstd::trunc()は切り捨てだが、std::floor()と違って「ゼロ方向への丸め」というものになっている。この2つは、負の値に対する動作が違うので注意
  • C++11から追加されたstd::round()は四捨五入と決まっている。最近接偶数への丸めではないので注意

C++1z 畳み込み式

C++

C++1zでは、「畳み込み式 (Fold expressions)」という機能が入ります。これは可変引数テンプレートのパラメータパックに対して集計操作を行うためのものです。

たとえば、整数のシーケンスを与えてその合計値を求める計算は、以下のように書けます:

#include <iostream>

template <class... Args>
int sum(Args... args)
{
    // 「... @ args」が畳み込み式。@の部分は任意の二項演算子
    return (... + args);
}

int main()
{
    int x = sum(1, 2, 3, 4, 5);
    std::cout << x << std::endl; // 15
}

このような計算はこれまで再帰を使用して実装する必要がありましたが、より簡潔に書けるようになりました。

ここでは、argsパラメータパックに含まれる全要素を、+二項演算子を使って集計しています。...演算子の左辺に置いているので、左から畳み込みが行われます。計算過程としては、以下のようになります:

1 + 2
3 + 3
6 + 4
10 + 5

...演算子の右辺に置くと、右から畳み込まれます:

template <class... Args>
int sum(Args... args)
{
    return (args + ...);
}

計算過程:

4 + 5
3 + 9
2 + 12
1 + 14

accumulate()アルゴリズムと同様に、初期値をくっつけることもできます:

template <class... Args>
int sum(Args... args)
{
    return (0 + ... + args);
}

畳み込み式で使用できる演算子

畳み込み式で使用できるのは、以下の二項演算子です:

+  -  *  /  %  ^  &  |  ~  =  <  >  <<  >>
+=  -=  *=  /=  %=  ^=  &=  |=  <<=  >>=
==  !=  <=  >=  &&  ||  ,  .*  ->*

前述した例の+演算子以外ですと、たとえば条件式のシーケンスに対して「全てtrueか」「いずれかがtrueか」「全てfalseか」を調べる関数は、以下のように書けます:

#include <cassert>

// 全てのパラメータがtrueならtrueを返す
template <class... Args>
bool all(Args... args)
{
    return (true && ... && args);
}

// いずれかのパラメータがtrueならtrueを返す
template <class... Args>
bool any(Args... args)
{
    return (... || args);
}

// 全てのパラメータがfalseならtrueを返す
template <class... Args>
bool none(Args... args)
{
    return !any(args...);
}

int main()
{
    bool a = all(true, true, true);
    bool b = any(false, true, false);
    bool c = none(false, false, false);

    assert(a == true);
    assert(b == true);
    assert(c == true);
}

パラメータパックが空の場合のデフォルト値

パラメータパックがからの場合のデフォルト値は、演算子によって変わります:

演算子
* 1
+ int()
& -1
| int()
&& true
|| false
, void()

この表に記載されていない演算子を空のパラメータパックに対して適用すると、コンパイルエラーになります。

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z トライグラフを削除

C++

C言語から引き継がれた「トライグラフ (trigraph)」という機能は、ASCIIより小さなISO/IEC 646という文字コードでもプログラムが書けるように用意された機能で、いくつかの文字の代替表現を使用できるようにするものです。

トライグラフ表現 置き換え後の文字
??= #
??/ \
??' ^
??( [
??) ]
??! |
??< {
??> }
??- ~

以下のトライグラフを使用したコードは、

??=define arraycheck(a,b) a??(b??) ??!??! b??(a??)

以下のように置き換えられます:

#define arraycheck(a,b) a[b] || b[a]

文字コードの問題がなくなった現代では、トライグラフは不要になりました。ユーザーにとってわかりにくいこともあり、多くのコンパイラがトライグラフをデフォルトで無効にしたり警告を出力したりといった対応をしていました。

C++1zの策定作業にともない、トライグラフの利用状況が大規模に調査されました。この機能が使用されていることはほとんどないということがわかり、C++1zからはトライグラフが削除されることが決定しました。

今後、トライグラフを使用したC++コードはコンパイルが通らなくなりますので、注意してください。C言語の方はC11時点でまだ残っています。

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。