1. ホーム
  2. python

[解決済み] 浮動小数点数のコレクションに対するPythonのユニットテスト(assertAlmostEqual)

2023-02-19 01:57:16

質問

その assertAlmostEqual(x, y) メソッドで Python のユニットテストフレームワーク xy は浮動小数点数であると仮定するとほぼ等しくなります。

の問題点は assertAlmostEqual() は浮動小数点数に対してのみ動作するということです。私は、以下のようなメソッドを探しています。 assertAlmostEqual() のような、float のリスト、float のセット、float の辞書、float のタプル、float のタプルのリスト、float のリストのセット、などに対して機能するメソッドを探しています。

例えば x = 0.1234567890 , y = 0.1234567891 . x そして y は、最後の一桁を除くすべての桁で一致するため、ほぼ等しくなります。したがって self.assertAlmostEqual(x, y)True なぜなら assertAlmostEqual() は浮動小数点数に対して機能するからです。

より一般的な assertAlmostEquals() への次の呼び出しを評価するような True :

  • self.assertAlmostEqual_generic([x, x, x], [y, y, y]) .
  • self.assertAlmostEqual_generic({1: x, 2: x, 3: x}, {1: y, 2: y, 3: y}) .
  • self.assertAlmostEqual_generic([(x,x)], [(y,y)]) .

そのような方法があるのでしょうか、それとも自分で実装しなければならないのでしょうか。

明確にしてください。

  • assertAlmostEquals() は、オプションのパラメータ places というオプションのパラメータがあり、小数点以下を丸めた差分を計算することで比較されます。 places . デフォルトでは places=7 であり、したがって self.assertAlmostEqual(0.5, 0.4) は偽であるのに対して self.assertAlmostEqual(0.12345678, 0.12345679) は真である。私の推測では assertAlmostEqual_generic() は同じ機能を持つはずです。

  • 二つのリストは、全く同じ順番でほぼ同じ数を持っている場合、ほぼ等しいとみなされます。 正式には for i in range(n): self.assertAlmostEqual(list1[i], list2[i]) .

  • 同様に、2つの集合は、(各集合に順序を割り当てることによって)ほぼ等しいリストに変換できる場合、ほぼ等しいとみなされます。

  • 同様に、2つの辞書は、それぞれの辞書のキーセットがもう一方の辞書のキーセットにほぼ等しく、そのようなほぼ等しいキーペアごとに対応するほぼ等しい値が存在する場合、ほぼ等しいと見なされます。

  • 一般的には 私は、2 つのコレクションが、互いにほぼ等しいだけのいくつかの対応する浮動小数点数を除いて等しい場合、ほぼ等しいと考えます。言い換えれば、私はオブジェクトを本当に比較したいのですが、途中で浮動小数点数を比較するときは、低い (カスタマイズされた) 精度で比較したいと思います。

どのように解決するのですか?

ここでは、一般的な is_almost_equal(first, second) 機能 :

まず、比較する必要のあるオブジェクトを複製します ( firstsecond しかし、正確なコピーを作成しないでください。オブジェクトの内部で遭遇した浮動小数点以下の桁をカットしてください。

のコピーができたので firstsecond を比較すると、小数点以下が消えています。 firstsecond を使用して == 演算子を使っています。

があると仮定しましょう。 cut_insignificant_digits_recursively(obj, places) を複製する関数があるとします。 obj を複製するが places の各浮動小数点の最上位桁を残し、元の obj . 以下は,実際に動作する is_almost_equals(first, second, places) :

from insignificant_digit_cutter import cut_insignificant_digits_recursively

def is_almost_equal(first, second, places):
    '''returns True if first and second equal. 
    returns true if first and second aren't equal but have exactly the same
    structure and values except for a bunch of floats which are just almost
    equal (floats are almost equal if they're equal when we consider only the
    [places] most significant digits of each).'''
    if first == second: return True
    cut_first = cut_insignificant_digits_recursively(first, places)
    cut_second = cut_insignificant_digits_recursively(second, places)
    return cut_first == cut_second

そして、これは cut_insignificant_digits_recursively(obj, places) :

def cut_insignificant_digits(number, places):
    '''cut the least significant decimal digits of a number, 
    leave only [places] decimal digits'''
    if  type(number) != float: return number
    number_as_str = str(number)
    end_of_number = number_as_str.find('.')+places+1
    if end_of_number > len(number_as_str): return number
    return float(number_as_str[:end_of_number])

def cut_insignificant_digits_lazy(iterable, places):
    for obj in iterable:
        yield cut_insignificant_digits_recursively(obj, places)

def cut_insignificant_digits_recursively(obj, places):
    '''return a copy of obj except that every float loses its least significant 
    decimal digits remaining only [places] decimal digits'''
    t = type(obj)
    if t == float: return cut_insignificant_digits(obj, places)
    if t in (list, tuple, set):
        return t(cut_insignificant_digits_lazy(obj, places))
    if t == dict:
        return {cut_insignificant_digits_recursively(key, places):
                cut_insignificant_digits_recursively(val, places)
                for key,val in obj.items()}
    return obj

コードとそのユニットテストはこちらで公開されています。 https://github.com/snakile/approximate_comparator . 改善やバグフィックスを歓迎します。