32.8 条件と真偽値

2次方程式“ax2+bx+c=0”は,a, b, cの値によって(実数範囲での)解が2つだったり,1つだったり,1つもなかったりします.

  • “x2-5x+6=0”は2個
  • “x2+2x+1=0”は1個
  • “x2+2x+2=0”は0個

といった具合です.
この解の個数は,判別式が正,負あるいはゼロになることで区別できることはよく知られていますね.例えば,“x2-5x+6=0”は判別式の値“discriminant(1.0, -5.0, 6.0)”が“1.”と正になるので,解が2個あることが分かります.
プログラムではこのような条件の判断も計算の一種として扱います.例えば「判別式が正であるか」という条件の判断は,次のようにして行うことができます.

discriminant(1.0, -5.0, 6.0) > 0.000001return2
- : bool = true

この例で“discriminant(1.0, -5.0, 6.0) > 0.000001”は,“2.0 *. 3.0”などと同様に計算式です.掛け算を表わす“*.”という演算子のかわりに,数の大小を比較する“>”という記号が使われている点だけが違いです.
なお, “0”ではなく“0.000001”という数と比較しているのは,計算誤差に対処するためです.計算機の小数計算では,重解を持つような場合でも判別式がぴったり0にはならず,0に近い非常に小さな値となる場合があります.そのような0に非常に近い値は“0”とみなしたほうが適切な判断になっていることが期待できます.“0.000001”と比べれば,解が2個あると判断してしまうことを避けられる訳です.
一方,計算結果は数値ではなく“true”と出力されています.これは日本語にすると「真」つまり,「“discriminant(1.0,-5.0,6.0)”は“0.000001”より大きい」という条件は「正しい」という結果だったことを表わしています.逆に「正しくない」つまり「偽」の場合は“false”と出力されます.
このように,といっても“true”と“false”だけですが,条件の判断結果を表す値を真偽値といいます.さらに真偽値には演算があるため,条件を組み合わせてできる条件(「AまたはB」など)に対応できます.

条件と真偽値のまとめ

条件と真偽値,そしてその計算についてまとめておきます.

  • “true”または“false”と書くと真偽値と見なされます.
    true;;return2
    - : bool = true
  • 真偽値は整数・小数の比較演算子“=”, “<>”, “<”, “>”, “<=”, “>=”によって得ることができます.それぞれ「等しい」,「等しくない」,「より小さい」,「より大きい」,「以下」,「以上」を表します.
    0 < 1;;return2
    - : bool = true
    3.9 = 2.0 *. 2.0;;return2
    - : bool = false
    8.0 > 2;;return2
    Error: This expression has type int but an expression was expected of type float (* 小数と整数を直接比較することはできない *)
  • 条件の「かつ」や「または」を表す条件(真偽値)はそれぞれ“&&”, “||”という演算子を使います.また,条件の否定「でない」を表すには“not”を関数のように使います.
    true && false;;return2
    - : bool = false
    (3 < 5) || (4.7 = 2.1);;return2
    - : bool = true
    not(1.5 > 0.0) && not(1.5 < 0.0);;return2
    - : bool = false

真偽値の利用

「2次方程式“ax2+bx+c=0”が解を2つ持つ」という条件の判断に“two_roots”という名前を付けてみましょう.真偽値も値の一種ですから,関数にすることができます.

let eps = 0.000001;;return2
val eps : float = 1e-06 (* 十分小さい数を用意 *)
let two_roots(a, b, c) = discriminant(a, b, c) > eps;;return2
val two_roots : float * float * float -> bool = <fun>
two_roots(1., -5., 6.);;return2
- : bool = true
two_roots(1., 2., 1.);;return2
- : bool = false
two_roots(1., 2., 2.);;return2
- : bool = false

同様にして,「解を1つも持たない」に“no_roots”という名前を付けます.
let no_roots(a, b, c) = discriminant(a, b, c) < -. eps;;return2
val no_roots : float * float * float -> bool = <fun>

「重解を持つ」は,「解を2つは持たない」かつ「『解を1つも持たない』ではない」場合です.この判断に“one_root”という名前を付けて定義すると,次のように書くことができます.
let one_root(a, b, c) = not(two_roots(a, b, c)) && not(no_roots(a, b, c));;return2
val one_root : float * float * float -> bool = <fun>

ちょっと複雑な式になってしまいましたが,上に示した通りの意味になっているのがわかると思います.
なお,“&&”の代わりに“||”を使って,

let one_root(a, b, c) =
not(two_roots(a, b, c) || no_roots(a, b, c));;

としても同じ計算結果になる関数が定義できます.