1

signum函数是符号的通常数学定义的实现,它有条件地返回 {-1,0,1} 中的值。这是一个理论定义,因此,它没有考虑操作的计算成本或值的数据类型,因此乘以 (-1) 是改变符号的零成本理论方法。因此,它不是编程中最有用的符号处理。

该案例signum a == 0并不是真正有用,因为您始终可以直接进行测试a == 0,而无需额外的计算成本signum a。至于其他两个值,我认为它们仅以 3 种一般方式使用:

  • 您可以测试一个值是正数还是负数以有条件地启动不同的代码,如:

    f x y | signum x == -1  = h x y
          | otherwise       = g x y
    
  • 或者您将某物乘以1-1在使用它之前进行操作,例如:

    f x y = g x (y * b) where 
          b = signum x
    
  • 或者您在操作之前添加1或添加某些内容,如:-1

    f x y = g x (y + b) where 
          b = signum x
    

Bool在所有情况下,符号值会更好。因此,我们只需要函数将 a 分解Num为绝对值和布尔符号,以及一个逆函数,它根据布尔条件(表示符号)改变值的符号。这个函数相当于乘以1或乘以-1一个数,所以我们将其定义为类似于 的运算符(*)。:

sgn  a      = a >= 0
(*.) a True = a
(*.) a _    = -a
abs  a      = a *. sgn a
signum1 a   = 1 *. sgn a

我添加了一个signum只能返回“{-1,1}”的二分变体。请注意,在它前面加上signum 0 = 0我们会得到通常的signum功能,但第三种情况是我认为通常没有用的。

我们可以类似地编写加法运算符,因为添加1-1取决于某事物的符号是非常常见的情况(您可以看到这些运算符只是将Trueas1Falseas 视为-1):

(+.) a b    = a + 1 *. b
(-.) a b    = a - 1 *. b

我们甚至可以将声明包含在一个名为 的类中Signed,以便于使用,包括正确的签名和固定性。

这样,上面的一般示例不仅在代码上,而且在执行时间和空间上都会简化,因为我们避免了乘法((*.)改为使用),一旦我们有了 a Bool,我们就避免了额外的比较,我们可以从一种类型的data 并将其用于另一种类型而不需要类型转换,我们使用短类型Bool而不是潜在的长类型 class Num。但是我们获得了更大的灵活性,同时允许对代码和数据类型进行一些优化。

那么,我的问题是,是否存在与此处公开的三个一般用例不同的情况,即这种方法不容易涵盖的情况,当前signum函数优于布尔符号方法的情况。更准确地说,我可以完全避免使用当前signum函数而不损失效率或代码清晰度吗?


编辑:在 Reid Barton 评论之后,我将第一段修改为更“中性”的方式。


进度更新:在当前答案和评论的帮助下,为了简单明了,这种方法的代码得到了极大的改进。

4

3 回答 3

10

您假设“积极”和“消极”是仅有的两个可能的迹象。但是对于 eg Complex Double,该signum操作返回一个具有相同“方向”但幅度为 1 的复数:

Data.Complex> signum (3 :+ 4)
0.6 :+ 0.8
于 2017-01-31T00:23:28.203 回答
4

我已经使用这样的函数通过一系列移动到(正交和对角)相邻单元格来将光标导航到方形网格中的目标偏移量。

move :: (Int, Int) -> [(Int, Int)]
move (0, 0) = []
move (x, y) = (dx, dy) : move (x - dx, y - dy)
  where dx = signum x
        dy = signum y
于 2017-01-31T01:32:06.663 回答
4

为了解决时间复杂度的问题:

分支不是免费的,如果您必须(在概念上)将值乘以几个不同位置的相同值的符号结果,那么在 - 子句中拥有let s = signum x in ...或拥有该绑定可能会更有效where。您不再需要每次都经过一个分支。还要记住,在某些情况下,由于分支行为违背了分支预测器的预期,代码可能会变慢

例如,假设您有这样的代码:

f x y z = (s * y) / (1 + (s * z))
  where
    s = signum x

效率分析通常不像您期望的那样明确,并且可能高度依赖于特定程序的非常具体的方面,正如我在上面链接的问题中所看到的那样,因此经常引用“优化前的配置文件”的建议。在我链接到的问题中,执行更多指令的代码版本实际上比执行更少指令的版本运行得更快(我可以在我的机器上验证这些结果,即使我在分析中包含了额外的排序指令)!

于 2017-01-31T07:08:50.310 回答