手元に、昔の貰い物の Visual C++ 4.0 があるんですが、unsigned __int64 から
double への型変換ができません。FreeBSD 4.7-RELEASE の gcc 2.95.4 では
(表記が __int64 ではなく long long になりますが)型変換できます。
というわけで質問です。他の x86 用コンパイラ、特に Visual C++ の新しいバー
ジョンでの実装状況はどうなのでしょうか?
☆
テストに使用したコード
-----intXtod.c-----
#ifndef INTX
#error please supply -DINTX='unsigned long' or whatsoever
#endif
double intXtod(INTX a)
{
return a;
}
-------------------
コンパイル時のコマンドライン
|C:\My Documents\Devel> cl /Ox /Fonul /Fa /DINTX="unsigned __int64" intXtod.c
|intXtod.c(7) : error C2520: 'unsigned __int64' から 'double' への変換は
|インプリメントされていません。'signed __int64' を使用します。
吐き出したアセンブラソース
|_intXtod PROC NEAR
| sub esp, 16
| fild (null) PTR -16+[esp+16]
| add esp, 16
| ret 0
| npad 5
|_intXtod ENDP
/Fonul /Fa オプションを外してみると、.obj ファイルが生成されません。
もちろん、/DINTX= に "signed long"、"unsigned long"、"signed __int64" を
指定するとコンパイルが通ります。
☆
x86 の fild 命令(整数型→浮動小数点型の変換)には signed 32bit int→
80bit float と signed 64bit int→80bit float の 2 種類しかないので仕方が
ないのですが、FreeBSD の gcc で同じコードを
% gcc -O6 -S -fomit-frame-pointer -DINTX="unsigned long long" intXtod.c
でコンパイルするとすんなり通るので、MS もそのくらい頑張ってくれてもいい
のでは…と思うのです。ましてや、VC++ 4.0 は 80bit float をサポートしない
(long double も double も両方とも 64bit)ので、実装は非常に簡単なはずな
のですが…
========================================================================
飯嶋 浩光 / でるもんた・いいじま http://www.ht.sakura.ne.jp/~delmonta/
IIJIMA Hiromitsu, aka Delmonta mailto:delm...@ht.sakura.ne.jp
In article <3EB9A650...@ht.sakura.ne.jp>, IIJIMA Hiromitsu <delm...@ht.sakura.ne.jp> writes
> 手元に、昔の貰い物の Visual C++ 4.0 があるんですが、unsigned __int64 から
> double への型変換ができません。FreeBSD 4.7-RELEASE の gcc 2.95.4 では
> (表記が __int64 ではなく long long になりますが)型変換できます。
これ、割と実装がめんどうなわりに、得られるのは、64bi int の
表現のちょっとの拡大なんですよね.... singed だと思ってdouble
に変換して、そのあと適当に変換するのかな。
もし欲しいなら自分で書いちゃった方が可搬性は高いんでしょうね。
簡単なんでしょうし。
---
Shinji KONO @ Information Engineering, University of the Ryukyus,
PRESTO, Japan Science and Technology Corporation
河野真治 @ 琉球大学工学部情報工学科,
科学技術振興事業団さきがけ研究21(機能と構成)
> これ、割と実装がめんどうなわりに、得られるのは、64bi int の
> 表現のちょっとの拡大なんですよね.... singed だと思ってdouble
> に変換して、そのあと適当に変換するのかな。
>
> もし欲しいなら自分で書いちゃった方が可搬性は高いんでしょうね。
> 簡単なんでしょうし。
Workaround としては
inline double i64tod(uint64_t i)
{
if ( i < (UINT64_C(1)<<63) )
return (int64_t)i;
else
{
register double d = (int64_t)(i>>1);
#if sizeof(double) > sizeof(uint64_t)
return 2*d+1;
#else
/* 仮数部 53 ビットなので 1 を足しても無駄*/
return 2*d;
#endif
}
}
ですかね。
>x86 の fild 命令(整数型→浮動小数点型の変換)には signed 32bit int→
>80bit float と signed 64bit int→80bit float の 2 種類しかないので仕方が
>ないのですが、FreeBSD の gcc で同じコードを
>% gcc -O6 -S -fomit-frame-pointer -DINTX="unsigned long long" intXtod.c
>でコンパイルするとすんなり通るので、MS もそのくらい頑張ってくれてもいい
gcc の問題ではありませんが、FreeBSD では FPU のデフォールトの演
算精度が 64 bit です。
#最近は変わったかな? 手元のは FreeBSD 3.4 です
>のでは…と思うのです。ましてや、VC++ 4.0 は 80bit float をサポートしない
>(long double も double も両方とも 64bit)ので、実装は非常に簡単なはずな
>のですが…
コンパイラにとっては、long double を 64 bit にするのと 80 bit に
するのは、ロード/ストアの命令が変わるだけで、unsigned long long
→ floating type の変換の難易度は変わりません。
gcc 2.96 のオブジェクトを見てみたら、long double → unsigned
long long は内部関数 __fixunsdfdi を使って行なっていました。これ
の中身は見ていませんが、規格が要求している最低限を保証するだけな
ら、x を long double とすると、
(long long)(x - 9223372036854775808.L) ^ (1LL << 63)
# 9223372036854775808 は 2 の 63 乗
で済むんですが、、、
--
片山@PFU
In article <3EB9B512...@ht.sakura.ne.jp>,
IIJIMA Hiromitsu <delm...@ht.sakura.ne.jp> writes:
>> もし欲しいなら自分で書いちゃった方が可搬性は高いんでしょうね。
>> 簡単なんでしょうし。
>Workaround としては
>inline double i64tod(uint64_t i)
>{
・・・
> register double d = (int64_t)(i>>1);
>
> #if sizeof(double) > sizeof(uint64_t)
> return 2*d+1;
> #else
> /* 仮数部 53 ビットなので 1 を足しても無駄*/
> return 2*d;
> #endif
仮数部 53 bit の時は、0xほげ0…01 の時に IEEE が要請している結果
と異なりますので、まずいと思います。
条件分岐と浮動少数点加算が同程度の実行時間なら、
return (double)((int64_t)i + INT64_MIN) + 2. * (1<<31) * (1<<31);
というのがよろしいかと。
--
片山@PFU
> >inline double i64tod(uint64_t i)
> >{
> ・・・
> > register double d = (int64_t)(i>>1);
> >
> > #if sizeof(double) > sizeof(uint64_t)
> > return 2*d+1;
> > #else
> > /* 仮数部 53 ビットなので 1 を足しても無駄*/
> > return 2*d;
> > #endif
>
> 仮数部 53 bit の時は、0xほげ0…01 の時に IEEE が要請している結果
> と異なりますので、まずいと思います。
むう、やっぱり厳密にやろうとするとそうなりますか。
GCC が吐いたコードも、なにやら複雑なことをしているようです。
x86 の浮動小数点命令や GCC の内部サブルーチンのことははっきり言ってチン
プンカンプンなので、まだ解析してはいないのですが…
> 条件分岐と浮動少数点加算が同程度の実行時間なら、
>
> return (double)((int64_t)i + INT64_MIN) + 2. * (1<<31) * (1<<31);
>
> というのがよろしいかと。
アドバイスありがとうございます。利用させていただきます。
#ついでに細かいことをいうと、(1<<31) は int が 16bit だと 0 になっちゃう
#ので(そんな処理系で int64_t をサポートしているものはまずないと思います
#が)、(1UL<<31) か 0x80000000UL ですね。
> > 条件分岐と浮動少数点加算が同程度の実行時間なら、
> >
> > return (double)((int64_t)i + INT64_MIN) + 2. * (1<<31) * (1<<31);
> >
> > というのがよろしいかと。
>
> アドバイスありがとうございます。利用させていただきます。
すいません、もうひとつ重箱の隅を。
いまどきの普通のプロセッサの整数型は 2 の補数表現(-n == (not n)+1)なの
で上記で正しいという確信が持てるのですが、1 の補数表現(-n == (not n))
のプロセッサだとどうなるのでしょうか?
return (double)((int64_t)i + INT64_MIN) - (double)INT64_MIN;
で大丈夫でしょうか?
In article <3EB9E061...@ht.sakura.ne.jp>, IIJIMA Hiromitsu <delm...@ht.sakura.ne.jp> writes
> GCC が吐いたコードも、なにやら複雑なことをしているようです。
たぶん、FPPの状態コードの調整とか、NaNの処理とかいろいろ
あるんでしょう。あまり近寄りたくない...
> > return (double)((int64_t)i + INT64_MIN) + 2. * (1<<31) * (1<<31);
1<<31 って、ちゃんと浮動小数点に変換されるのかな?
> > > return (double)((int64_t)i + INT64_MIN) + 2. * (1<<31) * (1<<31);
>
> 1<<31 って、ちゃんと浮動小数点に変換されるのかな?
これは、2. をかけているから、大丈夫です :-)
>いまどきの普通のプロセッサの整数型は 2 の補数表現(-n == (not n)+1)なの
>で上記で正しいという確信が持てるのですが、1 の補数表現(-n == (not n))
>のプロセッサだとどうなるのでしょうか?
> return (double)((int64_t)i + INT64_MIN) - (double)INT64_MIN;
N ビットの 2 の補数表現では 2**N 通りの数値を表現できることを利
用しています。
1 の補数表現や絶対値表現では 2**N - 1 通りの数値しか表現できませ
んから、条件判定が必要になります。
--
片山@PFU
> > return (double)((int64_t)i + INT64_MIN) - (double)INT64_MIN;
>
> N ビットの 2 の補数表現では 2**N 通りの数値を表現できることを利
> 用しています。
>
> 1 の補数表現や絶対値表現では 2**N - 1 通りの数値しか表現できませ
> んから、条件判定が必要になります。
解説ありがとうございます。
とりあえず、片山さんの書いてくださったコードで行こうと思います。
IIJIMA Hiromitsu wrote:
> #ついでに細かいことをいうと、(1<<31) は int が 16bit だと 0 になっちゃう
自分は痛い目見たので、参考までに投稿します。
環境は cygwin です。
seo@CASTER ~/testset/c/rot_shift
$ cat rot_shift.c
#include <stdio.h>
int main(void) {
int i; for(i=30; i<35; i++) printf("%2d : %x\n", i, 1<<i);
return 0;
}
seo@CASTER ~/testset/c/rot_shift
$ gcc rot_shift.c -o rot_shift.exe
seo@CASTER ~/testset/c/rot_shift
$ ./rot_shift.exe
30 : 40000000
31 : 80000000
32 : 1
33 : 2
34 : 4
怖いですね。
たぶん、mod 32 の結果を使ってるんでしょうね。
ANSIでは未定義か不定か忘れました。
> > #ついでに細かいことをいうと、(1<<31) は int が 16bit だと 0 になっちゃう
...
> ANSIでは未定義か不定か忘れました。
あ、ゼロになるとは規定されてないんですか…しらなんだ。
> seo@CASTER ~/testset/c/rot_shift
> $ cat rot_shift.c
> #include <stdio.h>
> int main(void) {
> int i; for(i=30; i<35; i++) printf("%2d : %x\n", i, 1<<i);
> return 0;
> }
>
> seo@CASTER ~/testset/c/rot_shift
> $ gcc rot_shift.c -o rot_shift.exe
>
> seo@CASTER ~/testset/c/rot_shift
> $ ./rot_shift.exe
> 30 : 40000000
> 31 : 80000000
> 32 : 1
> 33 : 2
> 34 : 4
>
> 怖いですね。
> たぶん、mod 32 の結果を使ってるんでしょうね。
FreeBSD でアセンブラソース吐かせて確認しました。
% cat shift.c
int shift(int n)
{
return 1<<n;
}
% gcc -O6 -S -fomit-frame-pointer shift.c
% cat shift.s
(中略)
shift:
movl 4(%esp),%ecx
movl $1,%eax
sall %cl,%eax
ret
(後略)
というわけで、mod 32 を使うのは gcc が決めてることではなくて、プロセッサ
の仕様をそのまま採用した、という形ですね。このへんは Ralf Brown の Inter-
rupt List の付録の 86BUGS.LST あたりに書いてありそう。
#Intel の仕様書は読む気がしない
>> > #ついでに細かいことをいうと、(1<<31) は int が 16bit だと 0 になっちゃう
>...
>> ANSIでは未定義か不定か忘れました。
>あ、ゼロになるとは規定されてないんですか…しらなんだ。
>というわけで、mod 32 を使うのは gcc が決めてることではなくて、プロセッサ
>の仕様をそのまま採用した、という形ですね。このへんは Ralf Brown の Inter-
ドーデモイイコトデスガ、PDP-11 のシフトカウントは -32 ~ 31 でし
たから、(1<<31) は 0 になってました。マイナスの時は逆方向のシフ
トになります。
--
片山@PFU
In article <3EBA5A88...@ht.sakura.ne.jp>
IIJIMA Hiromitsu <delm...@ht.sakura.ne.jp> writes:
> > ANSIでは未定義か不定か忘れました。
>
> あ、ゼロになるとは規定されてないんですか…しらなんだ。
C のシフトカウンタが負またはオペランドサイズ以上の場合は undefined
behavior です.
また, シフトカウンタの 5 ビットマスクは 8086 には無く, 286 以降 (仮想
86 モードを含む) で採用されたと手元のマニュアルにあります.
--
Kazuo Fox Dohzono / doh...@hf.rim.or.jp