ある計算で、’遊動小数点数のオーバーフロー’のエラーが発生したのですが
原因は、限りなく0に近い数値が、doubleとsingleを混同した計算式に入った為でした。
実証する為に簡単なコードを作ってみました
procedure TForm1.Button1Click(Sender: TObject);
var x:double;
y:double;
xy:single;
begin
x:=minsingle;
y:=mindouble;
xy:=x/y; //<-ここでエラー
showmessage(floattostr(xy));
end;
xyをdouble型にしたり事前にxやyの値を検査して0とすれば回避できるのですが
single型変数に、その範囲より小さい値が代入されたら0としてくれれば、問題ないと思います。
その様に処理させる事は出来ないのでしょうか?
(プロジェクトオプションの実行時エラーのオーバーフローチェックを外してみましたが
これとは別のようでした。)
try exceptで実行時エラー(例外)を処理すれば良いと思います。
procedure TForm1.Button1Click(Sender: TObject);
var x:double;
y:double;
xy:single;
begin
x:=minsingle;
y:=mindouble;
try
xy:=x/y; //<-ここでエラー
except
// try .. except間で実行時エラー(例外)が発生すると
// ここに飛んでくる。
// オーバーフローか0の割り算かなど判定したい場合は、
// on .. do .. を記述します。
showmessage('エラー'); // <= とりあえずエラー表示
end;
showmessage(floattostr(xy));
end;
詳しくは、ヘルプでtryまたはexceptを検索してみてください。
mさん 回答有難うございます
質問の内容ですが、例外の中身の事です
つまり、singleに小さい値を入れた時のエラーを回避できないかと言う事です。
このエラーは、例えば関数を呼び出した時点でも発生すり可能性が有るようなので
Halbow です。
> singleに小さい値を入れた時のエラーを回避できないかと言う事です。
最初のコードを試してみましたが、「小さい」値ではないですよ。
Single 型の範囲外のおおきな数値を代入しようとしてるんでは?
なぜ、全部 Double でやらないのか不思議です。型の異なる演算の
結果は、幅の大きい方の型の変数に入れるべきではないでしょうか?
try .. exceptで回避できませか?
例えば...
try
xy:=x/y; //<-ここでエラー
except
on EOverflow do xy := 0; // オーバーフロー時 とりあえずxyを0
on EZeroDivide do xy := 0; // 0での割算 とりあえずxyを0
end;
showmessage(floattostr(xy));
ではだめでしょうか?
ちなみにDelphi(デバッカー)上で実行する場合は必ず実行時エラー(例外)が発生しますが、try exceptは正しく機能してます。(on ..の行にブレークポイントを設定してみればわかります。)
本当に実行時エラーが回避されたか確認するにはコンパイルしてEXEを直接実行してみてください。
PS.前のレス、ハンドルネーム打ち間違えました。m -> masayanです。
Halbowさん masayanさん、レス有難うございます
例外については、承知していますし、SingleにDoubleの値を代入するべきでは無いのでしょうが
勘違いの例で質問してしまったようなので、例題のコードを変更します。
T2Ddata=record
x,y:double;
Check:boolean;
end;
procedure TForm1.Button1Click(Sender: TObject);
var x,y,z:double;
A:T2Ddata;
begin
x:=-1.0;
y:=1.0;
A:=Call1(x,y);
try
z:=A.x - A.y;
showmessage(floattostr(z));
except
showmessage('');
end;
end;
function Call1(x,y:double):T2Ddata;
begin
if x * y<0 then
begin
result.Check:=false;
exit;
end;
result.x:=x * y;
end;
原因は、Call1のx、yの戻り値に正確な値が返されていないので
z:=A.x - A.y; の所で、オーバーフローのエラーになります。
当然ながら、戻り値A.Checkを検査して処理を進めれば良いのですが
このコードを実行した時、2000では問題なく処理されました。
今回、疑問になった実際の関数では、複雑な処理をしているのですが
同様の位置で、エラーになったり通過したりします。
又、上記の実行ファイルを98で行うと、遊動小数点エラーとなり
このエラーの場合には、例外処理も実行されません。
そこで新たな質問ですが
x,yはrecordの中でDoubleで宣言しているのに、なぜオバーフローの値で初期化されるのでしょうか?
又、98では必ずエラーになるのは、なぜでしょうか?
Halbow です。
> x,yはrecordの中でDoubleで宣言しているのに、なぜオバーフローの値で
> 初期化されるのでしょうか?
この原因は、Call1() の中でちゃんと初期化していないからです。
一見、環境依存のように思えますが、たんにたまたまゴミが返される
ときに値がばらばらなだけです。
x:=-1.0;
y:=1.0;
A:=Call1(x,y);
では、Call1() で返される T2Ddata の x と y フィールドには
なんの演算もされずにゴミの値が返されます。ちゃんとゼロを設定する
などの初期化をしてください。
Halbowさん レス有難うございます
確かにそのとうりですね
元々は、不正な使い方が原因なのは解っていますが
OS(PCの環境なのかも)によって違うのは何故でしょうか?
(ゴミだからと言われれば、それまでですが)
今回のエラーは、Meのユーザー様から頂いたのですが
当方の開発環境(2000)では現象が現れず、解らなかったので、、、
仕様、という言葉で片づける訳にはいきませんか^^;
ヘルプの、Object Pascal言語ガイドの、「関数宣言」には、
> Result または関数名に値を代入せずに関数の実行が終了した場合,関数の戻り値は未定義になります。
とかかれています。
未定義ということは、0とは限らないし、ゴミが入るとも限らない(0である場合もあり、ゴミの場合もあり得る)、ということですので、明確に「ゴミが入る理由」はいえません。
にしのさま いつも有難うございます
ヘルプを確認しました。やはり”未定義”と明確に書いてあるのですね。
未定義だから何があっても、仕方が無いと言うことで解決です。
ローカル変数では、「初期化されていない可能性があります」と教えてくれるので
関数でも警告してくれれば助かるのですが
先ずは注意して作ると言う事ですね
皆さん、有難うございました。
Halbow です。
にしのさんのご指摘のとおりです。
「未定義」な値に依存して、エラーが起こるとか起こらないとかを
議論しても無意味です。たまたま、そのときのゴミの状態に依存して
いますので、なぜそうなったかを言うことはできません。
初期化は、ローカル変数や今回のように result 部分で、演算のない
可能性があるときは必須です。全部代入されることが分かっていると
きは、もちろん初期化は必要ありません。
ツイート | ![]() |