函数计算整数的“log”

时间:2014-10-17 01:02:02

标签: haskell

我写了以下函数。

f :: Integer -> Integer
f x = if (odd x) then 0 else (floor . logBase 2) x

但是发生以下编译时错误:

  

F.hs:2:31:       使用floor' Possible fix: add an instance declaration for (RealFrac Integer) In the first argument of(。)'时没有(RealFrac Integer)的实例,即“floor”       在表达式:floor。 logBase 2       在表达式中:( floor.logBase 2)x

     

F.hs:2:39:       使用logBase' Possible fix: add an instance declaration for (Floating Integer) In the second argument of(。)',即`logBase 2'时,没有(浮动整数)的实例       在表达式:floor。 logBase 2       在表达式中:( floor.logBase 2)x失败,模块加载:无。

如何正确编写上述功能?

2 个答案:

答案 0 :(得分:9)

这将是一个很长的时间,因为我不仅仅是为您提供有效的代码,而是深入解释这个问题,以便您更好地理解GHC的类型错误。

正如已经简要回答的那样(并且当类型错误尽力告诉你,虽然它当然不够清楚),为了使用logBase x y,两个参数x和{{ 1}}必须都是“浮点”类型类的实例。

特别是,ylogBase类型类的方法(来自Prelude's documentation):

Floating

我们也发现,也来自前奏曲:

class Fractional a => Floating a where Source
    logBase :: a -> a -> a 

也就是说,为了使用函数class (Real a, Fractional a) => RealFrac a where Source floor :: Integral b => a -> b ,我们需要两个参数:(floor . logBase)(因为Fractional需要这个),logBase(自{{{ 1}}需要两个)。这两者的合并被定义为Real,这正是GHC抱怨你未能提供它(在你的函数的类型声明中)。

为什么抱怨?在Prelude中,我们为floor找到了以下RealFrac声明。请注意,“instance”缺失:

RealFrac

Haskell的工作方式是,如果你给它一个整数文字(没有小数点的连续数字),它将假设它属于RealFrac Integer类型类(并将试图弄清楚是否要制作它是隐式RealFrac Double RealFrac Float RealFrac CDouble RealFrac CFloat Integral a => RealFrac (Ratio a) HasResolution a => RealFrac (Fixed a) Integral,但永远不会隐式地将整数文字提升为其中一个Integer类(包括Int) 。由于没有“Fractional”行,这意味着您不能指望Haskell编译代码。

你告诉Haskell你将通过你的显式类型声明给它RealFrac个实例(这是为什么这些通常是一个好主意的原因之一 - Haskell会悄悄地接受你的函数声明,否则,在使用它的客户端函数中抛出编译错误):

RealFrac Integer

解决方案是使用以下函数(将Integral转换为任何兼容的f :: Integer -> Integer 类型)来提升整数:

Integral

Num执行相反方向的转换(从fromIntegral :: (Integral a, Num b) => a -> b Floor),如其类型所示。

总之,您只需说出

即可
Fractional

请注意Integral调用,使参数类型与编译器期望的内容兼容,以及使用f :: Integer -> Integer f x = if (odd x) then 0 else (floor . logBase 2.0 . fromIntegral) x fromIntegral字面值)作为基础。< / p>

答案 1 :(得分:1)

请注意logBase需要转换为Floating类型,这可能会导致错误的结果。

f :: Integer -> Integer
f x = if (odd x) then 0 else (floor . logBase 2.0 . fromIntegral) x

λ> f (2^99999)
    179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216

这是因为(2^99999 :: Double) = Infinityfloor Infinity显然评估为......令人惊讶的事情。

integer-logarithms包提供了integerLog2功能,效果更好:

λ> import Math.NumberTheory.Logarithms

λ> integerLog2 (2^99999)
    99999
it :: Int

integer-logarithms中的函数只是integer-gmp的一个薄包装,所以你也可以直接使用它:

λ> :set -XMagicHash

λ> import GHC.Exts

λ> import GHC.Integer.Logarithms

λ> I# (integerLog2# (2^99999))
    99999
it :: Int

请注意,即使结果不是2的幂,这些函数也会返回一个值:

λ> integerLog2 1023
    9