計算結果が振動する

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
y_and_y

計算結果が振動する

#1

投稿記事 by y_and_y » 6年前

お世話になっております。

計算結果が小数になる計算をしようとしているのですが、ループ毎に計算結果が振動してしまいます。
最終的にbeforeの値をint型に変換して使用するのですが、floatの計算結果の小数点以下が毎回異なるため
intに変換した後の値がループ毎に変化してしまいます。
今回は例として簡略化していますが、本来は使用している変数のうち、pos および y の初期値が様々な値に変化します。
最終的にbeforeをint型にキャストする以外は、3つの変数をint型またはfloat型のどちらで宣言しても構いません。
ループ毎に計算結果が変化しないようにするためには、どのような宣言および計算を行えば良いのでしょうか。
(一見出力結果が変化していないように見えても、小数点以下の値が振動しているため、
pos や y の初期値によってはキャスト後の値がループ毎に変化する可能性があります)

コード:

#include <stdio.h>
int main(void)
{
	int before = 0;
	float pos = 703;
	int y = 123;

	while(1){
		printf("before:%d\n", before);
		pos = 650 - (704 - y)*(-1)*(((before * (-1)) + 649) / 649);

		for(int i = 0; i < 200; i++){
				for(int j = 5; j >= 0; j--){
					pos -= 26;
				}
		}
		before = pos;
	}
}

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: 計算結果が振動する

#2

投稿記事 by softya(ソフト屋) » 6年前

posがfloatじゃないとダメな理由がわかりません。いかなる理由でしょうか?
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
みけCAT
記事: 6216
登録日時: 8年前
住所: 千葉県
連絡を取る:

Re: 計算結果が振動する

#3

投稿記事 by みけCAT » 6年前

ループ内最後の

コード:

before = pos;
の代わりに

コード:

before = pos+1e-5;
としたらどうなりますか?[/s]

【追記】
このレスは間違いです。
posに代入している計算式は全て整数で計算しているため、提示した対策は無駄な誤差を生むだけです。
最後に編集したユーザー みけCAT on 2013年9月15日(日) 15:04 [ 編集 1 回目 ]
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6216
登録日時: 8年前
住所: 千葉県
連絡を取る:

Re: 計算結果が振動する

#4

投稿記事 by みけCAT » 6年前

y_and_y さんが書きました:intに変換した後の値がループ毎に変化してしまいます。
これはどういう意味ですか?
候補A「サンプルプログラムのwhileの中の計算を1回するたびに、beforeの値が変わる」→当たり前?
候補B「サンプルプログラムのmain関数を実行するたびに、全体で出力される値が変わる」→誤差?メモリ破壊?
y_and_y さんが書きました:(一見出力結果が変化していないように見えても、小数点以下の値が振動しているため、
pos や y の初期値によってはキャスト後の値がループ毎に変化する可能性があります)
その初期値の例を提示できますか?
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6216
登録日時: 8年前
住所: 千葉県
連絡を取る:

Re: 計算結果が振動する

#5

投稿記事 by みけCAT » 6年前

なぜposの計算をわかりにくい式でしているのですか?コンパイラの動作の実験ですか?理論的な意味ですか?

コード:

pos = 650 + (704 - y)*((-before + 649) / 649);
ではダメなのですか?
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

y_and_y

Re: 計算結果が振動する

#6

投稿記事 by y_and_y » 6年前

softya(ソフト屋) さんが書きました:posがfloatじゃないとダメな理由がわかりません。いかなる理由でしょうか?
softya様
posがfloatになっている理由は、割り算を行った際に割り切れない可能性があるためです。
intで済ませられるのであれば、勿論intにしたいのですが…

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: 計算結果が振動する

#7

投稿記事 by softya(ソフト屋) » 6年前

y_and_y さんが書きました:
softya(ソフト屋) さんが書きました:posがfloatじゃないとダメな理由がわかりません。いかなる理由でしょうか?
softya様
posがfloatになっている理由は、割り算を行った際に割り切れない可能性があるためです。
intで済ませられるのであれば、勿論intにしたいのですが…
必要な時だけ649で割れば良いので、それ以外は割らずに済ませたらfloatはいらないのでは?
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

ISLe
記事: 2645
登録日時: 9年前
連絡を取る:

Re: 計算結果が振動する

#8

投稿記事 by ISLe » 6年前

floatをintにキャストした値を使って計算を継続すれば誤差が出るのは当たり前ですが、そういう話ではないですよね。

キャストによる丸めは環境に依存するので、数学関数を使って確実に整数部だけ取得するのが良いのではないでしょうか。

y_and_y

Re: 計算結果が振動する

#9

投稿記事 by y_and_y » 6年前

みけCAT様
posの計算に-1を2回掛けているのは、単に計算式の簡略化を行っていないだけで、特に意味はありません。
申し訳ありません。

本来のプログラムでは、posの初期値は固定値とします。(while文内で変化するのは正常です)
また、yの値はマウスカーソルのy座標を想定しており、動的に変化する可能性があります。
(勿論カーソルが動かなければ固定値です)

このプログラムでは、yの値が変化しなければ、毎回同じ計算結果が出力されることを想定しています。
ですので、
>候補A「サンプルプログラムのwhileの中の計算を1回するたびに、beforeの値が変わる」→当たり前?
これは想定外の動作で今回問題となっているポイントになります。

例として適切な値が見つからないため、小数を例として使用させていただきます。
(わかりやすくするために、beforeをfloatにしています。

コード:

#include <stdio.h>
int main(void)
{
	float before = 0;
	float pos = 703;
	int y = 123;

	while(1){
		printf("before:%f\n", before);
		pos = 650 - (704 - y)*(-1)*(((before * (-1)) + 649) / 649);

		for(int i = 0; i < 200; i++){
				for(int j = 5; j >= 0; j--){
					pos -= 26;
				}
		}
		before = pos;
	}
}
このコードを実行すると、私の環境では誤差のため出力結果が、
before:-15812.916016
before:-15812.907227
before:-15812.916016
before:-15812.907227
: :
と交互に繰り返されます。この例では誤差が整数部分に及んでいないので問題はないのですが、
今回の誤差の大きさですと、小数点以下の値によっては整数部1の位に影響し、intにキャストした際にwhileループ毎に計算結果が変化する可能性があります。

また、
>その初期値の例を提示できますか?
適切な例ではありませんが、一番初めに示した例ではintの出力結果が2つの値を交互に行き来しています。
毎回同じ結果となることを想定し、結果として2種類のみの値が交互に現れていることから、明らかに誤差による問題かと思われます。

y_and_y

Re: 計算結果が振動する

#10

投稿記事 by y_and_y » 6年前

softya(ソフト屋) さんが書きました:
y_and_y さんが書きました:
softya(ソフト屋) さんが書きました:posがfloatじゃないとダメな理由がわかりません。いかなる理由でしょうか?
softya様
posがfloatになっている理由は、割り算を行った際に割り切れない可能性があるためです。
intで済ませられるのであれば、勿論intにしたいのですが…
必要な時だけ649で割れば良いので、それ以外は割らずに済ませたらfloatはいらないのでは?
申し訳ありません。どういう意味なのでしょうか。
前回の計算結果を毎回引き継ぐことを想定しています。ただし引き継ぐ値が小数である必要はないので
int型の値を引き継ぐことも考えたのですが、これを行っても振動が発生してしまいます。

アバター
みけCAT
記事: 6216
登録日時: 8年前
住所: 千葉県
連絡を取る:

Re: 計算結果が振動する

#11

投稿記事 by みけCAT » 6年前

y_and_y さんが書きました:例として適切な値が見つからないため、小数を例として使用させていただきます。
(わかりやすくするために、beforeをfloatにしています。

コード:

#include <stdio.h>
int main(void)
{
	float before = 0;
	float pos = 703;
	int y = 123;

	while(1){
		printf("before:%f\n", before);
		pos = 650 - (704 - y)*(-1)*(((before * (-1)) + 649) / 649);

		for(int i = 0; i < 200; i++){
				for(int j = 5; j >= 0; j--){
					pos -= 26;
				}
		}
		before = pos;
	}
}
このコードを実行すると、私の環境では誤差のため出力結果が、
before:-15812.916016
before:-15812.907227
before:-15812.916016
before:-15812.907227
: :
と交互に繰り返されます。この例では誤差が整数部分に及んでいないので問題はないのですが、
今回の誤差の大きさですと、小数点以下の値によっては整数部1の位に影響し、intにキャストした際にwhileループ毎に計算結果が変化する可能性があります。
Ideoneで試したところ、-15812.911605294202672666870057582855に収束しました。
あなたの実行環境を提示していただけますか?
http://ideone.com/KtjjDu

【追記】
収束する値は、正確には-15812.911605294202672666870057582855224609375000000000000000000000のようですね。
http://ideone.com/dAuaf7
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6216
登録日時: 8年前
住所: 千葉県
連絡を取る:

Re: 計算結果が振動する

#12

投稿記事 by みけCAT » 6年前

y_and_y さんが書きました:このプログラムでは、yの値が変化しなければ、毎回同じ計算結果が出力されることを想定しています。
ですので、
>候補A「サンプルプログラムのwhileの中の計算を1回するたびに、beforeの値が変わる」→当たり前?
これは想定外の動作で今回問題となっているポイントになります。
posを決める式にパラメータとしてbeforeが入っているので、beforeが変化すればposも変化するのは当たり前だと感じます。
何か特殊な思想に基づいた特殊な処理系をお使いなのでしょうか?
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

y_and_y

Re: 計算結果が振動する

#13

投稿記事 by y_and_y » 6年前

みけCAT さんが書きました: Ideoneで試したところ、-15812.911605294202672666870057582855に収束しました。
あなたの実行環境を提示していただけますか?
http://ideone.com/KtjjDu

【追記】
収束する値は、正確には-15812.911605294202672666870057582855224609375000000000000000000000のようですね。
http://ideone.com/dAuaf7
こちらでは、Visual Studio Express 2012 for Windows Desktop Version 11.0.60610.01 Update 3
を使用しています。

Ideoneの結果を見ると、計算結果が安定するまでに、それなりの回数の計算が必要になるようですが…
実際のプログラムでは、while1回=1フレームとして計算結果のposの位置に描画処理を行っています。
(実際は1フレームに1000回も計算が発生しないようには考慮しています)
そのため、最終的には収束するとしても、収束するまでの振動が問題になってしまいます。
多少精度が悪くなっても構わないので(振動が整数部に影響を与えなければ小数点以下を切り捨てても構いません)
毎回の計算結果を一様にすることはできないのでしょうか。

アバター
みけCAT
記事: 6216
登録日時: 8年前
住所: 千葉県
連絡を取る:

Re: 計算結果が振動する

#14

投稿記事 by みけCAT » 6年前

コード:

a[0]=0
a[n+1]=650+k*(649-a[n])/649

lim(n→∞)a[n]=?

ただし k=704-y
[/s]
このような漸化式で、いきなり最終的な値を求めてもいいですか?(漸化式を解くのはこれからです)
それとも、ある程度の途中経過が必要ですか?

【追記】
間違えました。正しい漸化式はこちらになります。

コード:

a[0]=0
a[n+1]=p+k*(649-a[n])/649

lim(n→∞)a[n]=?

ただし
k=704-y
p=650-26*200*(5+1)=-30550
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: 計算結果が振動する

#15

投稿記事 by softya(ソフト屋) » 6年前

全部double型にすると収束するので、有効桁が足らないだけでは?
floatは6桁程度です。

コード:

#include <stdio.h>
int main(void)
{
    double before = 0;
    double pos = 703;
    double y = 123;
 
    while(1){
        printf("before:%f\n", before);
        pos = 650.0 - (704.0 - y)*(-1.0)*(((before * (-1.0)) + 649.0) / 649.0);
 
        for(int i = 0; i < 200; i++){
                for(int j = 5; j >= 0; j--){
                    pos -= 26.0;
                }
        }
        before = pos;
    }
}
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

box
記事: 1737
登録日時: 8年前

Re: 計算結果が振動する

#16

投稿記事 by box » 6年前

このコードを実行したときの結果がほしい、ということなんでしょうか?

コード:

#include <stdio.h>

int main(void)
{
    double before = 0, pos = 703;
    int y = 123, value = before, i;
 
    for (i = 0; i < 200; i++) {
        printf("value:%d\n", value);
        pos = 650 - (y - 704) * ((-before + 649) / 649) - 26 * 200 * 6;
        value = before = pos;
    }
    return 0;
}
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

アバター
みけCAT
記事: 6216
登録日時: 8年前
住所: 千葉県
連絡を取る:

Re: 計算結果が振動する

#17

投稿記事 by みけCAT » 6年前

softya(ソフト屋) さんが書きました:全部double型にすると収束するので、有効桁が足らないだけでは?
floatは6桁程度です。

コード:

#include <stdio.h>
int main(void)
{
    double before = 0;
    double pos = 703;
    double y = 123;
 
    while(1){
        printf("before:%f\n", before);
        pos = 650.0 - (704.0 - y)*(-1.0)*(((before * (-1.0)) + 649.0) / 649.0);
 
        for(int i = 0; i < 200; i++){
                for(int j = 5; j >= 0; j--){
                    pos -= 26.0;
                }
        }
        before = pos;
    }
}
このコードをIdeoneで試すと、結果が
-15812.911382113819854566827416419982910156250000000000000000000000と-15812.911382113821673556230962276458740234375000000000000000000000で振動しました。
http://ideone.com/8LyAs2
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6216
登録日時: 8年前
住所: 千葉県
連絡を取る:

Re: 計算結果が振動する

#18

投稿記事 by みけCAT » 6年前

「収束する=計算した値とbeforeの値が同じになる」と考えると、前述の漸化式の極限は一次方程式で計算できます。
(厳密な数学的な証明はわかりません)

考え方

コード:

p+k*(649-pos)/649==pos

p*649+k*649-k*pos==649*pos
(p+k)*649==(649+k)*pos
pos==(p+k)*649/(649+k)
計算の実装

コード:

double getFinalPos(double y) {
	double p=650-26*200*(5+1);
	double k=704-y;
	return (p+k)*649/(649+k);
}
y=123を入力すると、もとのコードと同じくらいの値が得られることがわかります。
http://ideone.com/8wTGKC
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

y_and_y

Re: 計算結果が振動する

#19

投稿記事 by y_and_y » 6年前

こちら必死のため、全てにレスを返せず申し訳ありません。
この振動の問題を解決できればプログラムとして問題のないものが出来上がると信じていましたが、
再帰的に計算するため、どうしても初期の振動が発生してしまい計算結果の振動をある程度無視するような
ロジックを書かない限り、計算結果をそのまま描画位置に使用することは不適切であるという結論なのかな、と思います。

頂いているアドバイスを元に振動を解決する方法も検討していきますが、そもそも計算から描画位置を求める方法が
不適切な可能性もあるので、このような質問に至った背景からお話させてください。

現在ゲームソフトを作成(DXライブラリ使用)しており、画面にはウィンドウ内に収まらない量のテキストが表示されます。
ウィンドウ内にテキストが入りきらないので、擬似的なスクロールバーを実装し、これを移動させることでメッセージの描画位置を変化させます。
ただし、スクロールバーの掴むバー部分は一般的なものと違い、サイズが動的に変化することはないものとします。
(変数yはスクロールバーのy座標、最初のposは一番下に表示されるテキストのy座標です。
実際は2重のfor文の中に、posの位置にテキストを描画する処理が入ります。
一番下の行から上に向かって順に一行ずつ描画していきます。)

スクロールバーの移動によるテキスト描画位置の変化は、全体のテキストの量に依存する必要があります。
例えばテキスト全体量が画面内に収まる量であれば、スクロールバーを移動してもテキスト位置は変化しない、
またテキスト全体量がウィンドウ3画面分あれば、スクロールバーを一番上から下まで動かして
ちょうどテキスト描画位置が2画面分移動するような処理を実装します。
posの計算式で649で割っているのは、テキストの総量によってテキストのスクロール量を変化させるためです。
649という値は、スクロールバー移動可能範囲の上端と下端の差分です。
また前回の計算結果beforeを使用しているのは、これによりテキストの総量(ピクセル数)が求められるからです。
(beforeは一番上に表示されるテキストの位置になります。
テキスト内容によって縦の行間を変化させる場合があるので、テキストの行数から
全体の縦の長さを求めることはできません。
そのため、テキストの一番下の行位置と、前フレームで表示した一番上の行位置(before)の差分を求めることで
テキスト全体の縦ピクセル数およびスクロールバー移動時のテキストの移動量を計算しようとしています。)

ただ、現在問題になっている通り、テキスト全体の縦ピクセル数を求める際に前回の計算結果を使用すると振動が発生してしまいます。
最終的に収束するとしても、収束し切るまでの間は文字が縦に震えて描画されてしまいます。(これが今回の問題でした)

一般的にこういったものを実装する場合は、どういった手段を取るのでしょうか。


みけCAT様、box様
アドバイスを頂いていますが、検証しきれていない状態です。申し訳ありません。
時間はかかりますが、頂いたご意見を少しずつ試させてください。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: 計算結果が振動する

#20

投稿記事 by softya(ソフト屋) » 6年前

一行毎の構造体配列に行の高さとか行間とか記録しておけばよいのでは?
それらを足すだけで先頭からの絶対位置は求まります。あとはスクロール量と計算すれば画面の表示位置も求まります。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

たいちう
記事: 418
登録日時: 9年前

Re: 計算結果が振動する

#21

投稿記事 by たいちう » 6年前

数学的に収束するのかどうかの検証はみけCATさんや他の人におまかせするとして、
数学的に収束する場合でも、そのままコーディングしては振動する可能性があります。

floatがdoubleになっても振動するときはします。
プログラムでの計算に誤差はつきものなので、
うまく処理できるようになるか、諦めましょう。


提案1)
doubleを使う事にして、直前の値との差の絶対値が閾値以下になったら収束したとみなす。
数学的に正しい値が必要なわけでないならば、これで十分でしょう。


提案2)
お求めのスクロールのイメージが十分はつかめていませんが、
普通のスクロールだったら実装できますか?
サンプルコードも簡単に見つかるだろうし、まずはそれを実装してみて、
十分理解してから、必要な改造を行っては?


提案3)
誤差の扱いを極める。
まずは何故振動しているか理解しましょう。
floatでの計算を追ってみて下さい。

アバター
usao
記事: 1552
登録日時: 6年前

Re: 計算結果が振動する

#22

投稿記事 by usao » 6年前

>(beforeは一番上に表示されるテキストの位置になります。
>テキスト内容によって縦の行間を変化させる場合があるので、テキストの行数から
>全体の縦の長さを求めることはできません。
>そのため、テキストの一番下の行位置と、前フレームで表示した一番上の行位置(before)の差分を求めることで
>テキスト全体の縦ピクセル数およびスクロールバー移動時のテキストの移動量を計算しようとしています。)

よくわかりませんが,この辺の理屈あるいは実装がバグっているだけな気がします.

単に表示スクロール量を計算するだけのことに対して謎のループ計算(?)が必要な意味がわかりません.
振動とか収束とかいう言葉が出てくるような処理が本当に必要なのでしょうか?

・表示したいもののサイズ と 表示領域のサイズ から最大スクロール量が定まる → 表示内容が変わらない限り定数
・スクロールバーの表示的なスクロール範囲(649) と ↑の最大スクロール量 の比率から,yに対する表示スクロール量が定まる → yが変わらない限り定数
・↑の表示スクロール量を(加算か減算かわからんが)用いて最終的な表示位置を決める → 定数

(実装としてこれを本当に毎フレーム計算するかどうかは別として)フレーム間で独立な単純計算だと思うのですが……私は話を簡単に捉えすぎでしょうか?
(「途中計算がfloatかdoubleか?誤差が…」なんて話を持ち出すような内容か?処理内容的に考えて… とか…)

閉鎖

“C言語何でも質問掲示板” へ戻る