将String转换为Int检查溢出

时间:2015-04-12 07:45:14

标签: haskell type-conversion

当我尝试将一个很长的整数转换为Int时,我很惊讶没有抛出任何错误:

Prelude> read "123456789012345678901234567890" :: Int
-4362896299872285998
来自readMaybe模块的

Text.Read给出相同的结果。

两个问题:

  • 我应该调用哪个函数来执行安全转换?
  • 地球上最安全的语言怎么能允许这种不安全的东西?

更新1:

这是我尝试编写检查边界的read版本:

{-# LANGUAGE ScopedTypeVariables #-}

parseIntegral :: forall a . (Integral a, Bounded a) => String -> Maybe a
parseIntegral s = integerToIntegral (read s :: Integer) where
  integerToIntegral n | n < fromIntegral (minBound :: a) = Nothing
  integerToIntegral n | n > fromIntegral (maxBound :: a) = Nothing
  integerToIntegral n = Just $ fromInteger n

这是我能做的最好的吗?

2 个答案:

答案 0 :(得分:4)

背景:为什么未经检查的溢出实际上很精彩

Haskell 98使溢出行为显式未指定,这对实现者有利,对其他人也有害。 Haskell 2010在两个部分中对它进行了讨论 - 在继承自Haskell 98的部分中,它是明确未指定的,而在Data.IntData.Word的部分中,它是指定的。这种不一致有望最终得到解决。

GHC非常友好地明确指出:

  

所有算术都以2 ^ n为模进行,其中n是类型中的位数。

这是一个非常有用的规范。特别是,它保证IntWordInt64Word32等在加法和乘法下形成环,甚至principal ideal rings。这意味着算术将始终正常工作 - 您可以通过许多不同的方式转换表达式和方程式而不会破坏事物。在溢出时抛出异常会破坏所有这些属性,使编写和推理程序变得更加困难。您真正需要注意的唯一情况是当您使用<compare等比较运算符时 - 固定宽度整数不会形成有序组,因此这些运算符有点敏感。

为什么不检查读取

是有意义的

读取整数涉及许多乘法和加法。它也需要快速。检查以确保读取“有效”并不容易快速完成。特别是,虽然很容易发现添加是否已经溢出,但很容易找出乘法是否有。我能想到的Int执行检查读取的唯一合理方法是

  1. 作为Integer阅读,检查,然后转换。 Integer算术比Int算术要贵得多。对于较小的内容,例如Int16,可以使用Int进行阅读,检查Int16溢出,然后缩小。这样便宜,但仍然没有免费。

  2. 在阅读时将十进制中的数字与maxBound(或者,对于负数,minBound)进行比较。这似乎更有可能合理有效,但仍会有一些成本。正如本回答的第一部分所解释的那样,溢出没有本身错误,所以抛出错误实际上比给出模2 ^ n的答案要好得多。

答案 1 :(得分:2)

如果不是“不安全”,则问题的行为不是未定义的。 (它完全是定义的,可能不是你想要的。)例如,unsafeWriteAray是不安全的,因为如果你犯了错误,它会将数据写入任意内存位置,或者导致您的应用程序出现段错误,或仅仅损坏自己的内存,导致它以任意未定义的方式运行。

分裂头发很多。如果你想处理如此庞大的数字,Integer是唯一的出路。但你可能已经知道了。

至于为什么没有溢出检查......有时你实际上希望一个数字溢出。 (例如,你可以转换为Word8而不明确地输出最后8位。)无论如何,每个可能的算术运算都可能溢出(例如,maxBound + 1 = minBound,这只是正常的加法。)你真的希望每一个算术运算都有溢出检查,在每一步都减慢你的程序吗?

您在C,C ++或C#中获得完全相同的行为。我猜不同的是,在C#中我们有checked关键字,它允许您自动检查溢出。也许有人有一个Haskell程序包用于检查算术......现在,自己实现这个检查可能更简单。