木村です.

> 鶴谷さん [FreeBSD-users-jp 91686] Re: long double の bug ? について 

> おそらくは、
> http://gcc.gnu.org/bugzilla/show_bug.cgi?id=17778
> が原因でgccにこんなコードが挿入されたのだと思うのですが、
  ここでは,fpsetprec(FP_PE); のことが一言も出てこないのが
奇妙です.ここに報告された問題はむしろそちらに関係しています.
従って,このテストコードをそのまま使うと,私の報告した定数の
問題と fpsetprec(FP_PE) の問題とが混在することになります.

> しかし、よくわからない問題が残っています。
> 上述のバグレポートで提示されているテストコードをビルドした結果を見ると、
> ほかのOSでの結果と異なります。
> Debian : -m96bit-long-doubleなどのオプションがあっても無くても、
>       0xc.90fdaa22168c4p-2
>       0xc.90fdaa22168c4p-2
> FreeBSD : -m96bit-long-doubleなどのオプションがあっても無くても、
>       0xc.90fdaa22168c4p-2
>       0xc.90fdaa22168cp-2

  うちの FreeBSD 6.2 + gcc 3.4.6 では 96, 128bit で違います.
96bit:
c9 0f da a2 21 68 c0 00  : 0xc.90fdaa22168cp-2   <- 最初から精度が低い
c9 0f da a2 21 68 c0 00  : 0xc.90fdaa22168cp-2

128bit:
c9 0f da a2 21 68 c4 00  : 0xc.90fdaa22168c4p-2
c9 0f da a2 21 68 c0 00  : 0xc.90fdaa22168cp-2   <- 演算により精度が落ちる

このコードに fpsetprec(FP_PE); を加えると,演算による
精度の低下はなくなります.
96bit:
c9 0f da a2 21 68 c0 00  : 0xc.90fdaa22168cp-2
c9 0f da a2 21 68 c0 00  : 0xc.90fdaa22168cp-2  <- こちらは判別できない

128bit:
c9 0f da a2 21 68 c4 00  : 0xc.90fdaa22168c4p-2
c9 0f da a2 21 68 c4 00  : 0xc.90fdaa22168c4p-2

  それに加えて,d を定数の代入でなく sscanf() を使って
「初期化」すると,96, 128bit の差は無くなります.
(数値が違うのは,読み込ませた数値が違うから.)
c9 0f da a2 21 68 c2 35  : 0xc.90fdaa22168c235p-2
c9 0f da a2 21 68 c2 35  : 0xc.90fdaa22168c235p-2

ちなみに,上述の Web のテストコードを次のようにいじりました.
printf() で数値を出すよりも,ana_ldouble() を使って中身を
hexa で出した方が,比較検討が容易だと思います.

#include <stdio.h>
#include <ieeefp.h>

/*  function prototypes  */
void ana_ldouble( long double x );

int main(void)
{
#if 1  /*  1 : original  */
   volatile long double d = 0x3.243f6a8885a31p0L;
#else
   long double d;

   sscanf("3.14159265358979323846L","%Lf",&d);
#endif

#if 1    /*  0 : OFF, 1 : ON  */
   fpsetprec(FP_PE);
#endif

   ana_ldouble(d);   printf("%La\n", d);

   d = 2.0L * d / 2.0L;

   ana_ldouble(d);   printf("%La\n", d);

   return(0);
}


void ana_ldouble( long double x )  /*  for 80 bits  */
{
   int    i;
   unsigned int s;
   typedef union {  long double     l;
                    unsigned char   c[16]; }  exd;
   exd e;

   for ( i = 0; i < 16; i++ )  e.c[i] = '\0';           /*  clear  */
   e.l = x;

   s = sizeof(long double);
   for ( i = 7; i >= 0; i-- )  printf("%02x ",e.c[i]&0xff);
   printf(" : ");
}


> 試しに、FreeBSD 6.3で、ports/lang/gcc42に、
(中略)
> -#define TARGET_96_ROUND_53_LONG_DOUBLE (!TARGET_64BIT)
> +#define TARGET_96_ROUND_53_LONG_DOUBLE 0
> のようなパッチを当ててビルドして使ってみると、
> -m96bit-long-doubleと-m128bit-long-doubleの出力の違いはなくなりました。
  大変な検討をしていただき申し訳ありません.それにしても,
これだと意図的にやっている,すなわち「bug でなく仕様」なので
すね.だとしても,対象が 96bit のみだとか,sscanf() で読み込ませ
れば精度は落ちないとか,一貫性に欠ける仕様のように思えます.
  あと,Linux (Debian) や cygwin や Mac ではこれをやっていない
わけで,FreeBSD だけこれをやる意味はないと思います.
(合理的な説明が出てこない限りは.)

                     Satoshi Kimura  
([&#x30E1;&#x30FC;&#x30EB;&#x30A2;&#x30C9;&#x30EC;&#x30B9;&#x4FDD;&#x8B77;])

メールによる返信