crackNum 3.24 → 3.25
raw patch · 6 files changed
+311/−92 lines, 6 files
Files
- CHANGES.md +16/−0
- GUI/tclGUI/crackNum.tcl +21/−19
- README.md +79/−33
- crackNum.cabal +1/−1
- src/CrackNum/Main.hs +151/−39
- src/CrackNum/TestSuite.hs +43/−0
CHANGES.md view
@@ -3,6 +3,22 @@ * Latest Hackage released version: 3.24, 2026-08-17 +### Version 3.25, 2026-08-18++ * Add support for the FP4 (E0M3) format, via `-ffp4e0m3`. Unlike every other+ format crackNum knows about, this one has no exponent bits at all, which makes+ it a plain 4-bit sign-magnitude integer: a sign bit and a 3-bit magnitude,+ covering -7 to 7, with both a positive and a negative zero. It has neither NaN+ nor Inf, so those inputs are rejected; values outside [-7, 7] saturate to the+ nearest end-point, and fractional inputs round according to `-r`.++ * Report the format the user actually asked for when decoding E4M3 and FP4.+ Both are modeled by an IEEE look-alike, and the patterns that need no special+ handling were printed straight from it, so `crackNum -ffp4 0b0100` answered+ `2.0 :: FloatingPoint 2 2` and `crackNum -fe4m3 0b00111000` answered+ `1.0 :: FloatingPoint 4 4`. The values were right, only the type name leaked.+ The patterns that do deviate, and E5M2 throughout, were already correct.+ ### Version 3.24, 2026-08-17 * Add a quad-precision example to the help output. `-fqp` has always been
GUI/tclGUI/crackNum.tcl view
@@ -33,16 +33,17 @@ set FORMAT_SECTIONS { {"Float" {- {ffp4 "FP4 (E2M1)" fixed fp4}- {fe4m3 "FP8 (E4M3)" fixed e4m3}- {fe5m2 "FP8 (E5M2)" fixed e5m2}- {fhp "Half" fixed hp}- {fbp "Brain" fixed bp}- {ftf32 "TF32" fixed tf32}- {fsp "Single" fixed sp}- {fdp "Double" fixed dp}- {fqp "Quad" fixed qp}- {fcs "Custom" customFloat {}}+ {ffp4 "FP4 (E2M1)" fixed fp4}+ {ffp4e0m3 "FP4 (E0M3)" fixed fp4e0m3}+ {fe4m3 "FP8 (E4M3)" fixed e4m3}+ {fe5m2 "FP8 (E5M2)" fixed e5m2}+ {fhp "Half" fixed hp}+ {fbp "Brain" fixed bp}+ {ftf32 "TF32" fixed tf32}+ {fsp "Single" fixed sp}+ {fdp "Double" fixed dp}+ {fqp "Quad" fixed qp}+ {fcs "Custom" customFloat {}} }} {"Word (Unsigned)" { {w8 "8-bit" word 8}@@ -469,15 +470,16 @@ if {[string match "-f*" $a]} { set v [string tolower [string range $a 2 end]] switch $v {- sp { set state(selection) fsp }- dp { set state(selection) fdp }- qp { set state(selection) fqp }- hp { set state(selection) fhp }- bp { set state(selection) fbp }- tf32 { set state(selection) ftf32 }- e4m3 { set state(selection) fe4m3 }- e5m2 { set state(selection) fe5m2 }- fp4 { set state(selection) ffp4 }+ sp { set state(selection) fsp }+ dp { set state(selection) fdp }+ qp { set state(selection) fqp }+ hp { set state(selection) fhp }+ bp { set state(selection) fbp }+ tf32 { set state(selection) ftf32 }+ e4m3 { set state(selection) fe4m3 }+ e5m2 { set state(selection) fe5m2 }+ fp4 { set state(selection) ffp4 }+ fp4e0m3 { set state(selection) ffp4e0m3 } default { if {[regexp {^(\d+)\+(\d+)$} $v _ e s]} { set state(selection) fcs
README.md view
@@ -23,19 +23,24 @@ ### Supported formats -| Flag | Format | Exponent | Significand (incl. implicit bit) |-|-----------|-------------------------------------|---------:|---------------------------------:|-| `-fhp` | Half precision (IEEE-754 binary16) | 5 | 11 |-| `-fbp` | Brain float (bfloat16) | 8 | 8 |-| `-ftf32` | TensorFloat-32 | 8 | 11 |-| `-fsp` | Single precision (binary32) | 8 | 24 |-| `-fdp` | Double precision (binary64) | 11 | 53 |-| `-fqp` | Quad precision (binary128) | 15 | 113 |-| `-fe5m2` | FP8, IEEE-754 style | 5 | 3 |-| `-fe4m3` | FP8, alternate (no infinities) | 4 | 4 |-| `-ffp4` | FP4 (E2M1) | 2 | 2 |-| `-fa+b` | Arbitrary IEEE-754 float | a | b |+| Flag | Format | Exponent | Significand (incl. implicit bit) |+|-------------|-------------------------------------|---------:|---------------------------------:|+| `-fhp` | Half precision (IEEE-754 binary16) | 5 | 11 |+| `-fbp` | Brain float (bfloat16) | 8 | 8 |+| `-ftf32` | TensorFloat-32 | 8 | 11 |+| `-fsp` | Single precision (binary32) | 8 | 24 |+| `-fdp` | Double precision (binary64) | 11 | 53 |+| `-fqp` | Quad precision (binary128) | 15 | 113 |+| `-fe5m2` | FP8, IEEE-754 style | 5 | 3 |+| `-fe4m3` | FP8, alternate (no infinities) | 4 | 4 |+| `-ffp4` | FP4 (E2M1) | 2 | 2 |+| `-ffp4e0m3` | FP4 (E0M3), sign-magnitude | 0 | 3 |+| `-fa+b` | Arbitrary IEEE-754 float | a | b | +FP4 (E0M3) is the odd one out: with no exponent bits at all it is really a 4-bit+sign-magnitude *integer*, holding a sign and a 3-bit magnitude. It covers -7 to 7,+with both a positive and a negative zero, and has neither NaN nor Inf.+ Integers come in two flavors: `-iN` for a signed `N`-bit 2's complement integer, and `-wN` for an unsigned `N`-bit word. Both `N` and the arbitrary float sizes can be as large as you like, within machine-word limits.@@ -149,6 +154,42 @@ Hex: 0x6 ``` +### Example: Decode an FP4 (E0M3) sign-magnitude integer+```+$ crackNum -ffp4e0m3 0b1101+Satisfiable. Model:+ DECODED = -5 :: FP4E0M3+ 3 210+ S -M-+ Binary layout: 1 101+ Hex layout: D+ Type: 4-bit sign-magnitude integer+ Sign: Negative+ Binary: -0b101+ Octal: -0o5+ Decimal: -5+ Hex: -0x5+```++### Example: Encode an FP4 (E0M3) sign-magnitude integer+```+$ crackNum -ffp4e0m3 -- -5+Satisfiable. Model:+ ENCODED = -5 :: FP4E0M3+ 3 210+ S -M-+ Binary layout: 1 101+ Hex layout: D+ Type: 4-bit sign-magnitude integer+ Sign: Negative+ Binary: -0b101+ Octal: -0o5+ Decimal: -5+ Hex: -0x5+ Rounding mode: RNE: Round nearest ties to even.+ Note: Conversion from "-5" was exact. No rounding happened.+```+ ### Example: Encode a TensorFloat-32 number ``` $ crackNum -ftf32 2.5@@ -319,29 +360,31 @@ Examples: Encoding:- crackNum -i4 -- -2 -- encode as 4-bit signed integer- crackNum -w4 2 -- encode as 4-bit unsigned integer- crackNum -f3+4 2.5 -- encode as float with 3 bits exponent, 4 bits significand- crackNum -f3+4 2.5 -rRTZ -- encode as above, but use RTZ rounding mode.- crackNum -fbp 2.5 -- encode as a brain-precision float- crackNum -ftf32 2.5 -- encode as a TensorFloat-32 float- crackNum -fdp 2.5 -- encode as a double-precision float- crackNum -fqp 2.5 -- encode as a quad-precision float- crackNum -fe4m3 2.5 -- encode as an E4M3 FP8 float- crackNum -fe5m2 2.5 -- encode as an E5M2 FP8 float- crackNum -ffp4 2.5 -- encode as an FP4 (E2M1) float- crackNum -fsp 0x3.2p5 -- encode as single-precision from hex-float+ crackNum -i4 -- -2 -- encode as 4-bit signed integer+ crackNum -w4 2 -- encode as 4-bit unsigned integer+ crackNum -f3+4 2.5 -- encode as float with 3 bits exponent, 4 bits significand+ crackNum -f3+4 2.5 -rRTZ -- encode as above, but use RTZ rounding mode.+ crackNum -fbp 2.5 -- encode as a brain-precision float+ crackNum -ftf32 2.5 -- encode as a TensorFloat-32 float+ crackNum -fdp 2.5 -- encode as a double-precision float+ crackNum -fqp 2.5 -- encode as a quad-precision float+ crackNum -fe4m3 2.5 -- encode as an E4M3 FP8 float+ crackNum -fe5m2 2.5 -- encode as an E5M2 FP8 float+ crackNum -ffp4 2.5 -- encode as an FP4 (E2M1) float+ crackNum -ffp4e0m3 3.5 -- encode as an FP4 (E0M3) sign-magnitude integer+ crackNum -fsp 0x3.2p5 -- encode as single-precision from hex-float Decoding:- crackNum -i4 0b0110 -- decode as 4-bit signed integer, from binary- crackNum -w4 0xE -- decode as 4-bit unsigned integer, from hex- crackNum -f3+4 0b0111001 -- decode as float with 3 bits exponent, 4 bits significand- crackNum -fbp 0x000F -- decode as a brain-precision float- crackNum -ftf32 19\'h0000F -- decode as a TensorFloat-32 float- crackNum -fdp 0x8000000000000000 -- decode as a double-precision float- crackNum -fhp 0x8000 -- decode as a half-precision float- crackNum -ffp4 0b0111 -- decode as an FP4 (E2M1) float- crackNum -l4 -fhp 64\'hbdffaaffdc71fc60 -- decode as half-precision float over 4 lanes using verilog notation+ crackNum -i4 0b0110 -- decode as 4-bit signed integer, from binary+ crackNum -w4 0xE -- decode as 4-bit unsigned integer, from hex+ crackNum -f3+4 0b0111001 -- decode as float with 3 bits exponent, 4 bits significand+ crackNum -fbp 0x000F -- decode as a brain-precision float+ crackNum -ftf32 19\'h0000F -- decode as a TensorFloat-32 float+ crackNum -fdp 0x8000000000000000 -- decode as a double-precision float+ crackNum -fhp 0x8000 -- decode as a half-precision float+ crackNum -ffp4 0b0111 -- decode as an FP4 (E2M1) float+ crackNum -ffp4e0m3 0b1101 -- decode as an FP4 (E0M3) sign-magnitude integer+ crackNum -l4 -fhp 64\'hbdffaaffdc71fc60 -- decode as half-precision float over 4 lanes using verilog notation GUI: crackNum --gui -- launch the graphical interface@@ -354,6 +397,9 @@ along with a decimal (2.3, -4.1e5) or hexadecimal float (0x2.4p3) - FP4 (E2M1) has neither NaN nor Inf, so those inputs are rejected. Finite values outside its range of [-6, 6] saturate to the nearest end-point.+ - FP4 (E0M3) is a sign-magnitude integer: a sign bit and a 3-bit magnitude,+ covering -7 to 7, with both a positive and a negative zero. It has no NaN+ and no Inf either, and values outside [-7, 7] saturate to the end-point. - For decoding: - Use hexadecimal (0x) binary (0b), or N'h (verilog) notation as input. Input must have one of these prefixes.
crackNum.cabal view
@@ -1,6 +1,6 @@ Cabal-version : 2.2 Name : crackNum-Version : 3.24+Version : 3.25 Synopsis : Crack various integer and floating-point data formats Description : Crack IEEE-754 and other float formats and arbitrary sized words and integers, showing the layout. .
src/CrackNum/Main.hs view
@@ -20,7 +20,7 @@ import Control.Monad (when) import Control.DeepSeq (rnf)-import Data.Char (isDigit, isSpace, toLower)+import Data.Char (intToDigit, isDigit, isSpace, toLower, toUpper) import Data.List (isPrefixOf, isSuffixOf, unfoldr, isInfixOf, intercalate) import Data.Maybe (fromMaybe) @@ -64,6 +64,7 @@ | E5M2 -- Synonym for FP 5 3 (yes, confusing M2->3, but that's the naming) | E4M3 -- Custom FP8 format with no infinities and limited NaNs | FP4 -- NVIDIA FP4 (E2M1) format with no infinities and no NaNs+ | FP4E0M3 -- 4-bit sign-magnitude integer format; no exponent at all deriving (Show, Eq) -- | How many bits does this float occupy@@ -74,6 +75,7 @@ fpSize E5M2 = 8 fpSize E4M3 = 8 fpSize FP4 = 4+fpSize FP4E0M3 = 4 kSize :: NKind -> Int kSize (SInt i) = i@@ -161,32 +163,34 @@ -- | Given a float flag value, turn it into a flag getFP :: String -> Flag-getFP "hp" = Floating $ FP 5 11-getFP "bp" = Floating $ FP 8 8-getFP "tf32" = Floating $ FP 8 11-getFP "sp" = Floating SP-getFP "dp" = Floating DP-getFP "qp" = Floating $ FP 15 113-getFP "e5m2" = Floating E5M2-getFP "e4m3" = Floating E4M3-getFP "fp4" = Floating FP4-getFP ab = case span isDigit ab of+getFP "hp" = Floating $ FP 5 11+getFP "bp" = Floating $ FP 8 8+getFP "tf32" = Floating $ FP 8 11+getFP "sp" = Floating SP+getFP "dp" = Floating DP+getFP "qp" = Floating $ FP 15 113+getFP "e5m2" = Floating E5M2+getFP "e4m3" = Floating E4M3+getFP "fp4" = Floating FP4+getFP "fp4e0m3" = Floating FP4E0M3+getFP ab = case span isDigit ab of (eb@(_:_), '+':r) -> case span isDigit r of (sp@(_:_), "") -> mkEBSB (read eb) (read sp) _ -> bad _ -> bad where bad = BadFlag [ "Option " ++ show "-f" ++ " requires one of:" , ""- , " hp: Half float ( 5 + 11)"- , " bp: Brain float ( 8 + 8)"- , " tf32: TensorFloat-32 ( 8 + 11)"- , " sp: Single precision ( 8 + 24)"- , " dp: Double precision (11 + 53)"- , " qp: Quad precision (15 + 113)"- , " a+b: Arbitrary IEEE-754 ( a + b)"- , " e5m2: FP8 format (IEEE-754) ( 5 + 3)"- , " e4m3: FP8 format (Alternate) ( 4 + 4)"- , " fp4: FP4 format (E2M1) ( 2 + 2)"+ , " hp: Half float ( 5 + 11)"+ , " bp: Brain float ( 8 + 8)"+ , " tf32: TensorFloat-32 ( 8 + 11)"+ , " sp: Single precision ( 8 + 24)"+ , " dp: Double precision (11 + 53)"+ , " qp: Quad precision (15 + 113)"+ , " a+b: Arbitrary IEEE-754 ( a + b)"+ , " e5m2: FP8 format (IEEE-754) ( 5 + 3)"+ , " e4m3: FP8 format (Alternate) ( 4 + 4)"+ , " fp4: FP4 format (E2M1) ( 2 + 2)"+ , "fp4e0m3: FP4 format (E0M3) ( 0 + 3)" , "" , "In the arbitrary format, the first number is the number of bits in the exponent" , "and the second number is the number of bits in the significand, including the implicit bit."@@ -254,6 +258,7 @@ , " " ++ pn ++ " -fe4m3 2.5 -- encode as an E4M3 FP8 float" , " " ++ pn ++ " -fe5m2 2.5 -- encode as an E5M2 FP8 float" , " " ++ pn ++ " -ffp4 2.5 -- encode as an FP4 (E2M1) float"+ , " " ++ pn ++ " -ffp4e0m3 3.5 -- encode as an FP4 (E0M3) sign-magnitude integer" , " " ++ pn ++ " -fsp 0x3.2p5 -- encode as single-precision from hex-float" , "" , " Decoding:"@@ -265,6 +270,7 @@ , " " ++ pn ++ " -fdp 0x8000000000000000 -- decode as a double-precision float" , " " ++ pn ++ " -fhp 0x8000 -- decode as a half-precision float" , " " ++ pn ++ " -ffp4 0b0111 -- decode as an FP4 (E2M1) float"+ , " " ++ pn ++ " -ffp4e0m3 0b1101 -- decode as an FP4 (E0M3) sign-magnitude integer" , " " ++ pn ++ " -l4 -fhp 64\\'hbdffaaffdc71fc60 -- decode as half-precision float over 4 lanes using verilog notation" , "" , " GUI:"@@ -278,6 +284,9 @@ , " along with a decimal (2.3, -4.1e5) or hexadecimal float (0x2.4p3)" , " - FP4 (E2M1) has neither NaN nor Inf, so those inputs are rejected. Finite" , " values outside its range of [-6, 6] saturate to the nearest end-point."+ , " - FP4 (E0M3) is a sign-magnitude integer: a sign bit and a 3-bit magnitude,"+ , " covering -7 to 7, with both a positive and a negative zero. It has no NaN"+ , " and no Inf either, and values outside [-7, 7] saturate to the end-point." , " - For decoding:" , " - Use hexadecimal (0x) binary (0b), or N'h (verilog) notation as input." , " Input must have one of these prefixes."@@ -612,12 +621,13 @@ } case fp of- SP -> print =<< satWith config (dFloat bs)- DP -> print =<< satWith config (dDouble bs)- FP i j -> print =<< satWith config (dFP i j bs)- E5M2 -> fixE5M2Type =<< satWith config (dFP 5 3 bs)- E4M3 -> de4m3 config allBits- FP4 -> dFP4 config allBits+ SP -> print =<< satWith config (dFloat bs)+ DP -> print =<< satWith config (dDouble bs)+ FP i j -> print =<< satWith config (dFP i j bs)+ E5M2 -> printAs E5M2 =<< satWith config (dFP 5 3 bs)+ E4M3 -> de4m3 config allBits+ FP4 -> dFP4 config allBits+ FP4E0M3 -> decodeFP4E0M3 allBits dFloat :: [SBool] -> ConstraintSet dFloat bs = do x <- sFloat "DECODED"@@ -642,9 +652,9 @@ do res <- satWith config (dFP 4 4 (map literal allBits)) case res of SatResult (Satisfiable{}) -> de4m3Model debug (sign, s1, s2, s3) res- _ -> print res+ _ -> printAs E4M3 res -- Otherwise, it's just FP 4 4- de4m3 config allBits = print =<< satWith config (dFP 4 4 (map literal allBits))+ de4m3 config allBits = printAs E4M3 =<< satWith config (dFP 4 4 (map literal allBits)) -- FP4 also deviates from IEEE. dFP4 config allBits@[sign, True, True, s1] =@@ -652,10 +662,10 @@ do res <- satWith config (dFP 2 2 (map literal allBits)) case res of SatResult (Satisfiable{}) -> dFP4Model debug (sign, s1) res- _ -> print res+ _ -> printAs FP4 res -- Otherwise, it's just FP 2 2- dFP4 config allBits = print =<< satWith config (dFP 2 2 (map literal allBits))+ dFP4 config allBits = printAs FP4 =<< satWith config (dFP 2 2 (map literal allBits)) -- The non-IEEE formats are all modeled by an IEEE look-alike, so SBV displays the look-alike's -- type name. Rewrite it to the format the user actually asked for.@@ -669,9 +679,10 @@ = s retype _ res = show res --- Print a model for E5M2, this is the same as dFP 5 3, we just fix the "printed" type-fixE5M2Type :: SatResult -> IO ()-fixE5M2Type = putStrLn . retype E5M2+-- Print a model for one of the non-IEEE formats: the look-alike does all the work,+-- we merely fix the type name it prints.+printAs :: FP -> SatResult -> IO ()+printAs fmt = putStrLn . retype fmt -- Print a deviating model for E4M3: de4m3Model :: Bool -> (Bool, Bool, Bool, Bool) -> SatResult -> IO ()@@ -794,12 +805,12 @@ do let hr = readHexRational inp () <- (rnf hr `seq` return ()) `C.catch` (\(_ :: C.SomeException) -> unrecognized inp) res <- satCmd (pRat hr)- if wasE5M2 then fixE5M2Type res+ if wasE5M2 then printAs E5M2 res else print res else do let run | bfIsNaN v = satCmdNaN i j | True = satCmd res <- run (p v)- if wasE5M2 then fixE5M2Type res+ if wasE5M2 then printAs E5M2 res else print res note mbS where p :: BigFloat -> Predicate@@ -821,12 +832,14 @@ pure $ sr .== val .&& SBV (sx `svEqual` SVal k (Right (cache r))) - ef E5M2 _ = ef (FP 5 3) True -- 3 is intentional; the format ignores the sign storage, but SBV doesn't, following SMTLib+ ef E5M2 _ = ef (FP 5 3) True -- 3 is intentional; the format ignores the sign storage, but SBV doesn't, following SMTLib - ef E4M3 _ = encodeE4M3 debug rm inp+ ef E4M3 _ = encodeE4M3 debug rm inp - ef FP4 _ = encodeFP4 debug rm inp+ ef FP4 _ = encodeFP4 debug rm inp + ef FP4E0M3 _ = encodeFP4E0M3 rm inp+ -- | Convert certain strings to more understandable format by read -- If first argument is True, then we're reading using reads, i.e., haskell syntax -- If first argument is False, then we're using big-float library, which has a different notion for infinity and nans@@ -1115,3 +1128,102 @@ = putStrLn $ " Note: Conversion from " ++ show inp ++ " was exact. No rounding happened." | True = putStrLn $ " Note: Original value of " ++ show v ++ " was rounded to " ++ show t ++ "."++-- FP4E0M3 is a 4-bit sign-magnitude integer: a sign bit and a 3-bit magnitude, covering+-- -7 to 7, with both a positive and a negative zero. Having no exponent at all, it has no+-- IEEE look-alike we could lean on, so we lay the bits out by hand; the shape follows what+-- crackNum prints for the plain integer formats, which is what this format really is.+fp4e0m3Layout :: String -> Bool -> Int -> [String]+fp4e0m3Layout tag isNeg mag =+ [ "Satisfiable. Model:"+ , " " ++ tag ++ " = " ++ sign ++ show mag ++ " :: " ++ show FP4E0M3+ , " 3 210"+ , " S -M-"+ , " Binary layout: " ++ (if isNeg then '1' else '0') : ' ' : pad 3 (inBase 2 mag)+ , " Hex layout: " ++ map toUpper (inBase 16 ((if isNeg then 8 else 0) + mag))+ , " Type: 4-bit sign-magnitude integer"+ , " Sign: " ++ (if isNeg then "Negative" else "Positive")+ , " Binary: " ++ sign ++ "0b" ++ inBase 2 mag+ , " Octal: " ++ sign ++ "0o" ++ inBase 8 mag+ , " Decimal: " ++ sign ++ show mag+ , " Hex: " ++ sign ++ "0x" ++ inBase 16 mag+ ]+ where sign = if isNeg then "-" else ""++ inBase b v = showIntAtBase b intToDigit v ""++ pad n s = replicate (n - length s) '0' ++ s++-- | Decoding FP4E0M3: the sign bit and the magnitude are simply read off.+decodeFP4E0M3 :: [Bool] -> IO ()+decodeFP4E0M3 (sign : mag@[_, _, _]) = putStr $ unlines $ fp4e0m3Layout "DECODED" sign (foldl (\sofar b -> 2 * sofar + (if b then 1 else 0)) 0 mag)+decodeFP4E0M3 bs = error $ "decodeFP4E0M3: Unexpected bits: " ++ show bs -- Can't happen; the caller checks the width++-- | Encoding FP4E0M3. The representable values are just the integers -7 to 7, so we round+-- the magnitude by hand, saturating anything that doesn't fit.+encodeFP4E0M3 :: RM -> String -> IO ()+encodeFP4E0M3 rm inp = case reads (fixup True inp) of+ [(v :: Double, "")] -> analyze v+ _ -> -- maybe it's a hexfloat? As in encodeFP4, the catch must+ -- scope over the parse only: analyze can legitimately die,+ -- and die throws an exit-exception of its own.+ do let hr = readHexRational inp+ ok <- (rnf hr `seq` pure True)+ `C.catch` (\(_ :: C.SomeException) -> pure False)+ if ok then analyze (fromRational hr)+ else unrecognized inp+ where analyze :: Double -> IO ()+ analyze v+ | isNaN v+ = die [ "FP4E0M3 has no representation for NaN." ]+ | isInfinite v+ = die [ "FP4E0M3 has no representation for infinity."+ , "The representable range is [-7, 7]."+ ]+ | True+ = do let isNeg = v < 0 || isNegativeZero v+ mag = roundMag isNeg (abs v)++ putStr $ unlines $ fp4e0m3Layout "ENCODED" isNeg mag+ trailer v isNeg mag++ -- Round the magnitude to one of 0 .. 7, honoring the rounding mode. Note that rounding+ -- a negative value towards +oo is the same thing as rounding its magnitude towards 0;+ -- hence the need for the sign here.+ roundMag :: Bool -> Double -> Int+ roundMag isNeg m+ | m >= 7 -- Larger than we can represent; saturate+ = 7+ | m == fromIntegral lo -- Exactly representable+ = lo+ | True+ = case rm of+ RTZ -> lo+ RTP -> if isNeg then lo else hi+ RTN -> if isNeg then hi else lo+ RNE -> nearest (if even lo then lo else hi)+ RNA -> nearest hi+ where lo = floor m+ hi = lo + 1++ -- Ties are broken by the given choice; note that comparing against the sum+ -- avoids any rounding of its own, since all the values involved are exact.+ nearest tie = case compare (2 * m) (fromIntegral (lo + hi)) of+ LT -> lo+ GT -> hi+ EQ -> tie++ -- Since FP4E0M3 has no infinities, out-of-range values saturate to the largest magnitude.+ trailer :: Double -> Bool -> Int -> IO ()+ trailer v isNeg mag = do putStrLn $ " Rounding mode: " ++ show rm+ note+ where t = (if isNeg then "-" else "") ++ show mag++ note+ | abs v > 7+ = do putStrLn $ " Note: Original value of " ++ show v ++ " is out of range, saturated to " ++ t ++ "."+ putStrLn " The representable range is [-7, 7]."+ | abs v == fromIntegral mag+ = putStrLn $ " Note: Conversion from " ++ show inp ++ " was exact. No rounding happened."+ | True+ = putStrLn $ " Note: Original value of " ++ show v ++ " was rounded to " ++ t ++ "."
src/CrackNum/TestSuite.hs view
@@ -121,6 +121,30 @@ | rm <- ["RNE", "RNA", "RTP", "RTN", "RTZ"] , i :: Double <- [0.25, 0.75, 1.25, 1.75, 2.5, 3.5, 5, 7] ]+ , testGroup "EncodeFP4E0M3" [+ gold "encodeFP4E0M3_nan" "-ffp4e0m3 nan"+ , gold "encodeFP4E0M3_+inf" "-ffp4e0m3 inf"+ , gold "encodeFP4E0M3_-inf" "-ffp4e0m3 -- -inf"+ , gold "encodeFP4E0M3_zero1" "-ffp4e0m3 -- 0"+ , gold "encodeFP4E0M3_zero2" "-ffp4e0m3 -- -0"+ , gold "encodeFP4E0M3_exact" "-ffp4e0m3 -- 5"+ , gold "encodeFP4E0M3_max" "-ffp4e0m3 -- 7"+ , gold "encodeFP4E0M3_min" "-ffp4e0m3 -- -7"+ , gold "encodeFP4E0M3_rnd1" "-ffp4e0m3 -- 0.4" -- Rounds down to a zero+ , gold "encodeFP4E0M3_rnd2" "-ffp4e0m3 -- -0.4" -- Rounds down to a negative zero+ , gold "encodeFP4E0M3_oob1" "-ffp4e0m3 -- 100" -- Saturates+ , gold "encodeFP4E0M3_oob2" "-ffp4e0m3 -- -100"+ , gold "encodeFP4E0M3_hex" "-ffp4e0m3 -- 0x1.8p1"+ ]+ -- Every value that sits exactly half-way between two representable magnitudes,+ -- plus one out-of-range value, over all rounding modes.+ , testGroup "EncodeFP4E0M3Ties" $ concat [+ [ gold ("encodeFP4E0M3_tie_" ++ rm ++ "_+" ++ show i) ("-ffp4e0m3 -r" ++ rm ++ " -- " ++ show i)+ , gold ("encodeFP4E0M3_tie_" ++ rm ++ "_-" ++ show i) ("-ffp4e0m3 -r" ++ rm ++ " -- -" ++ show i)+ ]+ | rm <- ["RNE", "RNA", "RTP", "RTN", "RTZ"]+ , i :: Double <- [0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 8]+ ] , testGroup "Decode" [ gold "decode0" "-i4 0b0110" , gold "decode1" "-w4 0xE"@@ -147,6 +171,18 @@ ] , sign <- [False, True] ]+ -- The patterns that are ordinary FP 4 4 values. These go through the look-alike+ -- untouched, so they are the ones that used to leak its type name.+ , testGroup "DecodeE4M3Regular" [+ gold ("decodeE4M3_regular_" ++ bits) ("-fe4m3 0b" ++ bits)+ | bits <- [ "00000000" -- +0+ , "10000000" -- -0+ , "00000001" -- Smallest subnormal+ , "00111000" -- 1+ , "10111100" -- -1.5+ , "01110111" -- 240, the largest non-deviating magnitude+ ]+ ] , testGroup "DecodeE4M3_NaN" [ gold "decodeE4M3_+NaN" "-fe4m3 0b_0111_1111" , gold "decodeE4M3_-NaN" "-fe4m3 0b_1111_1111"@@ -158,6 +194,13 @@ , e <- ["00", "01", "10", "11"] , m <- ["0", "1"] , let bits = s ++ e ++ m+ ]+ -- FP4E0M3 is small enough that we can simply decode every last one of its 16 patterns.+ , testGroup "DecodeFP4E0M3" [+ gold ("decodeFP4E0M3_" ++ bits) ("-ffp4e0m3 0b" ++ bits)+ | s <- ["0", "1"]+ , m <- ["000", "001", "010", "011", "100", "101", "110", "111"]+ , let bits = s ++ m ] , testGroup "Bad" [ gold "badInvocation0" "-f3+4 0b01"