哈斯克尔轮数

时间:2017-10-10 20:01:09

标签: haskell decimal rounding

我有一个haskell功能,看起来像这样:

x 1 = 1 :: Double
x n = ((n-1)/3 + 1/(n-1)) * ((x (n-1))^2) - (n-1)^3/3 + 1 :: Double

不要考虑公式,它应该只添加一个,所以x 2 = 2; x 3 = 3,依此类推。 但是Haskell的结果是:

*Main> x 2
2.0
*Main> x 3
2.9999999999999996
*Main> x 4
3.9999999999999964
*Main> 

有人可以告诉我,我需要在第二行添加什么,以便将数字四舍五入到6位小数? 我不想解析为int!

谢谢!

5 个答案:

答案 0 :(得分:4)

您可以在小数点后以固定精度使用Fixed类型进行算术运算。甚至有a type synonym for six decimal digits of precision。例如,让我们定义:

x :: (Eq a, Fractional a) => a -> a
x 1 = 1
x n = ((n-1)/3 + 1/(n-1)) * ((x (n-1))^2) - (n-1)^3/3 + 1

现在我们可以在ghci中以六位数的精度进行尝试:

Data.Ratio> x <$> [1,2,3,4] :: [Micro]
[1.000000,2.000000,2.999998,3.999981]

当然,使用较少的精度数字实际上并没有帮助!您无法使用 less 精度来修复舍入错误,只需更多。那么,为什么不全力以赴呢?由于您只使用需要Fractional的操作,因此可以使用Rational进行精确算术运算。在ghci:

> x <$> [1,2,3,4] :: [Rational]
[1 % 1,2 % 1,3 % 1,4 % 1]

完美! (阅读%作为分部。)

答案 1 :(得分:3)

首先,该功能很容易出错。你正在递归一个浮点数;如果你错过1它会继续减少,直到-∞(或实际上,降到-9×10 15 ,因为从这一点开始,减少一个不会实际上改变浮动值,它将永远循环)。所以,它应该是

x :: Double -> Double
x n
 | n>1        = ((n-1)/3 + 1/(n-1)) * ((x (n-1))^2) - (n-1)^3/3 + 1
 | otherwise  = 1

接下来,如果您对与预期的确切结果的偏差感到困扰...... welcome to the world of floating point! FP有很多很好的用途,但是如果在十五分之一处存在差异,那么它们都不重要。如果这对你很重要,你应该使用任何花车,但要签名

x :: Rational -> Rational

立即结果将是准确的,但它们会出现在看起来有点奇怪的符号中:

*Main> x <$> [1..3]
[1 % 1,2 % 1,3 % 1]

现在,所有这一切,都有办法抑制浮点的症状。您所要求的,“在内部舍入一个数字”,可以这样实现:

quantize₁₀ :: Int -> Double -> Double
quantize₁₀ n x = fromIntegral (round $ x * η) / η
 where η = 10^n

然后(再次x :: Double -> Double

*Main> quantize₁₀ 6 . x <$> [1..3]
[1.0,2.0,3.0]

我强烈建议不要这样做 - 再次,如果你发现这种操作是必要的,你可能根本就不应该使用浮点数。

更合理的是将字符串输出四舍五入,而不对数字本身做任何事情。传统的方法是C printf,它也存在于Haskell中:

*Main Text.Printf> printf "%.6f\n" $ x 3
3.000000

答案 2 :(得分:2)

你的问题不是Haskell,它是浮点数的二进制表示。任何语言都会给你类似的结果。

说你想要&#34; round&#34;到一定数量的十进制数字是棘手的,因为许多十进制值的二进制表示并不精确。例如,0.1十进制是0.000110011001100110011...重复发生的。有关浮点数的详细信息以及它们可能导致的令人头疼的问题,您应该阅读&#34; What every computer scientist should know about floating point&#34;。

要了解二进制分数导致问题的原因,请考虑1/3 + 1/3 + 1/3。显然,这等于1.但如果你舍入到3位小数,你得到0.333 + 0.333 + 0.333 = 0.999。看起来很熟悉?

现在尝试评估[0,0.1..1]

您可以乘以1e6,四舍五入到最接近的整数,然后除以1e6,得到您想要的大概。你说你不想解析&#34;对于Int,但它是唯一的方法。我之所以说你只得到了你想要的东西,显然如果你的数字转到0.1那么你仍然只得到一个近似值而不是精确值。

在Haskell中舍入到6个小数位的函数将是

round6dp :: Double -> Double
round6dp = fromIntegral (round $ x * 1e6) / 1e6

> round6dp pi
3.141593

此外,您可以尝试导入Data.Ratio并替换类型&#34; Double&#34;与&#34; Rational&#34;在你的代码中。有理数表示为两个整数之间的比率,因此您可以精确地表示1/3。 Haskell使用%作为比率运算符来区分它与正常除法,因此您可以评估1%3 / 3%2。如果你这样做,那么你的代码会给出正确的答案。

答案 3 :(得分:2)

正如leftroundabout所说,你应该使用Rational来准确计算。事后舍入将导致麻烦。

但是,您可以使用numbers包中的Data.Number.BigFloat,它提供具有可配置精度的十进制浮点数。只要您保持足够精确的类型(例如RationalBigFloat具有相同或更高的精度),这就会回答您的问题“如何舍入到6个有效小数”。转换回Double(例如)会再次给你带来问题。

在某些情况下,使用Rational进行精确计算可能很有用,然后转换为Decimal6仅用于舍入(然后再转回Rational)。

示例代码:

{-# LANGUAGE NoMonomorphismRestriction #-}
import Data.Number.BigFloat

type Prec6 = EpsDiv10 (EpsDiv10 (EpsDiv10 (EpsDiv10 (EpsDiv10 Eps1))))
type Decimal6 = BigFloat Prec6

decimal6 :: Decimal6 -> Decimal6
decimal6 = id

double :: Double -> Double
double = id

fixed = print . decimal6 . realToFrac

broken = print . double . realToFrac . decimal6 . realToFrac

examples :: [Double]
examples = [pi, exp pi, exp (exp pi), exp (negate (exp pi))]

x 1 = 1
x n = ((n-1)/3 + 1/(n-1)) * ((x (n-1))^2) - (n-1)^3/3 + 1

testx = print . x

main :: IO ()
main = do
  putStrLn "fixed:"  >> mapM_ fixed examples
  putStrLn "broken:" >> mapM_ broken examples
  putStrLn "Rational:" >> mapM_ testx [1 .. 3 :: Rational]
  -- no Enum instance for BigFloat, so can't use [..] syntax at that type
  putStrLn "Decimal6:" >> mapM_ (testx . decimal6 . fromInteger) [1 .. 3]

输出:

fixed:
3.14159e0
2.31407e1
1.12169e10
8.91509e-11
broken:
3.1415929203539825
23.14070351758794
1.1216931216931217e10
8.915094339622642e-11
Rational:
1 % 1
2 % 1
3 % 1
Decimal6:
1.00000e0
2.00000e0
3.00000e0

答案 4 :(得分:0)

如前所述,您的问题是用于表示计算机中数字的有限精度。对于更高精度的浮点数,您可能需要查看包Data.Scientific