当我尝试将一个很长的整数转换为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
这是我能做的最好的吗?
答案 0 :(得分:4)
Haskell 98使溢出行为显式未指定,这对实现者有利,对其他人也有害。 Haskell 2010在两个部分中对它进行了讨论 - 在继承自Haskell 98的部分中,它是明确未指定的,而在Data.Int
和Data.Word
的部分中,它是指定的。这种不一致有望最终得到解决。
GHC非常友好地明确指出:
所有算术都以2 ^ n为模进行,其中n是类型中的位数。
这是一个非常有用的规范。特别是,它保证Int
,Word
,Int64
,Word32
等在加法和乘法下形成环,甚至principal ideal rings。这意味着算术将始终正常工作 - 您可以通过许多不同的方式转换表达式和方程式而不会破坏事物。在溢出时抛出异常会破坏所有这些属性,使编写和推理程序变得更加困难。您真正需要注意的唯一情况是当您使用<
和compare
等比较运算符时 - 固定宽度整数不会形成有序组,因此这些运算符有点敏感。
读取整数涉及许多乘法和加法。它也需要快速。检查以确保读取“有效”并不容易快速完成。特别是,虽然很容易发现添加是否已经溢出,但不很容易找出乘法是否有。我能想到的Int
执行检查读取的唯一合理方法是
作为Integer
阅读,检查,然后转换。 Integer
算术比Int
算术要贵得多。对于较小的内容,例如Int16
,可以使用Int
进行阅读,检查Int16
溢出,然后缩小。这样便宜,但仍然没有免费。
在阅读时将十进制中的数字与maxBound
(或者,对于负数,minBound
)进行比较。这似乎更有可能合理有效,但仍会有一些成本。正如本回答的第一部分所解释的那样,溢出没有本身错误,所以抛出错误实际上比给出模2 ^ n的答案要好得多。
答案 1 :(得分:2)
如果不是“不安全”,则问题的行为不是未定义的。 (它完全是定义的,可能不是你想要的。)例如,unsafeWriteAray
是不安全的,因为如果你犯了错误,它会将数据写入任意内存位置,或者导致您的应用程序出现段错误,或仅仅损坏自己的内存,导致它以任意未定义的方式运行。
分裂头发很多。如果你想处理如此庞大的数字,Integer
是唯一的出路。但你可能已经知道了。
至于为什么没有溢出检查......有时你实际上希望一个数字溢出。 (例如,你可以转换为Word8
而不明确地输出最后8位。)无论如何,每个可能的算术运算都可能溢出(例如,maxBound + 1 = minBound
,这只是正常的加法。)你真的希望每一个算术运算都有溢出检查,在每一步都减慢你的程序吗?
您在C,C ++或C#中获得完全相同的行为。我猜不同的是,在C#中我们有checked
关键字,它允许您自动检查溢出。也许有人有一个Haskell程序包用于检查算术......现在,自己实现这个检查可能更简单。