【问题标题】:Function to Calculate `log` of Integer计算整数“log”的函数
【发布时间】:2014-10-17 01:02:01
【问题描述】:

我写了以下函数。

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 。对数库 2 在表达式中: (floor .logBase 2) x

F.hs:2:39: 没有因使用 logBase' Possible fix: add an instance declaration for (Floating Integer) In the second argument of(.)' 产生的 (Floating Integer) 实例,即 `logBase 2' 在表达式中: floor 。对数库 2 在表达式中:(floor .logBase 2) x Failed, modules loaded: none.

如何正确编写上述函数?

【问题讨论】:

  • fromIntegral 是你的朋友。
  • 谢谢,卢基。这特别有用 - stackoverflow.com/a/1970515/409976
  • floor . logBase 2 => floor . logBase 2 . fromIntegral
  • 为什么不想看奇数呢?日志适用于任何正数。
  • 我没有指定的错误。从 2013 年开始,我正在做这个作业problem 5 - 用于自学。我应该更好地措辞这个问题

标签: haskell


【解决方案1】:

这会有点长,因为我不仅想给你提供有效的代码,还想深入解释这个问题,以便你更好地理解 GHC 的类型错误。

正如已经简单回答的那样(并且类型错误尽力告诉你,虽然肯定不够清楚),为了使用logBase x yxy 这两个参数必须都是“浮点”类型类的实例。

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

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需要这个)和Real(因为floor需要这两个参数)。这两者的合并定义为RealFrac,而这正是 GHC 抱怨您未能提供它的原因(在您的函数的类型声明中)。

为什么抱怨?在 Prelude 中,我们发现以下 instance 声明为 RealFrac。请注意,缺少“RealFrac Integer”:

RealFrac Double  
RealFrac Float   
RealFrac CDouble     
RealFrac CFloat  
Integral a => RealFrac (Ratio a)     
HasResolution a => RealFrac (Fixed a)   

Haskell 的工作方式是,如果你给它一个整数文字(没有小数点的连续数字),它将假定它属于 Integral 类型类(并会尝试弄清楚是否将其设为IntegerInt 隐式),但它永远不会将整数文字隐式提升为 Fractional 类之一(包括 RealFrac)。由于没有“RealFrac Integer”这一行,这意味着你不能指望 Haskell 编译你的代码。

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

f :: Integer -> Integer

解决方案是使用以下函数来提升您的整数(将Integrals 转换为任何兼容的Number 类型):

fromIntegral :: (Integral a, Num b) => a -> b

Floor 执行相反方向的转换(从FractionalIntegral),如其类型所示。

总结你只需要说

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

注意 fromIntegral 调用以使参数的类型与编译器期望的类型兼容,以及使用 2.0Fractional 文字)作为基数。

【讨论】:

  • 欢迎来到 Stack Overflow 上的 Haskell 标签,详细的解释做得很好。
【解决方案2】:

注意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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-08-12
    • 1970-01-01
    • 2017-03-03
    • 1970-01-01
    • 2015-06-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多