誤差を出さずに整数計算をしたいです

このQ&Aのポイント
  • 整数計算において誤差を出さずに0~18446744073709551615までの整数値を得る方法について教えてください。
  • 計算に誤差が出てしまい意図した出力結果が得られません。方法1では丸め誤差が発生し、方法2では情報落ちが起きています。
  • 具体的な解決方法を教えていただけますか?
回答を見る
  • ベストアンサー

誤差を出さずに整数計算をしたいです

いつもお世話になっております。 0~18446744073709551615までの整数値を計算して出力するプログラムを作成しております。 しかし、計算に誤差が出てしまい意図した出力結果が得られません。 試してみた方法と出力は以下のとおりです。 ---------------------------------------------------------------------- [方法1] /* 18446744073709551615に0.0~1.0までの乗算 */ #define RANGE_MAX 18446744073709551615ULL #define MAGNIFICATION 1.0 /* 0.0から1.0 */ printf("%0.lf", RANGE_MAX * MAGNIFICATION); [出力1] 0~18446744073709552000 ---------------------------------------------------------------------- [方法2] /* 562949953421311に0.0~1.0までの乗算した結果と 562949953421312に0.0~1.0までの乗算した結果を 32767回加算した結果を加算する */ #define RANGE_MAX 562949953421312ULL #define MAGNIFICATION 1.0 /* 0.0から1.0 */ #define CYCLE 32767 unsigned long long int y = (RANGE_MAX - 1) * MAGNIFICATION; int loop; for(loop = 0; loop < CYCLE; loop++){ y += RANGE_MAX * MAGNIFICATION; } printf("%llu \n", y); [出力2] 0~9223372036854775808 ---------------------------------------------------------------------- 方法1は丸め誤差が原因だと考え方法2を試してみました。 しかし、方法2では情報落ちになってしまうのか、途中から計算されない 模様です。 誤差を出さずに計算によって0~18446744073709551615までの整数を 得るにはどのようにすれば良いのかご教授願います。

質問者が選んだベストアンサー

  • ベストアンサー
  • sakusaker7
  • ベストアンサー率62% (800/1280)
回答No.7

あーすいません。誤差出てますね。 まあ *1.0 なら取り除いてもいいじゃないかと思うんですが。 #include <stdio.h> #include <stdlib.h> #define RANGE_MAX 562949953421312ULL #define MAGNIFICATION 1.0 /* 0.0から1.0 */ #define CYCLE 32767 int main() { unsigned long long int y = (RANGE_MAX - 1); unsigned long long int y2 = y; int loop; for(loop = 0; loop < CYCLE; loop++){ y += RANGE_MAX; y2 += #ifdef USE_CAST (unsigned long long) #endif (RANGE_MAX * MAGNIFICATION); if (loop % 2048 ==0 || loop > 32760) printf("%05d %21llu %21llu \n", loop, y, y2); if (y != y2) { printf("y!=y2\n%05d %21llu %21llu\n", loop, y, y2); break; } } printf("%llu \n", y); printf("\n\n%llu \n", ~0ULL); printf("%llu \n", ~0ULL - RANGE_MAX); return 0; } gcc でコンパイルしたものをアセンブリ言語レベルでみると L2: cmpl $32766, -28(%ebp) jg L3 leal -16(%ebp), %eax addl $0, (%eax) adcl $131072, 4(%eax) fildq -24(%ebp) fstpt -56(%ebp) cmpl $0, -20(%ebp) jns L5 fldt LC0 fldt -56(%ebp) faddp %st, %st(1) fstpt -56(%ebp) L5: fldt -56(%ebp) fstpl -40(%ebp) fldl -40(%ebp) fldl LC1 faddp %st, %st(1) fstpl (%esp) call ___fixunsdfdi movl %eax, -24(%ebp) movl %edx, -20(%ebp) movl -28(%ebp), %eax andl $2047, %eax testl %eax, %eax je L7 cmpl $32760, -28(%ebp) jg L7 jmp L6 L7: 加算で浮動小数点命令を使ってます。そして 00000 1125899906842623 1125899906842623 y!=y2 00015 9570149208162303 9570149208162304 9570149208162303 loop が15のところで食い違いがでてます。 つまり、RANGE_MAX を16個足しこんだところで double の精度を超えちゃっているので誤差が出たと。 で、上のソースの、MAGNIFICATIONを使わない加算と、 加算の前にMAGNIFICATIONを乗算した結果を unsigned long long で キャストするようにしたものは期待通りの値になってるんじゃないですか? つまりは浮動小数点演算が出てくるようにしちゃだめってことです。 あるいは long double がdoubleよりも精度のよい型であるような処理系なら キャストなどしなくてもうまく行くかもしれません。 >#define MAGNIFICATION 1.0 /* 0.0から1.0 */ → #define MAGNIFICATION 1.0L /* 0.0から1.0 */

sora_naegino
質問者

お礼

お礼が遅くなりまして申し訳ございません。 >9570149208162303 >loop が15のところで食い違いがでてます。 >つまり、RANGE_MAX を16個足しこんだところで double の精度を超えちゃっているので誤差が出たと。 "y += RANGE_MAX * MAGNIFICATION;"と書いてしまうと "y = y + (unsigned long long int)(RANGE_MAX * MAGNIFICATION);" と解釈してもらえず "y = (double)y + (RANGE_MAX * MAGNIFICATION);" という感じになっていたのですね。 以下のようにキャストを使用するとうまく計算できるようになりました。 y += RANGE_MAX * MAGNIFICATION; → y += (unsigned long long int)(RANGE_MAX * MAGNIFICATION); ありがとうございました。

その他の回答 (6)

  • sakusaker7
  • ベストアンサー率62% (800/1280)
回答No.6

結局はどういう結果がほしいのかという#5での補足要求になるわけですけど、 方法2について MAGNIFICATION が1であれば演算誤差は出てないですよ。 #0も誤差がないといえばないけど意味がないので除外 #include <stdio.h> #include <stdlib.h> #define RANGE_MAX 562949953421312ULL #define MAGNIFICATION 1.0 /* 0.0から1.0 */ #define CYCLE 32767 int main() { unsigned long long int y = (RANGE_MAX - 1) * MAGNIFICATION; int loop; for(loop = 0; loop < CYCLE; loop++){ y += RANGE_MAX * MAGNIFICATION; if (loop % 2048 ==0 || loop > 32760) printf("%05d %21llu \n", loop, y); } printf("%llu \n", y); printf("\n\n%llu \n", ~0ULL); printf("%llu \n", ~0ULL - RANGE_MAX); return 0; } 00000 1125899906842623 02048 1154047404513689600 04096 2306968909120536576 06144 3459890413727383552 08192 4612811918334230528 10240 5765733422941077504 12288 6918654927547924480 14336 8071576432154771456 16384 9224497936761618432 18432 10377419441368465408 20480 11530340945975312384 22528 12683262450582159360 24576 13836183955189006336 26624 14989105459795853312 28672 16142026964402700288 30720 17294948469009547264 32761 18443929323942445056 32762 18444492273895866368 32763 18445055223849287680 32764 18445618173802708992 32765 18446181123756130304 32766 0 0 18446744073709551615 18446181123756130303 0.xという数値をMAGNIFICATIONに使った場合は小数部分が掛け算で発生するので そこで誤差が生じるというのはすでに述べたとおりです。 > この値を32768(2^15乗)回だけ加算すれば、 > 18446744073709551616になると考えたものが方法2です。 とのことですけど、MAGNIFICATIONを変化させることによってなにを得ようとしたのでしょうか? あと念のため、18446744073709551616 だと 2^64 なのでお使いの処理系の unsigned long long を使っても最大値を飛び越して0になっちゃいますよ。 64bit 長の unsigned な数値は 0 ~ 2^64-1 です。~2^64じゃないです。

sora_naegino
質問者

補足

回答して頂いた皆様に誤解を与える質問をしてしまった為、 質問内容を訂正いたします。 [質問内容] "(2^64-1) * 1.0"の計算結果が意図した結果(2^64-1)にならない。 "1.0"で乗算した時に誤差が出ないように計算したい。 "(2^64-1) * 0.x"の計算で誤差が出ないのは無理なので質問にミスがあったことをお詫びいたします。 >00000 1125899906842623 この時の変数yは(562949953421312 - 1) + (562949953421312 * 1) と同じ値であり、意図通りです。 >02048 1154047404513689600 この時の変数yは(562949953421312 - 1) + (562949953421312 * 2049) と違う値(1だけ大きい)であり、意図通りではないです。 "y += RANGE_MAX * MAGNIFICATION;"という計算では誤差が出る時と 出ない時があるという事なのでしょうか?

  • Oh-Orange
  • ベストアンサー率63% (854/1345)
回答No.5

★補足要求です。 ・そもそも何がしたいのでしょうか?  unsigned long long型に 0.0~1.0 の小数を掛けた時点で double 型へ変換されます。  その後に unsigned long long型に代入しても double の誤差をそのまま代入します。 ・MAGNIFICATION という定数は 0.1 刻みで 1.0 までを掛けるのなら 10 で割れば。  0.0001 刻みで 1.0000 なら 10000 で割って処理すれば良いが何がしたいのかが  分からないので多倍長演算を組むのが適切なのかこちらでは不明です。  どうのような目的で 0.0~1.0 を掛け、unsigned long long型の整数を何に  利用したいのでしょうか。詳細を補足にどうぞ。

sora_naegino
質問者

補足

>・そもそも何がしたいのでしょうか? 64bit対応プログラムのデバッグに使用します。 0~18446744073709551615の値を正しく処理できるか判断する為に、 0~18446744073709551615の値を返すデバッグ用のプログラムを 作成しております。 極端に良いますと、0~18446744073709551615の値が返れば良いので小数の計算をする必要はありません。 しかし、意図した結果がどうしても得られなかったので質問させて頂いた次第です。

  • yphkz4063
  • ベストアンサー率23% (34/144)
回答No.4

コンパイラの性能を上回る処理をしたければ、加減乗除ですら自前で実装するのが正しい方法です。

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.3

一番安直な方法は、Visual C++ 2008を止めて、GCCかBorland C++ Compilerにした上でlong double型を使えば解決します。 Visual C++ 2008にこだわるのであれば、C/C++の範囲では、自分で演算処理を実装せざるを得ません。

  • sakusaker7
  • ベストアンサー率62% (800/1280)
回答No.2

> /* 18446744073709551615に0.0~1.0までの乗算 */ > #define RANGE_MAX 18446744073709551615ULL 2^64 - 1 ですか。 であれば、1以下の浮動小数点数を掛けた時点でdouble にしているでしょうから、 その有効桁数(ゲタ含めて53bit)に収まらなくなるのでその分 答えが合わなくなっているというオチでしょう。 方法2も結局doubleを経由してダメですね。

sora_naegino
質問者

補足

double型の有効桁数が10進数で約15桁と書いてあった為、2^49の"562949953421312"を使用しました。 この値を32768(2^15乗)回だけ加算すれば、 18446744073709551616になると考えたものが方法2です。 有効桁数以内でも無理なようなのでお手上げです。 参考URL http://www.cc.kyoto-su.ac.jp/~yamada/pB/float.html

  • asuncion
  • ベストアンサー率33% (2126/6288)
回答No.1

直接の回答ではありません。 方法1、2とも、コードの断片でなく、 main関数の定義などを含めた全体を見せていただけますか? それから、 > #define MAGNIFICATION 1.0 /* 0.0から1.0 */ 0.0から1.0まで変化している形跡がありませんね。 まあ、コード全体を見せてくださればわかる話かもしれませんけれど。

sora_naegino
質問者

補足

以下が方法1と方法2のソースコードです。 「#include "stdafx.h"」はVisualC++2008が自動で付けており、 内容の変更はしておりません。 ---------------------------------------------------------------------- [方法1] ---------------------------------------------------------------------- #include "stdafx.h" #include <stdio.h> #define RANGE_MAX 18446744073709551615ULL #define MAGNIFICATION 1.0 /* 0.0から1.0 */ int main(void) { printf("%0.lf", RANGE_MAX / MAGNIFICATION); return 0; } ---------------------------------------------------------------------- [方法2] ---------------------------------------------------------------------- #include "stdafx.h" #include <stdio.h> #define RANGE_MAX 562949953421312ULL #define MAGNIFICATION 1.0 /* 0.0から1.0 */ #define CYCLE 32767 int main(void) { unsigned long long int y = (RANGE_MAX - 1) * MAGNIFICATION; int loop; for(loop = 0; loop < CYCLE; loop++){ y += RANGE_MAX * MAGNIFICATION; } printf("%llu \n", y); return 0; } ---------------------------------------------------------------------- なお、現在はMAGNIFICATIONについては手動で変更しております。

関連するQ&A

  • 計算機における誤差

    学校の実験で計算機における計算誤差の解析を行っています(丸め誤差など)。 計算機によって出力された計算結果の数字がどこまで有効であるか、計算機によって生じる最大誤差を知ることは、真の値を知っていない限り大変だと思います。そこで計算結果に対する信頼性を調べるためにどのような方法をとればよいと思いますか? 実行環境はWindowsXP,C言語で行っています。

  • 非整数微分のある計算について

    こんにちは、 Z= E^(ia*x + ib*y) のとき δzδz δz  δz ----------- = -------------- δx^2  δy^2   δy^2  δx^2   = -a^2*b^2 E^(ia*x + ib*y) のように、微分する順番を変えても計算結果は同じです。 また、1次のときも δzδz δz  δz ----------- = --------------- δx δy       δy δx =-a*b E^(ia*x + ib*y) のように、微分する順番を変えても計算結果は同じです。 ここで下記のように 非整数微分の場合 δz  δz        δz  δz ----------------    =  -------------------- δx^(1/2) δy^(1/2)   δy^(1/2)δx^(1/2) 多分、微分する順番を変えると計算結果が異なると 思うのですが、 質問 (1)微分する係数が、上記の式のように1以下で、   指数関数 E^(ia*x + ib*y)のような値を   順番を変えて微分しても計算結果が同じに   なるような非整数微分はあるのでしょうか? (2)逆に、非整数微分で、順番を変えても 計算結果が同じになるような指数関数 Zはないでしょうか? 追伸 なぜか、数式のδzδzの部分が、ずれて、ちゃんと表現できません。 なんとか、文意から意味をご理解願います。

  • 実験値につける誤差の計算方法

    実験で得た数値の誤差の計算方法に悩んでいます。 実験値xとyがあったときに、それぞれの誤差が x±dx、y±dyとかではなくて xに対しては x+dx1, x-dx2 yに対しては y+dy1, y-dy2 というふうにプラスの場合とマイナスの場合で 異なる大きさの誤差を持っていたときに xとyの比、x/yにつけるべき誤差の計算方法がわかりません。 例えば、高さ6 mという値に対する誤差がプラス方向には3 m、 マイナス方向には1 m で、幅4 mに対する誤差がプラス方向には 2 m、マイナス方向には0.2 mのときに、[高さ]/[幅]の比を 計算したいのですが、これにつくプラス・マイナスの誤差を どうやって計算すればよいのでしょうか。。 x±dx、y±dyのときは x/y に √((dx/x)^2 + (dy/y)^2) とすればよいのは知っているのですが・・・ どなたか教えていただけますでしょうか。 簡単な微分計算なら理解できますが、もともと文系なので できるだけやさしく解説していただけたらありがたいです。 よろしくおねがいします。

  • dounle型で計算時の誤差

    今、VC++(MFC)で、15桁の数値まで表示可能な電卓アプリを作成しています。 そこで今つまずいているのが、double型で計算したとき(演算結果が小数の場合)の誤差の問題です。 とりあえず、いろいろなHPなどの情報を見たりして、誤差問題解決を下記のようにしました。 「数値の頭(左側)から16桁目を四捨五入する」 小数の場合はほとんど誤差が生じるため、計算後、計算結果が小数ならば、必ず 上記の誤差処理を行っています。 しかしこれでは、以下の場合に不具合が出てしまいます。 ・ 0.99 999 999 999 999 ÷ 10 = 本来の答えは「0.09 999 999 999 999 9」 ⇒ しかし15桁までの表示なので、本来は「0.09 999 999 999 999」と15桁まで出力     させなくてはいけないのに、16桁目の「9」を四捨五入したせいで「0.1」という表示に     なってしまう。 16桁目を四捨五入しないと誤差をとることはできないし、でも上記の例だと正しい結果 が出力されません。 どうしたらいいのか頭を悩ませています。 何か良い解決法等あれば、ご教授お願いします!!

  • 少数計算の誤差について

    お世話になってます。 javascriptでの少数計算結果について、 document.write(1100 * 93.07);  ⇒102376.99999999999 document.write(1100 * 93.17);  ⇒102487 document.write(1100 * 93.57);  ⇒102926.99999999999 上記のような結果になるのですが、法則が分からず悩んでいます。 javascriptの仕様のようで、整数にしてから割り戻す方法で回避しようと考えてますが 結果に誤差がでる組み合わせの法則はどのようになっているのでしょうか? 宜しくお願いします。

  • 計算に誤差が出る?

    0.1 + 0.2 + 0.3・・・・・・ このように行うプログラムを2通りに分けて処理をして見ました。 以下にソースを載せます。 #include <stdio.h> int main() { float sum, i; float sum2; int f; for(i=0.1, sum=0.0; i<=100.0; sum+=(float)i, i+=0.1); printf("%f\n", sum); for(f=1, sum2=0.0; f<=1000; sum2+=(float)f/10.0, f++); printf("%f\n", sum2); return 0; } 初めのfor文と2番目のfor文では同じ処理を行っているのですが、計算結果が微妙に異なって出力されてしまいました。 理由が分かる方は教えてもらえないでしょうか?

  • 整数環 0×∞ 形の積

    内容を少しだけ修正しました。 実数で 0 と ∞ となるものを考えます。たとえば  a = lim[x→∞]1/x = 0  b = lim[x→∞]Σ[n=1,x]1 = lim[x→∞]x = ∞ という例が挙げられます。 加算について  1 + lim[x→∞]Σ[n=2,x]1 = b = ∞ となりますか? 乗算について  a × b = (lim[x→∞]1/x) × (lim[y→∞]Σ[n=1,y]1) = lim[x,y→∞]y/x は不定(未定義)ですか? 同じことを整数で行うと、たとえば  c = lim[x→∞]0 = 0  d = lim[x→∞]x = ∞ という例が挙げられ  c × d = lim[x,y→∞]0y = 0 となりますか? 整数環では 0 × ∞ という形の積は 0 と考えて良いのでしょうか?

  • 入力された2つの整数の差を絶対値を出力するプログラム

    2整数の差を絶対値を出力するプログラムをつくりました. 入力される数値は、正数の場合は最大8桁まで有効とし、9桁目以降を無視、負数の場合は '-'を含め、最大9桁まで有効とし,10桁目以降を無視するようにしたい。 2番目の数値入力で9桁以降に数字以外の文字を入力すると,うまく動きません.なぜ動かないかとどのように直したらよいか教えてください. お願いします. #include <stdio.h> #include <stdlib.h> #define BUFFERSIZE 10 #define PLUS_MAX 8 #define MINUS_MAX 9 #define C_NULL ('\0') #define RC_OK 0 #define RC_ND -1 #define RC_ID -2 #define RC_MD -3 int absolute( int n1, int n2 ) ; main() { char n[ 2 ][ BUFFERSIZE ]; int i ; int a[ 2 ] ; int ab ; int count ; int n_pos ; char c ; for( i = 0 ; i < 2 ; i ++ ){ printf( "%d番目の数値 :", i + 1 ) ; for( count = 0 ; count <= BUFFERSIZE ; count ++ ){ c = getchar() ; if( c >= '0' && c <= '9' || count == 0 && c == '-' ){ n[i][ count ] = c ; continue ; } if( c == '\n' ) break ; printf( "Error!\n" ) ; return( RC_ID ) ; } if( count == 0 ){ printf("Error!\n"); return( RC_ND ); } else if( n[ i ][ 0 ] == '-' && count < 2 ){ printf("Error!\n"); return( RC_ID ) ; } if( n[ i ][ 0 ] == '-' && count > MINUS_MAX ){ n_pos = MINUS_MAX ; }else{ if( count > PLUS_MAX ){ n_pos = PLUS_MAX ; }else{ n_pos = count ; } } n[ i ][ n_pos ] = '\0' ; a[ i ] = atoi( n[ i ] ) ; } ab = absolute( a[ 0 ], a[ 1 ] ) ; printf( "絶対値は %d \n", ab ) ; return( RC_OK ); } int absolute(int n1, int n2) { int i ; i = n1 - n2 ; return i >= 0 ? i : -i ; }

  • 交通費計算の誤差について

    複数の車に乗車して旅行に行ったので、交通費の精算をしていました ところが、交通費の総額と、車を出してくれた人に支払う費用の合計で、誤差が生じてしまい非常に困っています 計算に当たっての情報は以下の通りです A車 燃費 15キロ/リットル B車 燃費 5キロ/リットル ガソリン代 143円/リットル 走行距離 56キロ 1.トータルの交通費の算出 ・計算式   A車の燃費 15キロ/リットル + B車の燃費 5キロ/リットル ÷ 2台 = 2台の車の平均燃費 10キロ/リットル  走行距離 56キロ ÷ 2台の車の平均燃費 10キロ/リットル × ガソリン代 143円 × 2台分  計算結果 1601.6円 2.各車輌に支払う交通費の算出 ・計算式  走行距離 56キロ ÷ A車の燃費 15キロ/リットル × ガソリン代 143円 = 533.8666...円.  走行距離 56キロ ÷ B車の燃費 5キロ/リットル × ガソリン代 143円 = 1,601.6円  合計金額 2135.4666....円 1の計算結果と、2の計算結果で533円も誤差が生じてしまいます どちらの計算も、交通費の算出の為のものですが 燃費の平均を出してから行う計算と、 個別に費用を計算して行う方法で誤差が発生する理由が全くわかりません この理屈は一体同友事でしょうか、ご教授ください

  • log2の「正確な」計算方法

    perlでlog2を計算するにはどのようにしたらよいのでしょうか。 perldocによると sub log2 { my $n =shift; return log($n)/log(2); } でよいはずですが、log2 が「正しく整数を返すかどうか」は保証されていないので、時として問題があるようです。 通常、log2 の結果 $a を単に print $a などとして出力する分には Perl が適当? に判断して丸め処理をしてる傾向があるようですが、これを printf "%d", $a とすると、本当に整数部だけが出力され、演算精度によっては意図 しない数値になる場合があるとのこと、計算機環境にインストール した Perlで、演算精度を上げるオプションを追加した場合などで、実際に出力結果が異なる、との報告を受けました。 宜しくお願い致します。

    • ベストアンサー
    • Perl

専門家に質問してみよう