none
fesetroundの丸めモードが逆になる RRS feed

  • 質問

  • こちらでは初めての質問になります。

    次のようなプログラムの挙動について、疑問がありまして質問させて頂きます。

    #include <iostream>
    #include <cfenv>
    #include <cmath>
    
    int check_rounding()
    {
    	volatile double x, y, z;
    
    	x = 1;
    	y = pow(2., -55);
    
    	z = x + y;
    
    	if (z > 1) return 2; // up
    
    	z = x - y;
    
    	if (z == 1) return 0; // nearest
    
    	z = - x + y;
    
    	if (z == -x) return 1; // down
    
    	return 3; // chop
    }
    
    
    int main()
    {
    	std::cout << check_rounding() << "\n";
    
    	fesetround(FE_TONEAREST);
    	std::cout << check_rounding() << "\n";
    
    	fesetround(FE_DOWNWARD);
    	std::cout << check_rounding() << "\n";
    
    	fesetround(FE_UPWARD);
    	std::cout << check_rounding() << "\n";
    
    	fesetround(FE_TOWARDZERO);
    	std::cout << check_rounding() << "\n";
    }
    
    

    fesetroundで丸めモードを変えながら、丸めモードが正しく変わったかをチェックしています。

    IEEE754の規格通りなら、

    0

    0

    1

    2

    3

    と表示されるはずですが、64bitモードでコンパイルすると

    0

    0

    2

    1

    3

    と表示されてしまいます(upとdownが逆になっている)。32bitモードでコンパイルした場合は正常でした。Linux等他のOSでも確認した限り正常です。

    Visual C++ 2013 update2, Visual C++ 2013 update 5, Visual C++ 2015 update1で確認しました。コンパイルはNative Toolコマンドプロンプトからcl.exeで行いました。

    自分の勘違いでなければ、これはかなり深刻なバグなのではないでしょうか。

    どうなっているのか教えて頂ければ幸いです。


    2016年3月2日 23:10

回答

  • これは不具合のようです。

    海外のフォーラムでも上がっています。
    Peculiarities in the new C99 fenv.h?

    int main()
    {
    	std::cout << check_rounding() << "\n";
    
    	//fesetround(FE_TONEAREST);
    	_controlfp(_RC_NEAR, _MCW_RC);
    	std::cout << check_rounding() << "\n";
    
    	//fesetround(FE_DOWNWARD);
    	_controlfp(_RC_DOWN, _MCW_RC);
    	std::cout << check_rounding() << "\n";
    
    	//fesetround(FE_UPWARD);
    	_controlfp( _RC_UP , _MCW_RC );
    	std::cout << check_rounding() << "\n";
    
    	//fesetround(FE_TOWARDZERO);
    	_controlfp(_RC_CHOP, _MCW_RC);
    	std::cout << check_rounding() << "\n";
    }

    上記のように_controlfp関数を使うと正しく出力を得られます。
    それぞれ定数が以下のようになってますので、

    #define FE_TONEAREST    0x0000
    #define FE_UPWARD       0x0100
    #define FE_DOWNWARD     0x0200
    #define FE_TOWARDZERO   0x0300
    
    #define _RC_NEAR        0x00000000     /*   near */
    #define _RC_DOWN        0x00000100     /*   down */
    #define _RC_UP          0x00000200     /*   up   */
    #define _RC_CHOP        0x00000300     /*   chop */
    FE_DOWNWARDとFE_UPWARDの値が逆なのではないかと思います。

    • 回答の候補に設定 Hongliang 2016年3月3日 2:16
    • 回答としてマーク ms_kashi 2016年3月3日 3:39
    2016年3月3日 1:23

すべての返信

  • これは不具合のようです。

    海外のフォーラムでも上がっています。
    Peculiarities in the new C99 fenv.h?

    int main()
    {
    	std::cout << check_rounding() << "\n";
    
    	//fesetround(FE_TONEAREST);
    	_controlfp(_RC_NEAR, _MCW_RC);
    	std::cout << check_rounding() << "\n";
    
    	//fesetround(FE_DOWNWARD);
    	_controlfp(_RC_DOWN, _MCW_RC);
    	std::cout << check_rounding() << "\n";
    
    	//fesetround(FE_UPWARD);
    	_controlfp( _RC_UP , _MCW_RC );
    	std::cout << check_rounding() << "\n";
    
    	//fesetround(FE_TOWARDZERO);
    	_controlfp(_RC_CHOP, _MCW_RC);
    	std::cout << check_rounding() << "\n";
    }

    上記のように_controlfp関数を使うと正しく出力を得られます。
    それぞれ定数が以下のようになってますので、

    #define FE_TONEAREST    0x0000
    #define FE_UPWARD       0x0100
    #define FE_DOWNWARD     0x0200
    #define FE_TOWARDZERO   0x0300
    
    #define _RC_NEAR        0x00000000     /*   near */
    #define _RC_DOWN        0x00000100     /*   down */
    #define _RC_UP          0x00000200     /*   up   */
    #define _RC_CHOP        0x00000300     /*   chop */
    FE_DOWNWARDとFE_UPWARDの値が逆なのではないかと思います。

    • 回答の候補に設定 Hongliang 2016年3月3日 2:16
    • 回答としてマーク ms_kashi 2016年3月3日 3:39
    2016年3月3日 1:23
  • 本質的には、#defineのナマ値は関数の仕様自体とは無関係なので、64bitライブラリの瑕疵と言うべきかもしれません。
    32bitは正常に動作してますし。でもまぁ逆なんでしょうね。
    2016年3月3日 2:02
  • 同意見です。#defineの値は32bit / 64bit共通ですから入れ替えられるものでもありませんし。fesetround周りのソースコードはVisual Studioに含まれていなさそうですね。
    2016年3月3日 2:38
  • 皆様、ご回答ありがとうございます。

    いろいろ検索したのですが、海外のフォーラムで話題になっていたのは気づきませんでした。ありがとうございます。

    丸めモードを決めるレジスタでのbitの並びは、FPU, SSEともに

    00: near

    01: down

    10: up

    11: chop

    なので、マクロのdefine間違いかと思いきや、32bitでは正常なので不思議ですね。32bitのライブラリではマクロで逆にしたのをわざわざもう一度直していて、64bitでは直すのを忘れたんでしょうか。

    多くの環境で動くように書きたいので、できれば標準規格であるfesetroundを使いたいところですが、MSVCでは当面controlfpを使うようにします。

    2016年3月3日 3:38