私はいくつかのビジネス要件を解決するための新しいプログラミング言語を開発している最中です。この言語は初心者ユーザーを対象としています。そのため、言語での例外処理はサポートされておらず、追加したとしてもそれを使用することは期待できません。
Divide演算子を実装する必要があるポイントに達しました。ゼロ除算エラーを最適に処理する方法を知りたいですか。
このケースを処理する方法は3つしかないようです。
0
結果として。可能であれば警告をログに記録します。NaN
を追加しますが、これにより、言語の他の領域でのNaN
値の処理方法に関する質問が生じます。オプション#1が唯一の合理的な解決策のようです。オプション#3は、この言語が夜間cronとしてロジックを実行するために使用されるため、実用的ではありません。
ゼロによる除算を処理するための私の代替策は何ですか?オプション#1を使用することのリスクは何ですか?.
エラーを無視するだけでも危険なアンチパターンであるため、私は#1を強くお勧めしません。分析が困難なバグにつながる可能性があります。ゼロによる除算の結果を0に設定してもまったく意味がなく、無意味な値でプログラムの実行を続けると問題が発生します。 特にプログラムが無人で実行されている場合。プログラムインタープリターがプログラムにエラーがあることに気付いた場合(ゼロによる除算はほとんどの場合設計エラーです)、プログラムを中止し、すべてを現状のまま維持することは、通常、データベースをゴミで埋めるよりも優先されます。
また、このパターンを完全に順守することで成功することはほとんどありません。遅かれ早かれ、無視できないエラー状態(メモリ不足やスタックオーバーフローなど)に遭遇し、とにかくプログラムを終了する方法を実装する必要があります。
オプション#2(NaNを使用)は少し手間がかかりますが、思ったほどではありません。さまざまな計算でNaNを処理する方法は、IEEE 754標準で十分に文書化されているため、インタープリターが記述されている言語で実行できることを実行できます。
ところで:プログラマーではない人が使用できるプログラミング言語の作成は、1964年(ダートマスBASIC)以来、私たちがやろうとしていることです。これまでのところ、失敗しています。とにかく頑張ってください。
1-エラーを無視して
0
結果として。可能であれば警告をログに記録します。
それは良い考えではありません。全然。人々はそれに依存し始め、あなたがそれを修正するならば、あなたは多くのコードを壊すでしょう。
2-数値の可能な値として
NaN
を追加しますが、言語の他の領域でNaN
値を処理する方法について疑問が生じます。
NaNは他の言語のランタイムと同じように処理する必要があります。それ以降の計算でもNaNが生成され、すべての比較(NaN == NaNであっても)はfalseになります。
これは許容できると思いますが、必ずしも新人に優しいとは限りません。
3-プログラムの実行を終了し、重大なエラーが発生したことをユーザーに報告します。
これは私が思う最高の解決策です。その情報を入手すれば、ユーザーは0を処理できるはずです。特に、夜間に1回実行することが意図されている場合は、テスト環境を提供する必要があります。
4番目のオプションもあります。除算を三項演算にします。これら2つのいずれでも機能します。
実行中のアプリケーションを極端な偏見で終了させます。 (適切なデバッグ情報を提供しながら)
次に、除数がゼロになる可能性がある条件(ユーザーが入力した値など)を識別して処理するようにユーザーを教育します。
Haskell(およびScalaでも同様)では、例外をスローする(またはnull参照を返す)代わりに、ラッパー型Maybe
およびEither
を使用できます。 Maybe
を使用すると、ユーザーは、取得した値が「空」であるかどうかをテストできます。または、「アンラップ」時にデフォルト値を提供する可能性があります。 Either
も同様ですが、問題がある場合はそれを説明するオブジェクト(エラー文字列など)を返します。
他の回答では、アイデアの相対的なメリットがすでに考慮されています。別の方法を提案します。基本的なフロー分析を使用して、変数canがゼロかどうかを判断します。次に、潜在的にゼロである変数による除算を単に拒否できます。
x = ...
y = ...
if y ≠ 0:
return x / y // In this block, y is known to be nonzero.
else:
return x / y // This, however, is a compile-time error.
または、不変条件を確立するインテリジェントなアサート関数を使用します。
x = ...
require x ≠ 0, "Unexpected zero in calculation"
// For the remainder of this scope, x is known to be nonzero.
これは、ランタイムエラーをスローするのと同じくらい優れています(未定義の操作を完全に回避できます)。ただし、潜在的な障害が公開されるためにコードパスにヒットする必要がないという利点があります。これは、不変条件を追跡および検証するためのネストされた型指定環境でプログラムのすべてのブランチを評価することにより、通常の型チェックと同様に実行できます。
x = ... // env1 = { x :: int }
y = ... // env2 = env1 + { y :: int }
if y ≠ 0: // env3 = env2 + { y ≠ 0 }
return x / y // (/) :: (int, int ≠ 0) → int
else: // env4 = env2 + { y = 0 }
...
... // env5 = env2
さらに、言語にそのような機能がある場合は、範囲とnull
チェックに自然に拡張されます。
1番(デバッグできないゼロを挿入)は常に悪いです。 #2(伝播NaN)と#3(プロセスを強制終了)のどちらを選択するかはコンテキストに依存し、Numpyと同様に、理想的にはグローバル設定にする必要があります。
1つの大きな統合された計算を実行している場合、NaNの伝播は悪い考えです。それは結局、計算全体に広がり感染するためです。午前中に結果を見て、すべてがNaNであることがわかると、 d結果を破棄して、とにかく最初からやり直す必要があります。プログラムが終了し、真夜中に電話を受けてそれを修正した方がよかったでしょう-少なくとも時間の浪費という点では。
多くの場合、ほとんど独立した計算(マップリデュースや非常に複雑な並列計算など)を実行していて、その一部がNaNのために使用できないことを許容できる場合は、それがおそらく最良のオプションです。プログラムを終了して99%を実行しないと、1%の形式が正しくなく、ゼロで除算されるため、これは適切で有用です。
NaNに関連する別のオプション:同じIEEE浮動小数点仕様がInfと-Infを定義し、これらはNaNとは異なる方法で伝播されます。たとえば、私はInf>任意の数値および-Inf <任意の数値であることを確信しています。これは、ゼロが小さい数であると想定されていたためにゼロによる除算が発生した場合に必要なものです。入力が丸められ、測定エラー(手作業による物理的な測定など)の影響を受ける場合、2つの大きな量の差がゼロになる可能性があります。ゼロによる除算がなければ、あなたはいくつかの大きな数を得て、おそらくそれがどれほど大きいか気にしないでしょう。その場合、Inと-Infは完全に有効な結果です。
正式に正しいこともあります。拡張現実で作業していると言ってください。
。プログラムの実行を終了し、ユーザーに重大なエラーが発生したことを報告します。
[このオプション]は実用的ではありません…
もちろん、実用的です。実際に意味のあるプログラムを作成するのはプログラマの責任です。 0で除算しても意味がありません。したがって、プログラマが除算を実行している場合、除数が0でないことを検証するbeforehandを確認するのも彼/彼女の責任です。プログラマが検証チェックの実行に失敗した場合、s/heその間違いをできるだけ早く認識し、非正規化(NaN)または正しくない(0)計算結果は、その点では役に立たないでしょう。
オプション3は、最もわかりやすく、正直で、数学的に正しいものであるため、私があなたにすすめたものです。
エラーが無視される環境で重要なタスク(つまり、「nightly cron」)を実行することは、私には悪い考えのようです。これを機能にするのはひどい考えです。これにより、オプション1と2が除外されます。
オプション3が唯一の許容可能なソリューションです。例外は言語の一部である必要はありませんが、現実の一部です。終了メッセージは、エラーについて可能な限り具体的かつ有益であるべきです。
IEEE 754には、実際に問題に対する明確な解決策があります。 exceptions
を使用しない例外処理 http://en.wikipedia.org/wiki/IEEE_floating_point#Exception_handling
1/0 = Inf
-1/0 = -Inf
0/0 = NaN
これにより、すべての操作が数学的に意味をなすようになります。
\ lim_ {x\to 0} 1/x = Inf
私の意見では、IEEE 754に従うことは、計算がコンピューター上で行われるのと同じくらい正確であり、他のプログラミング言語の動作と一貫性があることを保証するため、最も理にかなっています。
発生する唯一の問題は、InfとNaNが結果を汚染し、ユーザーが問題の原因を正確に把握できないことです。 Juliaのようにこれをうまく行う言語を見てみましょう。
Julia> 1/0
Inf
Julia> -1/0
-Inf
Julia> 0/0
NaN
Julia> a = [1,1,1] ./ [2,1,0]
3-element Array{Float64,1}:
0.5
1.0
Inf
Julia> sum(a)
Inf
Julia> a = [1,1,0] ./ [2,1,0]
3-element Array{Float64,1}:
0.5
1.0
NaN
Julia> sum(a)
NaN
除算エラーは数学演算を通じて正しく伝播されますが、ユーザーは最終的に、エラーの原因となった演算を必ずしも知ることはできません。
edit:
ジム・ピヴァルスキの回答の2番目の部分は、基本的には私が上で言っているとおりです。私の悪い。
SQLは、プログラマーではない人が最も広く使用している言語であり、価値のあるものであれば何でも#3実行します。プログラマー以外のSQLの作成を観察および支援した私の経験では、この動作は一般によく理解されており、(caseステートメントなどで)簡単に補正できます。それはあなたが得るエラーメッセージが非常に直接的である傾向があることを助けます。 Postgres 9では、「エラー:ゼロによる除算」が発生します。
問題は「初心者ユーザーを対象としています。->したがって、...のサポートはありません」
なぜ例外処理は初心者ユーザーにとって問題があると思いますか?
何が悪いのですか? 「難しい」機能があるか、なぜ何かが起こったのか分かりませんか?何がもっと混乱するのでしょうか?コアダンプによるクラッシュまたは「致命的なエラー:ゼロ除算」?
代わりに、GREATメッセージエラーを目的とする方がFARが優れていると思います。そのため、代わりに次のように実行します。「計算が不適切、0/0を除算します(つまり、問題のkindだけでなく、問題を引き起こすDATAを常に表示します)。 。 PostgreSqlがメッセージエラーをどのように実行するかを見てください。
ただし、次のような例外を処理する他の方法を確認できます。
http://dlang.org/exception-safe.html
私は言語を構築することにも夢があります。この場合、Maybe/Optionalと通常の例外を組み合わせるのが最善だと思います。
def openFile(fileName): File | Exception
if not(File.Exist(fileName)):
raise FileNotExist(fileName)
else:
return File.Open()
#This cause a exception:
theFile = openFile('not exist')
# But this, not:
theFile | err = openFile('not exist')
私の見解では、言語はエラーを検出して処理するための一般的なメカニズムを提供する必要があります。プログラミングエラーは、コンパイル時(またはできるだけ早く)に検出する必要があり、通常はプログラムの終了につながります。予期しないデータや誤ったデータ、または予期しない外部条件に起因するエラーを検出し、適切なアクションを実行できるようにする必要がありますが、プログラムは可能な限り続行できます。
考えられるアクションには、(a)終了(b)アクションのユーザーへのプロンプト(c)エラーのログ記録(d)修正値の代入(e)コードでテストするインジケーターの設定(f)エラー処理ルーチンの呼び出しなどがあります。これらのうちどれを利用可能にし、どのような手段で選択しなければなりませんか。
私の経験では、誤った変換、ゼロ除算、オーバーフロー、範囲外の値などの一般的なデータエラーは無害であり、デフォルトでは別の値に置き換えてエラーフラグを設定することで処理する必要があります。この言語を使用する(プログラマーではない)は、エラーのあるデータを確認し、エラーをチェックして処理する必要性をすばやく理解します。
[例として、Excelスプレッドシートを考えてみましょう。数値がオーバーフローしたなどの理由で、Excelはスプレッドシートを終了しません。セルは奇妙な値を取得し、理由を見つけて修正します。]
だからあなたの質問に答えるために:あなたは確かに終了すべきではありません。 NaNを代用することもできますが、これを表示しないでください。計算が完了し、奇妙な高い値が生成されることを確認してください。また、エラーフラグを設定して、それを必要とするユーザーがエラーが発生したと判断できるようにします。
開示:私はまさにそのような言語実装(Powerflex)を作成し、1980年代にこの問題(および他の多くの問題)に正確に対処しました。過去20年ほどの間に、非プログラマー向けの言語はほとんどまたはまったく進歩しておらず、試してみたことに対する多くの批判が寄せられますが、成功することを願っています。
分子が0の場合に代替値を指定する3項演算子が好きでした。
私が見なかったもう1つのアイデアは、一般的な「無効な」値を生成することです。一般的に、「プログラムが何か悪いことをしたので、この変数には値がありません」。それ自体で完全なスタックトレースが実行されます。次に、その値をどこかで使用すると、結果は再び無効になり、新しい操作が最初に試行されます(つまり、無効な値が式に現れる場合、式全体が無効になり、関数呼び出しは試行されません。例外はブール演算子である-trueまたはinvalidはtrueでfalseで、invalidはfalseです-他にも例外がある場合があります)。その値がどこからも参照されなくなったら、問題が発生したチェーン全体のニースの長い説明を記録し、通常どおりビジネスを継続します。たぶん、プロジェクトリーダーまたは何かにトレースを電子メールで送信します。
基本的にはMaybeモナドのようなものです。それは失敗する可能性のある他のすべてのものでも機能し、人々が自分の無効を構築することを許可できます。そして、エラーが深すぎない限り、プログラムは実行し続けます。これは、ここで本当に必要なことだと思います。
ゼロ除算には2つの基本的な理由があります。
1.については、責任を負うのはユーザーであり、状況を改善する方法を最もよく知っているユーザーであるため、ユーザーが間違いを犯したことをユーザーに伝える必要があります。
2.の場合、これはユーザーの誤りではありません。アルゴリズム、ハードウェアの実装などを指さすことができますが、これはユーザーの誤りではないため、プログラムを終了したり、例外をスローしたりすることはできません(許可されている場合)。したがって、合理的な解決策は、何らかの合理的な方法で運用を継続することです。
この質問をしている人がケース1を求めているのが見えます。そのため、ユーザーに返信する必要があります。どのような浮動小数点標準、Inf、-Inf、Nanを使用しても、IEEEはこの状況に適合しません。基本的に間違った戦略。
プログラミング言語を作成するときは、その事実を利用し、ゼロ状態によるデバイスのアクションを含めることを必須にする必要があります。 a <= n/c:0 div-by-zero-action
私が今提案したのは、基本的にPLに「goto」を追加することです。
言語でそれを禁止します。つまり、通常は最初にテストすることにより、ゼロでないことが証明されるまで、数値による除算を許可しません。つまり。
int div = random(0,100);
int b = 10000 / div; // Error E0000: div might be zero