代码之家  ›  专栏  ›  技术社区  ›  emi

枚举列表时出现奇数值

  •  3
  • emi  · 技术社区  · 16 年前

    作为更大的函数定义的一部分,我需要允许函数的域(I,n)以不同的速率从I增加到n。所以我写了:

    f (i, n) k = [i, (i+k)..n]
    

    进入GHC。这返回了奇怪的结果:

    *Main> f (0.0, 1.0) 0.1
    [0.0,0.1,0.2,0.30000000000000004,0.4000000000000001,0.5000000000000001,0.6000000000000001,0.7000000000000001,0.8,0.9,1.0]
    

    3 回复  |  直到 16 年前
        1
  •  4
  •   Greg Bacon    16 年前

    , ,和 k 如果你是理性的,你可以走无限精确的路线:

    f :: (Rational, Rational) -> Rational -> [Rational]
    f (i, n) k = [i, (i+k) .. n]
    

    符号可能需要一些时间来适应:

    ghci> f (0%1, 1%1) (1%10)
    [0 % 1,1 % 10,1 % 5,3 % 10,2 % 5,1 % 2,3 % 5,7 % 10,4 % 5,9 % 10,1 % 1]

    想想 % 作为一个有趣的分数吧。

    import Control.Monad (mapM_)
    import Data.Ratio (Rational, (%), denominator, numerator)
    import Text.Printf (printf)
    
    printApprox :: [Rational] -> IO ()
    printApprox rs = do
      mapM_ putRationalToOnePlaceLn rs
      where putRationalToOnePlaceLn :: Rational -> IO ()
            putRationalToOnePlaceLn r = do
              let toOnePlace :: String
                  toOnePlace = printf "%.1f" (numFrac / denomFrac)
                  numFrac, denomFrac :: Double
                  numFrac    = fromIntegral $ numerator   r
                  denomFrac  = fromIntegral $ denominator r
              putStrLn toOnePlace
    

    上面的代码是以命令式风格编写的,带有完整的类型注释。将其类型理解为将有理数列表转换为某些I/O操作。这个 mapM_ 组合子来自 Control.Monad 评估操作( putRationalToOnePlaceLn for 循环,甚至有一个 forM_ mapM公司_ 只是论点的顺序颠倒了。最后的下划线是一个Haskell约定,表示它丢弃了运行操作的结果,请注意 mapM 和 forM 收集这些结果。

    通过 putStrLn

    int numerator = 1, denominator = 10;
    printf("%.1f\n", (double) numerator / (double) denominator);
    

    上面的Haskell代码在结构上是相似的。Haskell的类型 / 操作员是

    (/) :: (Fractional a) => a -> a -> a
    

    比如说 a typeclass的 Fractional ,当给定两个相同类型的值时 一 ,您将获得另一个该类型的值。

    我们可以问问 ghci 分数的 :

    ghci> :info Fractional
    class (Num a) => Fractional a where
      (/) :: a -> a -> a
      recip :: a -> a
      fromRational :: Rational -> a
        -- Defined in GHC.Real
    instance Fractional Float -- Defined in GHC.Float
    instance Fractional Double -- Defined in GHC.Float

    注意 instance 底部的线条。这意味着我们可以

    ghci> (22::Float) / (7::Float)
    3.142857

    或

    ghci> (22::Double) / (7::Double)
    3.142857142857143

    但不是

    ghci> (22::Double) / (7::Float)
    
    <interactive>:1:16:
        Couldn't match expected type `Double' against inferred type `Float'
        In the second argument of `(/)', namely `(7 :: Float)'
        In the expression: (22 :: Double) / (7 :: Float)
        In the definition of `it': it = (22 :: Double) / (7 :: Float)

    当然不是

    ghci> (22::Integer) / (7::Integer)
    
    <interactive>:1:0:
        No instance for (Fractional Integer)
          arising from a use of `/' at :1:0-27
        Possible fix: add an instance declaration for (Fractional Integer)
        In the expression: (22 :: Integer) / (7 :: Integer)
        In the definition of `it': it = (22 :: Integer) / (7 :: Integer)

    Rational 类型定义为 Integers fromIntegral 有点像C中的一个类型转换。

    即使在阅读之后 A Gentle Introduction to Haskell: Numbers ,您仍然可能会发现Haskell对混合数字类型的挑剔令人沮丧。对于我们这些在头脑中或纸上执行无限精度运算的人来说,忘记计算机只有有限精度,必须处理近似值是太容易了。类型安全是一个有用的现实检查。

    样本输出:

    *Main> printApprox $ f (0%1, 1%1) (1%10)
    0.0
    0.1
    0.2
    0.3
    0.4
    0.5
    0.6
    0.7
    0.8
    0.9
    1.0

    printApprox 所有有用的路标,比如函数名、参数名或类型注释,似乎都让人感到安慰。随着你对Haskell越来越有经验,也越来越熟悉,这种看起来很有必要的定义将开始显得杂乱无章。

    功能性

    在 point-free style ,争论消失了 计算的结果。学习阅读和这种风格确实需要练习,但您会发现它有助于编写更清晰的代码。

    import Control.Arrow ((***), (&&&))
    import Control.Monad (join, mapM_)
    import Data.Ratio (Rational, (%), denominator, numerator)
    import Text.Printf (printf)
    
    printApproxPointFree :: [Rational] -> IO ()
    printApproxPointFree =
      mapM_       $
      putStrLn    .
      toOnePlace  .
      uncurry (/) .
      join (***) fromIntegral .
      (numerator &&& denominator)
      where toOnePlace = printf "%.1f" :: Double -> String
    

    我们看到一些熟悉的片段:我们的新朋友 mapM公司_ putStrLn公司 printf numerator ,和 denominator .

    $ 运算符是编写函数应用程序的另一种方法。它的定义是

    f $ x = f x
    

    在你尝试之前,它可能不会显得非常有用

    Prelude> show 1.0 / 2.0
    
    <interactive>:1:0:
        No instance for (Fractional String)
          arising from a use of `/' at :1:0-13
        Possible fix: add an instance declaration for (Fractional String)
        In the expression: show 1.0 / 2.0
        In the definition of `it': it = show 1.0 / 2.0

    你可以把这句话写成

    show (1.0 / 2.0)
    

    或

    show $ 1.0 / 2.0
    

    所以你能想到 作为另一种写括号的方法。

    然后是 . 这意味着函数合成。它的定义是

    (f . g) x = f (g x)
    

    我们也可以写为

    (f . g) x = f $ g x
    

    如您所见,我们应用右手函数,然后将结果输入左手函数。你可能还记得数学课本上的定义,比如

    rendered composition example

    .

    因此,对于一系列的函数组合,通过前后阅读通常是最容易理解的。

    (numerator &&& denominator) bit使用 fan-out combinator 从 Control.Arrow . 例如:

    ghci> (numerator &&& denominator) $ 1%3
    (1,3)

    所以它将两个函数应用于同一个值,并返回一个包含结果的元组。记住我们需要申请 分子和分母,这就是 join (***) fromIntegral 做。请注意 *** 也来自 控制箭头 模块。

    最后是 运算符接受单独的参数,而不是元组。有必要的思考,你可能会想写一些

    (fst tuple) / (snd tuple)
    

    哪里

    fst (a,_) = a
    snd (_,b) = b
    

    但是从功能上考虑!如果我们能改变 一个接受元组并使用其组件作为除法参数的函数?那正是你想要的 uncurry (/)

    你和哈斯克尔迈出了伟大的第一步。旅途愉快!

        2
  •  9
  •   Marcelo Cantos    16 年前

    由于IEEE浮点算法一般不能精确地表示十进制数。二进制表示法中总是存在舍入错误,有时在显示数字时会渗入到表面。

    根据GHC将浮点数转换为十进制表示的方式,您可能会发现在Windows上它会按预期的方式显示结果 0.3 . 这是因为微软的运行时库比Linux和Mac更聪明,更能显示浮动。

    编辑:情况可能并非如此。号码 0.3 将编码为整数 3fd3333333333333 当使用IEEE浮点数时 0.1 + 0.1 + 0.1 将产生一个编码为 3fd3333333333334 显示此项时。

    0.3 变成一个Python交互式shell。如果是Python2.6,你会回来的 0.29999999999999999 0.3 .

        3
  •  2
  •   Paul Johnson    16 年前

    map (/10) [0 .. 10]
    

    推荐文章