File: CrackNum.hs

package info (click to toggle)
haskell-sbv 10.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 8,148 kB
  • sloc: haskell: 31,176; makefile: 4
file content (292 lines) | stat: -rw-r--r-- 11,722 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
-----------------------------------------------------------------------------
-- |
-- Module    : Data.SBV.Utils.CrackNum
-- Copyright : (c) Levent Erkok
-- License   : BSD3
-- Maintainer: erkokl@gmail.com
-- Stability : experimental
--
-- Crack internal representation for numeric types
-----------------------------------------------------------------------------

{-# LANGUAGE NamedFieldPuns #-}

{-# OPTIONS_GHC -Wall -Werror -Wno-incomplete-uni-patterns #-}

module Data.SBV.Utils.CrackNum (
        crackNum
      ) where

import Data.SBV.Core.Concrete
import Data.SBV.Core.Kind
import Data.SBV.Core.SizedFloats
import Data.SBV.Utils.Numeric
import Data.SBV.Utils.PrettyNum (showFloatAtBase)

import Data.Char (intToDigit, toUpper, isSpace)

import Data.Bits
import Data.List

import LibBF hiding (Zero, bfToString)

import Numeric

-- | A class for cracking things deeper, if we know how.
class CrackNum a where
  -- | Convert an item to possibly bit-level description, if possible.
  crackNum :: a -> Maybe String

-- | CVs are easy to crack
instance CrackNum CV where
  crackNum cv = case kindOf cv of
                  -- Maybe one day we'll have a use for these, currently cracking them
                  -- any further seems overkill
                  KBool      {}  -> Nothing
                  KUnbounded {}  -> Nothing
                  KReal      {}  -> Nothing
                  KUserSort  {}  -> Nothing
                  KChar      {}  -> Nothing
                  KString    {}  -> Nothing
                  KList      {}  -> Nothing
                  KSet       {}  -> Nothing
                  KTuple     {}  -> Nothing
                  KMaybe     {}  -> Nothing
                  KEither    {}  -> Nothing
                  KRational  {}  -> Nothing

                  -- Actual crackables
                  KFloat{}       -> Just $ let CFloat   f = cvVal cv in float f
                  KDouble{}      -> Just $ let CDouble  d = cvVal cv in float d
                  KFP{}          -> Just $ let CFP      f = cvVal cv in float f
                  KBounded sg sz -> Just $ let CInteger i = cvVal cv in int   sg sz i

-- How far off the screen we want displayed? Somewhat experimentally found.
tab :: String
tab = replicate 18 ' '

-- Make splits of 4, top one has the remainder
split4 :: Int -> [Int]
split4 n
  | m == 0 =     rest
  | True   = m : rest
  where (d, m) = n `divMod` 4
        rest   = replicate d 4

-- Convert bits to the corresponding integer.
getVal :: [Bool] -> Integer
getVal = foldl (\s b -> 2 * s + if b then 1 else 0) 0

-- Show in hex, but pay attention to how wide a field it should be in
mkHex :: [Bool] -> String
mkHex bin = map toUpper $ showHex (getVal bin) ""

-- | Show a sized word/int in detail
int :: Bool -> Int -> Integer -> String
int signed sz v = intercalate "\n" $ ruler ++ info
  where splits = split4 sz

        ruler = map (tab ++) $ mkRuler sz splits

        bitRep :: [[Bool]]
        bitRep = split splits [v `testBit` i | i <- reverse [0 .. sz - 1]]

        flatHex = concatMap mkHex bitRep
        iprec
          | signed = "Signed "   ++ show sz ++ "-bit 2's complement integer"
          | True   = "Unsigned " ++ show sz ++ "-bit word"

        signBit = v `testBit` (sz-1)
        s | signed && signBit = "-"
          | True              = ""

        av = abs v

        info = [ "   Binary layout: " ++ unwords [concatMap (\b -> if b then "1" else "0") is | is <- bitRep]
               , "      Hex layout: " ++ unwords (split (split4 (length flatHex)) flatHex)
               , "            Type: " ++ iprec
               ]
            ++ [ "            Sign: " ++ if signBit then "Negative" else "Positive" | signed]
            ++ [ "          Binary: " ++ s ++ "0b" ++ showIntAtBase 2 intToDigit av ""
               , "           Octal: " ++ s ++ "0o" ++ showOct av ""
               , "         Decimal: " ++ show v
               , "             Hex: " ++ s ++ "0x" ++ showHex av ""
               ]

-- | What kind of Float is this?
data FPKind = Zero       Bool  -- with sign
            | Infty      Bool  -- with sign
            | NaN
            | Subnormal
            | Normal
            deriving Eq

-- | Show instance for Kind, not for reading back!
instance Show FPKind where
  show Zero{}    = "FP_ZERO"
  show Infty{}   = "FP_INFINITE"
  show NaN       = "FP_NAN"
  show Subnormal = "FP_SUBNORMAL"
  show Normal    = "FP_NORMAL"

-- | Find out what kind this float is. We specifically ask
-- the caller to provide if the number is zero, neg-inf, and pos-inf. Why?
-- Because the FP type doesn't have those recognizers that also work with Float/Double.
getKind :: RealFloat a => a -> FPKind
getKind fp
 | fp == 0           = Zero  (isNegativeZero fp)
 | isInfinite fp     = Infty (fp < 0)
 | isNaN fp          = NaN
 | isDenormalized fp = Subnormal
 | True              = Normal

-- Show the value in different bases
showAtBases :: FPKind -> (String, String, String, String) -> Either String (String, String, String, String)
showAtBases k bvs = case k of
                     Zero False  -> Right ("0b0.0",  "0o0.0",  "0.0",  "0x0")
                     Zero True   -> Right ("-0b0.0", "-0o0.0", "-0.0", "-0o0")
                     Infty False -> Left  "Infinity"
                     Infty True  -> Left  "-Infinity"
                     NaN         -> Left  "NaN"
                     Subnormal   -> Right (dropSuffixes bvs)
                     Normal      -> Right (dropSuffixes bvs)
  where dropSuffixes (a, b, c, d) = (bfRemoveRedundantExp a, bfRemoveRedundantExp b, bfRemoveRedundantExp c, bfRemoveRedundantExp d)

-- | Float data for display purposes
data FloatData = FloatData { prec   :: String
                           , eb     :: Int
                           , sb     :: Int
                           , bits   :: Integer
                           , fpKind :: FPKind
                           , fpVals :: Either String (String, String, String, String)
                           }

-- | A simple means to organize different bits and pieces of float data
-- for display purposes
class HasFloatData a where
  getFloatData :: a -> FloatData

-- | Float instance
instance HasFloatData Float where
  getFloatData f = FloatData {
      prec   = "Single"
    , eb     =  8
    , sb     = 24
    , bits   = fromIntegral (floatToWord f)
    , fpKind = k
    , fpVals = showAtBases k (showFloatAtBase 2 f "", showFloatAtBase 8 f "", show f, showFloatAtBase 16 f "")
    }
    where k = getKind f

-- | Double instance
instance HasFloatData Double where
  getFloatData d  = FloatData {
      prec   = "Double"
    , eb     = 11
    , sb     = 53
    , bits   = fromIntegral (doubleToWord d)
    , fpKind = k
    , fpVals = showAtBases k (showFloatAtBase 2 d "", showFloatAtBase 8 d "", show d, showFloatAtBase 16 d "")
    }
    where k = getKind d

-- | Find the exponent values, (exponent value, exponent as stored, bias)
getExponentData :: FloatData -> (Integer, Integer, Integer)
getExponentData FloatData{eb, sb, bits, fpKind} = (expValue, expStored, bias)
  where -- | Bias is 2^(eb-1) - 1
        bias :: Integer
        bias = (2 :: Integer) ^ ((fromIntegral eb :: Integer) - 1) - 1

        -- | Exponent as stored is simply bit extraction
        expStored = getVal [bits `testBit` i | i <- reverse [sb-1 .. sb+eb-2]]

        -- | Exponent value is stored exponent - bias, unless the number is subnormal. In that case it is 1 - bias
        expValue = case fpKind of
                     Subnormal -> 1 - bias
                     _         -> expStored - bias

-- | FP instance
instance HasFloatData FP where
  getFloatData v@(FP eb sb f) = FloatData {
      prec   = case (eb, sb) of
                 ( 5,  11) -> "Half (5 exponent bits, 10 significand bits.)"
                 ( 8,  24) -> "Single (8 exponent bits, 23 significand bits.)"
                 (11,  53) -> "Double (11 exponent bits, 52 significand bits.)"
                 (15, 113) -> "Quad (15 exponent bits, 112 significand bits.)"
                 ( _,   _) -> show eb ++ " exponent bits, " ++ show (sb-1) ++ " significand bit" ++ if sb > 2 then "s" else ""
    , eb     = eb
    , sb     = sb
    , bits   = bfToBits (mkBFOpts eb sb NearEven) f
    , fpKind = k
    , fpVals = showAtBases k (bfToString 2 True True v, bfToString 8 True True v, bfToString 10 True False v, bfToString 16 True True v)
    }
    where opts = mkBFOpts eb sb NearEven
          k | bfIsZero f           = Zero  (bfIsNeg f)
            | bfIsInf f            = Infty (bfIsNeg f)
            | bfIsNaN f            = NaN
            | bfIsSubnormal opts f = Subnormal
            | True                 = Normal

-- | Show a float in detail
float :: HasFloatData a => a -> String
float f = intercalate "\n" $ ruler ++ legend : info
   where fd@FloatData{prec, eb, sb, bits, fpKind, fpVals} = getFloatData f

         splits = [1, eb, sb]
         ruler  = map (tab ++) $ mkRuler (eb + sb) splits

         legend = tab ++ "S " ++ mkTag ('E' : show eb) eb ++ " " ++ mkTag ('S' : show (sb-1)) (sb-1)

         mkTag t len = take len $ replicate ((len - length t) `div` 2) '-' ++ t ++ repeat '-'

         allBits :: [Bool]
         allBits = [bits `testBit` i | i <- reverse [0 .. eb + sb - 1]]

         flatHex = concatMap mkHex (split (split4 (eb + sb)) allBits)
         sign    = bits `testBit` (eb+sb-1)

         (exponentVal, storedExponent, bias) = getExponentData fd

         esInfo = "Stored: " ++ show storedExponent ++ ", Bias: " ++ show bias

         isSubNormal = case fpKind of
                         Subnormal -> True
                         _         -> False

         info =   [ "   Binary layout: " ++ unwords [concatMap (\b -> if b then "1" else "0") is | is <- split splits allBits]
                  , "      Hex layout: " ++ unwords (split (split4 (length flatHex)) flatHex)
                  , "       Precision: " ++ prec
                  , "            Sign: " ++ if sign then "Negative" else "Positive"
                  ]
               ++ [ "        Exponent: " ++ show exponentVal ++ " (Subnormal, with fixed exponent value. " ++ esInfo ++ ")" | isSubNormal    ]
               ++ [ "        Exponent: " ++ show exponentVal ++ " ("                                       ++ esInfo ++ ")" | not isSubNormal]
               ++ [ "  Classification: " ++ show fpKind]
               ++ (case fpVals of
                     Left val                       -> [ "           Value: " ++ val]
                     Right (bval, oval, dval, hval) -> [ "          Binary: " ++ bval
                                                       , "           Octal: " ++ oval
                                                       , "         Decimal: " ++ dval
                                                       , "             Hex: " ++ hval
                                                       ])
               ++ [ "            Note: Representation for NaN's is not unique" | fpKind == NaN]


-- | Build a ruler with given split points
mkRuler :: Int -> [Int] -> [String]
mkRuler n splits = map (trimRight . unwords . split splits . trim Nothing) $ transpose $ map pad $ reverse [0 .. n-1]
  where len = length (show (n-1))
        pad i = reverse $ take len $ reverse (show i) ++ repeat '0'

        trim _      "" = ""
        trim mbPrev (c:cs)
          | mbPrev == Just c = ' ' : trim mbPrev   cs
          | True             =  c  : trim (Just c) cs

        trimRight = reverse . dropWhile isSpace . reverse

split :: [Int] -> [a] -> [[a]]
split _      [] = []
split []     xs = [xs]
split (i:is) xs = case splitAt i xs of
                   (pre, [])   -> [pre]
                   (pre, post) -> pre : split is post