如何对浮子和复合物进行近似结构模式匹配

发布于 2025-02-06 18:10:10 字数 770 浏览 1 评论 0原文

我已经阅读并理解 flo floating Point Point rough-off rough off jearses 例如:

>>> sum([0.1] * 10) == 1.0
False

>>> 1.1 + 2.2 == 3.3
False

>>> sin(radians(45)) == sqrt(2) / 2
False

i i i还知道如何使用 math.isclose() cmath.isclose()

问题是如何将这些工作应用于Python的匹配/案例语句。我希望这可以工作:

match 1.1 + 2.2:
    case 3.3:
        print('hit!')  # currently, this doesn't match

I've read about and understand floating point round-off issues such as:

>>> sum([0.1] * 10) == 1.0
False

>>> 1.1 + 2.2 == 3.3
False

>>> sin(radians(45)) == sqrt(2) / 2
False

I also know how to work around these issues with math.isclose() and cmath.isclose().

The question is how to apply those work arounds to Python's match/case statement. I would like this to work:

match 1.1 + 2.2:
    case 3.3:
        print('hit!')  # currently, this doesn't match

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

童话里做英雄 2025-02-13 18:10:10

解决方案的关键是构建一个包装器,该包装器覆盖__ eq __方法并将其替换为近似匹配:

import cmath

class Approximately(complex):

    def __new__(cls, x, /, **kwargs):
        result = complex.__new__(cls, x)
        result.kwargs = kwargs
        return result

    def __eq__(self, other):
        try:
            return isclose(self, other, **self.kwargs)
        except TypeError:
            return NotImplemented

它为float值和复杂值创建近似的等价测试:

>>> Approximately(1.1 + 2.2) == 3.3
True
>>> Approximately(1.1 + 2.2, abs_tol=0.2) == 3.4
True
>>> Approximately(1.1j + 2.2j) == 0.0 + 3.3j
True

这是如何在中使用它匹配/案例语句:

for x in [sum([0.1] * 10), 1.1 + 2.2, sin(radians(45))]:
    match Approximately(x):
        case 1.0:
            print(x, 'sums to about 1.0')
        case 3.3:
            print(x, 'sums to about 3.3')
        case 0.7071067811865475:
            print(x, 'is close to sqrt(2) / 2')
        case _:
            print('Mismatch')

此输出:

0.9999999999999999 sums to about 1.0
3.3000000000000003 sums to about 3.3
0.7071067811865475 is close to sqrt(2) / 2

The key to the solution is to build a wrapper that overrides the __eq__ method and replaces it with an approximate match:

import cmath

class Approximately(complex):

    def __new__(cls, x, /, **kwargs):
        result = complex.__new__(cls, x)
        result.kwargs = kwargs
        return result

    def __eq__(self, other):
        try:
            return isclose(self, other, **self.kwargs)
        except TypeError:
            return NotImplemented

It creates approximate equality tests for both float values and complex values:

>>> Approximately(1.1 + 2.2) == 3.3
True
>>> Approximately(1.1 + 2.2, abs_tol=0.2) == 3.4
True
>>> Approximately(1.1j + 2.2j) == 0.0 + 3.3j
True

Here is how to use it in a match/case statement:

for x in [sum([0.1] * 10), 1.1 + 2.2, sin(radians(45))]:
    match Approximately(x):
        case 1.0:
            print(x, 'sums to about 1.0')
        case 3.3:
            print(x, 'sums to about 3.3')
        case 0.7071067811865475:
            print(x, 'is close to sqrt(2) / 2')
        case _:
            print('Mismatch')

This outputs:

0.9999999999999999 sums to about 1.0
3.3000000000000003 sums to about 3.3
0.7071067811865475 is close to sqrt(2) / 2
死开点丶别碍眼 2025-02-13 18:10:10

雷蒙德(Raymond)的回答非常奇特和人体工程学,但对于可能更简单的事情来说似乎很魔力。一个更小的版本将只是捕获计算值,并明确检查事物是否“关闭”,例如:

import math

match 1.1 + 2.2:
    case x if math.isclose(x, 3.3):
        print(f"{x} is close to 3.3")
    case x:
        print(f"{x} wasn't close)

我也建议仅使用cmath.isclose()在哪里/何时实际上需要它,使用适当的类型可让您确保代码正在执行您的期望。

上面的示例只是用于演示匹配的最小代码,如注释中指出的那样,可以使用传统的 If 语句更容易地实现。冒着脱离原始问题的风险,这是一个更完整的示例:

from dataclasses import dataclass

@dataclass
class Square:
    size: float

@dataclass
class Rectangle:
    width: float
    height: float

def classify(obj: Square | Rectangle) -> str:
    match obj:
        case Square(size=x) if math.isclose(x, 1):
            return "~unit square"

        case Square(size=x):
            return f"square, size={x}"

        case Rectangle(width=w, height=h) if math.isclose(w, h):
            return "~square rectangle"

        case Rectangle(width=w, height=h):
            return f"rectangle, width={w}, height={h}"

almost_one = 1 + 1e-10
print(classify(Square(almost_one)))
print(classify(Rectangle(1, almost_one)))
print(classify(Rectangle(1, 2)))

不确定我在这里是否实际使用Match语句,但希望更有代表性!

Raymond's answer is very fancy and ergonomic, but seems like a lot of magic for something that could be much simpler. A more minimal version would just be to capture the calculated value and just explicitly check whether the things are "close", e.g.:

import math

match 1.1 + 2.2:
    case x if math.isclose(x, 3.3):
        print(f"{x} is close to 3.3")
    case x:
        print(f"{x} wasn't close)

I'd also suggest only using cmath.isclose() where/when you actually need it, using appropriate types lets you ensure your code is doing what you expect.

The above example is just the minimum code used to demonstrate the matching and, as pointed out in the comments, could be more easily implemented using a traditional if statement. At the risk of derailing the original question, this is a somewhat more complete example:

from dataclasses import dataclass

@dataclass
class Square:
    size: float

@dataclass
class Rectangle:
    width: float
    height: float

def classify(obj: Square | Rectangle) -> str:
    match obj:
        case Square(size=x) if math.isclose(x, 1):
            return "~unit square"

        case Square(size=x):
            return f"square, size={x}"

        case Rectangle(width=w, height=h) if math.isclose(w, h):
            return "~square rectangle"

        case Rectangle(width=w, height=h):
            return f"rectangle, width={w}, height={h}"

almost_one = 1 + 1e-10
print(classify(Square(almost_one)))
print(classify(Rectangle(1, almost_one)))
print(classify(Rectangle(1, 2)))

Not sure if I'd actually use a match statement here, but is hopefully more representative!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文