Haskell 枚举列表时的奇数值

Haskell 枚举列表时的奇数值,haskell,Haskell,作为更大的函数定义的一部分,我需要允许函数的域(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] 为什么GHC返

作为更大的函数定义的一部分,我需要允许函数的域(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]

为什么GHC返回值,例如0.300000000000000004而不是0.3?

因为IEEE浮点算法通常不能精确表示十进制数。二进制表示法中始终存在舍入错误,有时在显示数字时会渗入表面

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

编辑:情况可能并非如此。当使用IEEE浮点数时,数字
0.3
将编码为整数
3fd3333
,而
0.1+0.1+0.1
将生成编码为
3fd3334
的数字,我不知道微软的运行库是否有足够的容错性,可以在显示此数据时返回到
0.3


无论如何,不同处理的一个好例子是在Python交互式shell中键入
0.3
。如果是Python2.6,您将返回
0.29999999999999
,如果是2.7,它将显示
0.3

更好的方法是按照

map (/10) [0 .. 10]
int numerator = 1, denominator = 10;
printf("%.1f\n", (double) numerator / (double) denominator);
这需要整数,从而避免了浮点问题,并将每个数除以10。

如果i、n和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] 上面的代码是以命令式风格编写的,带有完整的类型注释。将其类型理解为将有理数列表转换为某些I/O操作。
Control.Monad
中的组合符为列表中的每个值(我们要近似的理性值)计算一个动作(
putRationalToOnePlaceLn
)。你可以把它看作是一种
for
循环,甚至还有一个与
mapM
相同的组合符,只是参数的顺序相反。结尾的下划线是一个Haskell约定,表明它丢弃运行操作的结果,并注意有
mapM
forM
收集这些结果

为了通过安排近似输出,我们必须生成一个字符串。如果您是用C语言编写的,那么代码应该是

map (/10) [0 .. 10]
int numerator = 1, denominator = 10;
printf("%.1f\n", (double) numerator / (double) denominator);
上面的Haskell代码在结构上类似。Haskell的
/
运算符的类型为

(/) :: (Fractional a) => a -> a -> a
这意味着,对于typeclass
Fractional
的某些实例
a
,当给定相同类型的两个值
a
时,您将返回该类型的另一个值

我们可以要求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 或

printAbrox
的定义似乎与所有有用的路标(如函数和参数的名称或类型注释)相一致。随着您对Haskell越来越有经验,也越来越熟悉,这样的命令式定义将开始显得杂乱无章

Haskell是一种函数式语言:它的优势在于通过将简单函数组装成更复杂的函数来指定什么,而不是如何。有人曾经建议Haskell处理函数的能力和Perl处理字符串的能力一样强大

在中,参数消失,留下计算结构。学习阅读和这种风格确实需要练习,但您会发现它有助于编写更清晰的代码

通过对导入进行调整,我们可以定义一个无点等效项,例如

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\uu
putStrLn
printf
分子
,分母

还有一些奇怪的东西。Haskell的
$
操作符是编写函数应用程序的另一种方法。它的定义是

f $ x = f x
(f . g) x = f (g 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 或

因此,您可以将
$
视为编写括号的另一种方法

还有
这意味着函数组合。它的定义是

f $ x = f x
(f . g) x = f (g x)
我们也可以这样写

(f . g) x = f $ g x
如您所见,我们应用右手函数,然后将结果输入左手函数。您可能还记得数学教科书中的定义,例如

选择名称
,是因为其外观与凸起的圆点相似

因此,对于一系列函数组合,通常最容易通过背对背阅读来理解它

(分子和分母)
位使用from
控件。箭头
。例如:

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

但是从功能上考虑!如果我们能够以某种方式将
/
转换成一个接受元组并使用其组件作为除法参数的函数,会怎么样?这正是我们要做的


你和Haskell迈出了伟大的第一步。祝你旅途愉快

有趣。显然我有很多维基百科要读!谢谢。@danportin十年后,我发现你的评论最有价值。@danportin不客气!我很高兴这些信息是有用的。事实上,它没有。仅仅因为你没有在数字中加小数并不意味着它不是一。因此,虽然数字可能是“完整的”,但您并没有避免浮点问题。事实上,由于您编写的方式,您可能会使问题变得更糟(因为您试图说服自己这些数字没有使用浮点)。一个更好的方法是使用
Rational
s(或某种
Ratio
)类型。基本问题源于浮动二进制表示。0.1十进制
show $ 1.0 / 2.0
(f . g) x = f (g x)
(f . g) x = f $ g x
ghci> (numerator &&& denominator) $ 1%3 (1,3)
(fst tuple) / (snd tuple)
fst (a,_) = a
snd (_,b) = b