例外をサポートしない言語でゼロ除算を処理する方法は?


62

私は、いくつかのビジネス要件を解決するために新しいプログラミング言語を開発していますが、この言語は初心者ユーザーを対象としています。そのため、言語には例外処理のサポートがなく、追加しても例外を使用するとは思われません。

私は除算演算子を実装しなければならないポイントに達しましたが、ゼロ除算エラーを最適に処理する方法を疑問に思っていますか?

このケースを処理する方法は3つしかありません。

  1. エラーを無視し0て、結果として生成します。可能であれば警告を記録します。
  2. NaN数値の可能な値として追加しNaNますが、言語の他の領域の値を処理する方法についての質問が発生します。
  3. プログラムの実行を終了し、重大なエラーが発生したことをユーザーに報告します。

オプション#1が唯一の妥当な解決策のようです。オプション#3は、この言語を使用してロジックを夜間のcronとして実行するため、実用的ではありません。

ゼロ除算エラーを処理するための私の代替手段は何ですか?また、オプション#1を使用する場合のリスクは何ですか?


12
例外サポートを追加し、ユーザーがそれをキャッチしなかった場合、オプション#3があります
ラチェットフリーク

82
まったく新しいプログラミング言語を作成するために、どのような愚かな要件が必要になるのでしょうか?私の経験では、これまでに作成されたすべての言語は(多くの場合、両方で設計または実行中に)悪口を言い、それを得るために不当に多くの努力をました。最初の例外はいくつかありますが、2番目の例外は例外ではありません。これらはケースの<0.01%であるため、おそらく測定エラーです

16
@delnanほとんどの新しい言語は、ビジネスルールを実装方法から分離できるように作成されています。ユーザーはreject "Foo"、どのように実装されているかを知る必要はありませんが、キーワードが含まれている場合はドキュメントを拒否するだけですFoo。ユーザーが使い慣れている用語を使用して、言語を読みやすくするようにしています。ユーザーに独自のプログラミング言語を与えると、技術スタッフに依存せずにビジネスルールを追加できます。
-Reactgular

19
@マシュー・フォスカリーニ。決して、決してエラーを無視して、静かに0を返しません。除算を行うとき、0は完全に正当な値かもしれません(何らかの理由で、Power Basicにそのようなことがあり、本当に苦痛です)。浮動小数点数を除算する場合は、NanまたはInfが適しています(IEEE 754を参照して理由を理解してください)。整数を除算する場合、プログラムを停止できます。0による除算は許可されません(まあ、真の例外システムを実装したいのでなければ)。

16
私は、独自のチューリング完全なプログラミング言語を正当化するのに十分なほど複雑であるが、大幅に不正確な結果を許容するのに十分なほど複雑なビジネスドメインに魅了され、魅了されました。
マークE.ハーセ14

回答:


98

エラーを無視することは危険なアンチパターンであるため、#1に対して強くお勧めします。分析が困難なバグにつながる可能性があります。ゼロによる除算の結果を0に設定しても意味がありません。無意味な値でプログラムを実行し続けると問題が発生します。特に、プログラムが無人で実行されている場合。プログラムインタープリターがプログラムにエラーがあることに気付いた場合(およびゼロ除算はほとんどの場合設計エラーです)、データベースをゴミで埋めるよりも、それを中止してすべてをそのままにしておく方が通常は好まれます。

また、このパターンを徹底的に実行しても成功する可能性は低いでしょう。遅かれ早かれ、無視できないエラー状況(メモリ不足やスタックオーバーフローなど)に陥り、プログラムを終了する方法を実装する必要があります。

オプション#2(NaNを使用)は少し手間がかかりますが、思ったほどではありません。さまざまな計算でNaNを処理する方法は、IEEE 754標準で十分に文書化されているため、インタープリターが記述されている言語が行うことを実行できます。

ちなみに、非プログラマーが使用できるプログラミング言語を作成することは、1964年以来私たちがやろうとしていることです(Dartmouth BASIC)。これまでのところ、成功していません。とにかく幸運を。


14
+1ありがとう。あなたは私にエラーを投げるように説得しました、そしてあなたの答えを読んだ今、なぜ私がためらっていたのか分かりません。PHP私に悪い影響を与えてきました。
-Reactgular

24
はい、あります。あなたの質問を読んだとき、私はすぐに、誤った出力を生成し、エラーに直面してトラック輸送を続けることは非常にPHP風のことだと思いました。これを行う際にPHPが例外であることには、正当な理由があります。
ジョエル

4
BASICコメントの場合は+1。NaN初心者の言語で使用することはお勧めしませんが、一般的には素晴らしい答えです。
ロスパターソン

8
@Joel彼が十分に長生きしていたなら、ダイクストラはおそらく「[PHP]の使用は心を傷つけます。したがって、その教えは犯罪と見なされるべきです」と言ったでしょう。
ロスパターソン

12
@ロス。「コンピューターサイエンスの

33

1-エラーを無視0し、結果として生成します。可能であれば警告を記録します。

それは良い考えではありません。まったく。人々はそれに依存して起動し、あなたがそれを修正した場合、あなたは多くのコードを壊すでしょう。

2- NaN数値の可能な値として追加しNaNますが、言語の他の領域で値を処理する方法についての質問が発生します。

他の言語のランタイムが行うようにNaNを処理する必要があります。それ以上の計算でもNaNが生成され、すべての比較(NaN == NaNであっても)はfalseになります。

これは許容できると思いますが、必ずしも新人に優しいとは限りません。

3-プログラムの実行を終了し、重大なエラーが発生したことをユーザーに報告します。

これは私が考える最高の解決策です。その情報を手にすると、ユーザーは0を処理できるはずです。特に1晩に1回実行することを意図している場合は、テスト環境を提供する必要があります。

4番目のオプションもあります。除算を三項演算にします。次の2つのいずれでも機能します。

  • div(分子、分子、alternative_result)
  • div(分子、分子、alternative_denumerator)

ただし、NaN == NaNbe を作成した場合falseisNaN()ユーザーがNaNs を検出できるように関数を追加する必要があります。
AJMansfield

2
@AJMansfield:それか、人々が自分で実装します:isNan(x) => x != x。それでも、NaNプログラミングコードを作成したら、isNaNチェックの追加を開始するのではなく、原因を突き止めてそこで必要なチェックを行う必要があります。したがって、NaN完全に伝播することが重要です。
back2dos

5
NaNsは主に直感に反します。初心者の言語では、彼らは到着時に死んでいます。
ロスパターソン

2
@RossPattersonしかし、初心者は簡単に言うことができます1/0-あなたはそれで何かをしなければなりません。Infまたは以外に有用な結果はない可能性がありますNaN-エラーをプログラムにさらに伝播させる何か。そうでなければ、唯一の解決策はこの時点でエラーで停止することです。
マークハード

1
オプション4は、関数の呼び出しを許可することで改善できます。これにより、予期しない0除数から回復するために必要なアクションを実行できます。
Cyber​​Fonic

21

極端な偏見で実行中のアプリケーションを終了します。(適切なデバッグ情報を提供しながら)

次に、除数がゼロになる可能性がある条件(ユーザーが入力した値など)を特定して処理するようにユーザーを教育します。


13

Haskell(およびScalaでも同様)では、例外をスローする(またはnull参照を返す)代わりに、ラッパータイプMaybeEither使用できます。ではMaybe、ユーザー彼が得た値が「空」であるかどうかをテストする機会を持っている、または彼が「アンラップ」のデフォルト値を提供するかもしれません。Eitherは似ていますが、問題がある場合はそれを説明するオブジェクト(エラー文字列など)を返します。


1
本当ですが、Haskellはこれをゼロ除算に使用しないことに注意してください。代わりに、すべてのHaskellタイプには、可能な値として暗黙的に「ボトム」があります。これは、終了に失敗する式の「値」であるという意味で、nullポインターとは異なります。もちろん、値として非終了をテストすることはできませんが、操作上のセマンティクスでは、終了に失敗したケースは式の意味の一部です。Haskellでは、その「ボトム」値は、error "some message"評価される関数などの追加のエラーケースの結果も処理します。
Steve314

個人的には、プログラム全体を中止する効果が有効であると考えられる場合、純粋なコードが例外をスローする効果を持たない理由はわかりませんが、それは私だけです- Haskell純粋な式が例外をスローすることを許可しません。
Steve314

例外をスローする以外に、提案されたすべてのオプションがユーザーが間違えたことを伝えないため、これは良いアイデアだと思います。基本的な考え方は、ユーザーがプログラムに与えた値を間違えるということです。そのため、プログラムはユーザーに間違った入力をしたことを伝える必要があります(その後、ユーザーは修正方法を考えることができます)。ユーザーに間違いを伝えることなく、解決策は奇妙に感じます。
情報14

Rustプログラミング言語は、標準ライブラリでこれを広く使用しています。
アオガガビア14

12

他の答えはあなたのアイデアの相対的なメリットをすでに考慮しています。別の方法を提案します。基本的なフロー分析を使用して、変数ゼロになる可能性があるかどうかを判断します。次に、潜在的にゼロである変数による除算を単に禁止できます。

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言語にそのような機能がある場合は、範囲とチェックに自然に拡張されます。


4
きちんとしたアイデアですが、このタイプの制約解決はNP完全です。のようなものを想像してくださいdef foo(a,b): return a / ord(sha1(b)[0])。静的アナライザーはSHA-1を反転できません。Clangにはこのタイプの静的分析があり、浅いバグを見つけるのに最適ですが、処理できないケースがたくさんあります。
マークE.ハーセ14

9
これはNP完全ではありません、これは不可能です-補題を停止すると言います。ただし、静的アナライザーはこれを解決する必要はありません。このようなステートメントを単に解くだけで、明示的なアサーションまたは装飾を追加する必要があります。
MK01 14

1
@ MK01:つまり、分析は「保守的」です。
ジョンパーディ14

11

番号1(デバッグ不能なゼロを挿入)は常に不良です。#2(NaNを伝播する)と#3(プロセスを強制終了する)の選択はコンテキストに依存し、Numpyのように理想的にはグローバル設定にする必要があります。

1つの大きな統合された計算を行っている場合、NaNの伝播は最終的に計算全体に広がり感染するため、悪い考えです-朝の結果を見て、それらがすべてNaNであることがわかると、 d結果を捨てて、とにかくやり直す必要があります。プログラムが終了し、深夜に電話がかかってきて、それを修正したほうが良かったのですが、少なくとも無駄な時間の点では。

多くのほとんど独立した計算(map-reduceや恥ずかしいほどの並列計算など)を実行しており、NaNが原因で一部の割合が使用できないことを許容できる場合、これがおそらく最適なオプションです。プログラムを終了し、不正でありゼロで除算される1%のために、有益で有益な99%を実行しないのは間違いかもしれません。

NaNに関連する別のオプション:同じIEEE浮動小数点仕様がInfと-Infを定義し、これらはNaNとは異なる方法で伝播されます。たとえば、Inf>任意の数および-Inf <任意の数であることはかなり確信しています。これは、ゼロが小さな数であるはずであるためにゼロによる除算が行われた場合に必要なものです。入力が丸められ、測定誤差(手作業による物理測定など)の影響を受ける場合、2つの大きな量の差がゼロになる可能性があります。ゼロによる除算がなければ、いくつかの大きな数字が得られていたでしょうし、それがどれほど大きいか気にしないかもしれません。その場合、Inおよび-Infは完全に有効な結果です。

それは形式的にも正しい場合があります。拡張現実で作業していると言ってください。


しかし、分母が正であるか負であるかがわからないため、-infが必要なときに除算が+ infを生成する場合があります。
ダニエルルバロフ

確かに、測定誤差は小さすぎて+ infと-infを区別できません。これは、リーマン球に非常によく似ています。リーマン球では、複雑な平面全体が、正確に1つの無限点(原点と正反対の点)を持つボールにマッピングされます。非常に大きな正の数、非常に大きな負の数、さらには非常に大きな虚数や複素数でさえも、すべてその1つの無限点に近いものです。わずかな測定誤差では、それらを区別できません。
ジムピバルスキー

そのようなシステムで作業している場合、+ 0と-0が同等であると識別しなければならないのと同じように、+ infと-infが同等であると識別しなければなりません。
ジムピバルスキー

8

3.プログラムの実行を終了し、重大なエラーが発生したことをユーザーに報告します。

[このオプション]は実用的ではありません…

もちろん実用的です。実際に意味のあるプログラムを作成するのはプログラマの責任です。0で除算しても意味がありません。したがって、プログラマーが除算を実行している場合、除数が0に等しくないことを事前に確認することも彼/彼女の責任です。プログラマーが検証チェックを実行できなかった場合は、すぐにその誤りを認識する必要があります可能性があり、非正規化(NaN)または不正(0)の計算結果は単にその点では役に立ちません。

オプション3は、最も率直で、正直で、数学的に正しいものであるため、たまたま私がお勧めしたものです。


4

エラーが無視される環境で重要なタスク(つまり、「夜間のcron」)を実行するのは悪い考えのように思えます。これを機能にするのはひどい考えです。これにより、オプション1および2が除外されます。

オプション3が唯一の受け入れ可能なソリューションです。例外は言語の一部である必要はありませんが、現実の一部です。終了メッセージは、エラーについて可能な限り具体的かつ有益であるべきです。


3

IEEE 754には、実際に問題に対して明確に定義されたソリューションがあります。http://en.wikipedia.org/wiki/IEEE_floating_point#Exception_handlingを使用しない例外処理exceptions

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では「ERROR:division by zero」を受け取ります。


2

問題は「初心者ユーザーを対象としています。->したがって、...に対するサポートはありません」

例外処理が初心者ユーザーにとって問題があると考えるのはなぜですか?

さらに悪いことは何ですか?「難しい」機能を持っている、または何かが起こった理由がわからない?さらに混乱させるものは何ですか?コアダンプまたは「致命的なエラー:ゼロによる除算」によるクラッシュ?

代わりに、GREATメッセージエラーを狙う方がはるかに良いと思います。代わりに、「不正な計算、0/0の除算」(つまり、問題の種類だけでなく、問題を引き起こすデータを常に表示する)を実行してください。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')

1

私の見解では、あなたの言語はエラーを検出して処理するための一般的なメカニズムを提供すべきです。プログラミングエラーはコンパイル時に(またはできるだけ早く)検出され、通常はプログラムの終了につながるはずです。予期しないデータまたは誤ったデータ、または予期しない外部条件に起因するエラーを検出し、適切なアクションに使用できるようにする必要がありますが、可能な限りプログラムを続行できます。

考えられるアクションには、(a)終了(b)ユーザーにアクションを促す(c)エラーをログに記録する(d)修正値を置き換える(e)コードでテストするインジケーターを設定する(f)エラー処理ルーチンを呼び出す これらのどれを利用可能にし、どのような手段で選択をしなければなりませんか。

私の経験から、変換の誤り、ゼロ除算、オーバーフロー、範囲外の値などの一般的なデータエラーは良性であり、デフォルトでは別の値に置き換えてエラーフラグを設定することで処理する必要があります。この言語を使用する(プログラマーではない)ユーザーは、障害のあるデータを確認し、エラーをチェックして処理する必要性をすばやく理解します。

[例として、Excelスプレッドシートを考えます。Excelは、数値がオーバーフローしたなどの理由でスプレッドシートを終了しません。セルは奇妙な値を取得し、その理由を見つけて修正します。]

あなたの質問に答えるために:あなたは確かに終了すべきではありません。NaNを使用することもできますが、NaNを表示しないでください。計算が完了し、奇妙な高い値が生成されることを確認してください。また、エラーフラグを設定して、必要なユーザーがエラーが発生したことを判断できるようにします。

開示:私はまさにそのような言語実装(Powerflex)を作成し、1980年代にまさにこの問題(および他の多くの問題)に対処しました。過去20年ほどで非プログラマー向けの言語の進歩はほとんど、またはまったくありませんでした。試してみると多くの批判が寄せられますが、本当に成功することを願っています。


1

分子が0の場合に代替値を提供する三項演算子が好きでした。

私が見なかったもう1つのアイデアは、一般的な「無効な」値を生成することです。一般的な「プログラムが何か悪いことをしたため、この変数には値がありません」。これは、それ自体で完全なスタックトレースを実行します。次に、その値をどこかで使用すると、結果は再び無効になり、新しい操作が先頭に試行されます(つまり、式に無効な値が表示される場合、式全体が無効になり、関数呼び出しは試行されません;例外はブール演算子である-trueまたはinvalidはtrueおよびfalseであり、invalidはfalseです-それには他の例外もあります)。その値がどこでも参照されなくなったら、物事が間違っていたチェーン全体の長い説明を記録し、通常どおりビジネスを続けます。たぶん、プロジェクトリーダーなどにトレースをメールで送信します。

基本的には多分モナドのようなもの。それは失敗する可能性のある他のものでも動作し、人々が自分の無効なものを構築できるようにすることができます。そして、エラーがあまり深くない限り、プログラムは実行を続けます。それはここで本当に望まれていることです。


1

ゼロ除算には2つの基本的な理由があります。

  1. 正確なモデル(整数など)では、入力が間違っているため、ゼロの除算DBZが得られます。これは、ほとんどの人が考えるDBZの一種です。
  2. 非浮動モデル(浮動ptなど)では、入力が有効であっても丸め誤差のためにDBZを取得する場合があります。これは通常私たちが考えていないことです。

1.責任者であり、状況を改善する最善の方法を知っている人であるため、間違いを犯したことをユーザーに伝える必要があります。

2の場合、これはユーザーの障害ではありません。アルゴリズム、ハードウェアの実装などを指すことができますが、これはユーザーの障害ではありません。したがって、合理的な解決策は、何らかの合理的な方法で操作を継続することです。

この質問をしている人がケース1を求めているのを見ることができます。そのため、ユーザーとやり取りする必要があります。どんな浮動小数点標準、Inf、-Inf、Nan、IEEEを使用しても、IEEEはこの状況に適合しません。基本的に間違った戦略。


0

言語でそれを禁止します。つまり、通常は最初にテストすることにより、ゼロではないことが証明されるまで、数値による除算を禁止します。すなわち。

int div = random(0,100);
int b = 10000 / div; // Error E0000: div might be zero

これを行うには、整数ではなく、自然数である新しい数値型が必要です。それは...難しい...対処することができます。
セルビー

@Servy:いいえ、そうしません。なんで?可能な値を決定するためにコンパイラにロジックが必要ですが、とにかくそれが必要です(最適化の理由から)。
MSalters

ゼロとゼロ以外の値に別のタイプがない場合、一般的なケースでは問題を解決できません。誤検知があり、ユーザーに実際よりも頻繁にゼロをチェックさせたり、ゼロで割り切れる状況を作り出したりします。
セルビー

@Servy:間違いです。コンパイラは、そのような型を必要とせずにその状態を追跡できます。たとえば、GCCはすでにそうしています。たとえば、Cタイプでintはゼロの値を使用できますが、GCCはコード内の特定のintをゼロにすることはできません。
MSalters

2
ただし、特定の場合のみ。すべての場合において、100%の精度でこれを行うことはできません。偽陽性または偽陰性のいずれかがあります。これは確かに真実です。たとえば、完了してもなくてもよいコードのスニペットを作成できます。コンパイラが終了したかどうかさえわからない場合、結果のintがゼロ以外であるかどうかをどのように知ることができますか?単純な明らかなケースをキャッチできますが、すべてのケースではありません。
セルビー

0

プログラミング言語を作成する場合、事実を利用し、ゼロ状態によるdeviseのアクションを含めることを必須にする必要があります。a <= n / c:0 div-by-zero-action

先ほど提案したことは、基本的にPLに「goto」を追加することです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.