我有一个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!
谢谢!
答案 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,它提供具有可配置精度的十进制浮点数。只要您保持足够精确的类型(例如Rational
或BigFloat
具有相同或更高的精度),这就会回答您的问题“如何舍入到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