zwe*_*men 6 haskell common-lisp arbitrary-precision
这里的第一个问题,以及普通LISP和Haskell的新手,请善待.我在Common LISP中有一个函数 - 下面的代码 - 用于判断三角形的面积是否为整数?(整数?).
(defun area-int-p (a b c)
(let* ((s (/ (+ a b c) 2))
(area (sqrt (* s (- s a) (- s b) (- s c)))))
(if (equal (ceiling area) (floor area))
t
nil)))
Run Code Online (Sandbox Code Playgroud)
这应该使用Heron的公式来计算三角形的面积,给定三边的大小,并确定它是否是比较天花板和地板的整数.我们被告知等边三角形的面积永远不是整数.因此,为了测试函数是否正常工作,我使用参数运行它333
.这是我得到的回报:
CL-USER> (area-int-p 333 333 333)
NIL
Run Code Online (Sandbox Code Playgroud)
完善!有用.为了进一步测试它,我用参数运行它3333
.这是我得到的回报:
CL-USER> (area-int-p 3333 3333 3333)
T
Run Code Online (Sandbox Code Playgroud)
有些事情是错的,这不应该发生!所以,我尝试以下,希望等效的Haskell函数看看会发生什么:
areaIntP :: (Integral a) => a -> a -> a -> Bool
areaIntP a b c =
let aa = fromIntegral a
bb = fromIntegral b
cc = fromIntegral c
perimeter = aa + bb + cc
s = perimeter/2
area = sqrt(s * (s - aa) * (s - bb) * (s - cc))
in if ceiling area == floor area
then True
else False
Run Code Online (Sandbox Code Playgroud)
这就是我得到的:
*Main> areaIntP 3333 3333 3333
False
*Main> areaIntP 333 333 333
False
Run Code Online (Sandbox Code Playgroud)
看起来很完美 受此鼓励,我在Haskell中使用以下函数来求和等腰三角形的周长,第三边与另一边仅有一个单位,一个积分区域,周长低于1,000,000,000.
toplamArtilar :: Integral a => a -> a -> a -> a
toplamArtilar altSinir ustSinir toplam =
if ustSinir == altSinir
then toplam
else if areaIntP ustSinir ustSinir (ustSinir + 1) == True
then toplamArtilar altSinir (ustSinir - 1) (toplam + (3 * ustSinir + 1))
else toplamArtilar altSinir (ustSinir - 1) toplam
toplamEksiler :: Integral a => a -> a -> a -> a
toplamEksiler altSinir ustSinir toplam =
if ustSinir == altSinir
then toplam
else if areaIntP ustSinir ustSinir (ustSinir - 1) == True
then toplamEksiler altSinir (ustSinir - 1) (toplam + (3 * ustSinir - 1))
else toplamEksiler altSinir (ustSinir - 1) toplam
sonuc altSinir ustSinir =
toplamEksiler altSinir ustSinir (toplamArtilar altSinir ustSinir 0)
Run Code Online (Sandbox Code Playgroud)
(顺便说一下ustSinir
上限,altSinir
下限.)运行sonuc
参数2
然后333333333
,我的堆栈流过.运行Common LISP中的等效函数堆栈是可以的,但area-int-p
函数不可靠,可能是因为解释器推导出的数字类型的边界.毕竟,我的问题是双重的:
1)如何解决Common LISP功能中的问题area-int-p
?
2)如何使用上面的Haskell函数防止堆栈溢出,无论是在Emacs中还是从终端运行GHCi?
请注意那些想知道我在这里想要实现的目标的人:请不要告诉我使用Java BigDecimal
和BigInteger
.
在非常好的回复之后编辑:我在一个问题中提出了两个问题,并且从非常有帮助的人那里收到了完全令人满意的,新手友好的答案和关于风格的说明.谢谢.
让我们定义一个中间Common Lisp函数:
(defun area (a b c)
(let ((s (/ (+ a b c) 2)))
(sqrt (* s (- s a) (- s b) (- s c)))))
Run Code Online (Sandbox Code Playgroud)
你的测试给出:
CL-USER> (area 333 333 333)
48016.344
CL-USER> (area 3333 3333 3333)
4810290.0
Run Code Online (Sandbox Code Playgroud)
在第二种情况下,应该清楚天花板和地板都是相同的.在Haskell中不是这种情况,其中第二次测试(3333)返回:
4810290.040910754
Run Code Online (Sandbox Code Playgroud)
在Common Lisp中,我们取平方根的值是:
370222244442963/16
Run Code Online (Sandbox Code Playgroud)
这是因为计算是用有理数进行的.到目前为止,精度是最大的.但是,SQRT
如果可能,可以自由地返回合理的结果.作为特例,Rainer Joswig在评论中指出,结果可能是某些实现的整数.这是有道理的,因为整数和比率都是理性类型的不相交的子类型.但正如你的问题所示,一些平方根是不合理的(例如√2),在这种情况下,CL可以返回一个近似值的浮点数(或复数浮点数).
关于浮点数和数学函数的相关部分是12.1.3.3浮点数可替代性规则.长话短说,结果转换为single-float
计算平方根时的结果,这恰好会失去一些精度.为了获得双倍,你必须更明确:
(defun area (a b c)
(let ((s (/ (+ a b c) 2)))
(sqrt (float (* s (- s a) (- s b) (- s c)) 0d0))))
Run Code Online (Sandbox Code Playgroud)
我也可以使用(coerce ... 'double-float)
,但在这里我选择调用FLOAT
转换函数.可选的第二个参数是float原型,即.目标类型的值.上面是0d0
双浮.你也可以使用0l0
长双打或0s0
简称双打.如果要与输入float具有相同的精度,则此参数很有用,但也可以与文字一起使用,如示例中所示.short,single,double或long float类型的确切含义是实现定义的,但它们应遵守一些规则.当前的实现通常提供所需最小值的更高精度.
CL-USER> (area 3333 3333 3333)
4810290.040910754d0
Run Code Online (Sandbox Code Playgroud)
现在,如果我想测试结果是否为整数,我会截断浮点数并查看第二个返回值(余数)是否为零.
CL-USER> (zerop (nth-value 1 (truncate 4810290.040910754d0)))
NIL
Run Code Online (Sandbox Code Playgroud)
请注意,无论实现语言(Haskell,CL还是其他语言)如何,在给定浮点数的情况下,该方法将为某些输入提供不正确的结果.实际上,对于具有更精确浮点数的一些输入,可能会出现与CL相同的问题,其结果将非常接近整数.您可能需要另一种数学方法或MPFR之类的任意精度浮点计算.SBCL附带sb-mpfr
:
CL-USER> (require :sb-mpfr)
("SB-MPFR" "SB-GMP")
CL-USER> (in-package :sb-mpfr)
#<PACKAGE "SB-MPFR">
Run Code Online (Sandbox Code Playgroud)
然后:
SB-MPFR> (with-precision 256
(sqrt (coerce 370222244442963/16 'mpfr-float)))
.4810290040910754427104204965311207243133723228299086361205561385039201180068712e+7
-1
Run Code Online (Sandbox Code Playgroud)
我会回答你的第二个问题,我不确定第一个问题.在Haskell中,因为它是一种惰性语言,当你使用带有累加器参数的尾递归时,可能会发生"累积的thunk".thunk是一个暂停但尚未评估的表达式.举一个更简单的例子,将从0到0的所有数字相加n
:
tri :: Int -> Int -> Int
tri 0 accum = accum
tri n accum = tri (n-1) (accum + n)
Run Code Online (Sandbox Code Playgroud)
如果我们跟踪评估,我们可以看到发生了什么:
tri 3 0
= tri (3-1) (0+3)
= tri 2 (0+3)
= tri (2-1) ((0+3)+2)
= tri 1 ((0+3)+2)
= tri (1-1) (((0+3)+2)+1)
= tri 0 (((0+3)+2)+1)
= ((0+3)+2)+1 -- here is where ghc uses the C stack
= (0+3)+2 (+1) on stack
= 0+3 (+2) (+1) on stack
= 0 (+3) (+2) (+1) on stack
= 3 (+2) (+1) on stack
= 5 (+1) on stack
= 6
Run Code Online (Sandbox Code Playgroud)
这当然是一种简化,但它是一种直觉,可以帮助您理解由堆积累积引起的堆栈溢出和空间泄漏.GHC只在需要时评估一个thunk.我们询问n
每次的值是否为0,tri
因此在该参数中没有thunk积累,但是没有人需要知道accum
直到最后的值,这可能是一个非常大的thunk,正如您从示例中看到的那样.在评估那个庞大的thunk时,堆栈可能会溢出.
解决方案是尽快进行tri
评估accum
.这通常使用a BangPattern
来完成(但seq
如果您不喜欢扩展,则可以完成).
{-# LANGUAGE BangPatterns #-}
tri :: Int -> Int -> Int
tri 0 !accum = accum
tri n !accum = tri (n-1) (accum + n)
Run Code Online (Sandbox Code Playgroud)
该!
前accum
指(即使模式不技术上需要知道它的价值)"的模式匹配的时刻评估此参数".然后我们得到这个评估痕迹:
tri 3 0
= tri (3-1) (0+3)
= tri 2 3 -- we evaluate 0+3 because of the bang pattern
= tri (2-1) (3+2)
= tri 1 5
= tri (1-1) (5+1)
= tri 0 6
= 6
Run Code Online (Sandbox Code Playgroud)
我希望这有帮助.
归档时间: |
|
查看次数: |
238 次 |
最近记录: |