如何处理F#中的算术运算OverflowException?

时间:2015-06-15 13:44:08

标签: python f#

我在F#中执行Project Euler问题1:

  

3和5的倍数

     

如果我们列出10以下的所有自然数是3或5的倍数,我们得到3,5,6和9.这些倍数的总和是23.

     

查找低于1000的3或5的所有倍数的总和。

这是我的尝试:

[1..999]
    |> List.filter (fun x -> x%3 * x%5 = 0)
    |> List.sum

val it : int = 233168

我的朋友在Excel中通过添加3的倍数和5的倍数提取15的倍数来计算它,并且他用更大的上限来挑战我:找到1234567以下所有3或5的倍数的总和。

我试过了:

[1..1234567]
     |> List.filter (fun x -> x%3 * x%5 = 0)
     |> List.sum
  

System.OverflowException:算术运算导致溢出。

第二次尝试:

let mutable result = 0
for x < 1000 do
    if  x%3 * x%5 = 0 then result = result + x
  

错误FS0010:模式中出现意外的整数文字。预期的中缀运算符,引号或其他标记。

令我惊讶的是,Python可以很好地处理这个问题并且非常有效:

sum(x for x in range(1234567) if x%3 * x%5 == 0)
# 355636612814L

%time sum(x for x in range(1234567) if x%3 * x%5 == 0)
Wall time: 401 ms
Out: 355636612814L

问题:

  1. 为什么F#程序导致&#34;算术运算导致溢出&#34;?
  2. 是否可以在F#中编写上述python等效解决方案?
  3. 解决这个问题的惯用F#方式是什么?

3 个答案:

答案 0 :(得分:6)

您应该将bigint用于大数字,例如

[1I..1234567I] 
    |> List.filter (fun x -> x % 3I * x % 5I = 0I) 
    |> List.sum

或(不太可读)

[bigint  1..bigint 1234567] 
    |> List.filter (fun x -> x % bigint 3 * x % bigint 5 = bigint 0) 
    |> List.sum

对于数字less then 9,223,372,036,854,775,807,您可以使用int64类型(带 L 后缀),使用int64可以提高整体性能

[1L..1234567L] 
    |> List.filter (fun x -> x % 3L * x % 5L = 0L) 
    |> List.sum

答案 1 :(得分:1)

Python代码的直接翻译在我的机器上运行了一半的时间,158毫秒(对于Python为317毫秒)。

seq { 
    for x in 0L..1234566L do 
        if x % 3L * x % 5L = 0L then
            yield x
}
|> Seq.sum

这个可以说是更惯用的代码仍然比你的Python运行得更快(220毫秒)。

seq { 0L .. 1234566L }
|> Seq.filter (fun x -> x % 3L * x % 5L = 0L)
|> Seq.sum

即使LINQ版本更快(221毫秒)。

query {
    for x in 0L .. 1234566L do
    where (x % 3L * x % 5L = 0L)
    sumBy x
}

答案 2 :(得分:0)

让我试着回答你的问题:

  1. 为什么F#程序导致&#34;算术运算导致溢出&#34;?
  2. 由于32位有符号整数的可表示范围的限制(2 ^ 31-1 = 2,147,483,647),发生溢出错误。专属上限为1,234,567的答案如下:

    Sum of all natural numbers up to 1234567 divisible by 3 or by 5 is 355,636,612,814
    Result was computed in 12 ms
    
    1. 是否可以在F#中编写上述python等效解决方案?
    2. 这是一个类似于Python实现的完整程序。它以可以说是势在必行的风格写成,但它可以快速完成工作。

      open System.Diagnostics
      
      [<EntryPoint>]
      let main argv = 
         let limit_exclusive = 1234567
         let limit = limit_exclusive - 1
         let divisor1 = 3
         let divisor2 = 5
      
         let stopwatch = Stopwatch.StartNew()
         let mutable sum = 0UL
      
         for x = 1 to limit do 
            if (x % divisor1 = 0) || (x % divisor2 = 0) then 
               sum <- sum + uint64 x
      
         stopwatch.Stop()
      
         printfn "Sum of all natural numbers up to %d divisible by %d or by %d is %d" limit_exclusive divisor1 divisor2 sum
         printfn "Result was computed in %d ms" stopwatch.ElapsedMilliseconds
      
         0 // return an integer exit code
      
      Sum of all natural numbers up to 1234567 divisible by 3 or by 5 is 355,636,612,814
      Result was computed in 12 ms
      
      1. 解决这个问题的惯用F#方式是什么?
      2. 由于F#是一种多范式语言,它允许人们直观地或靠近机器或其他任何东西来表达算法。我想借此机会概述另一种可能对某些方法更直观的方法,但会产生较低的性能指标。

           let seq1 = seq { 0 .. divisor1 .. limit }
           let seq2 = seq { 0 .. divisor2 .. limit }
        
           let both = Seq.append seq1 seq2
           let sum = both
                     |> Seq.distinct
                     |> Seq.map uint64
                     |> Seq.sum
        
        Sum of all natural numbers up to 1234567 divisible by 3 or by 5 is 355,636,612,814
        Result was computed in 535 ms