crackNum 4.5 → 4.6
raw patch · 12 files changed
+330/−7 lines, 12 files
Files
- CHANGES.md +28/−1
- GUI/tclGUI/crackNum.tcl +2/−0
- README.md +31/−0
- crackNum.cabal +1/−1
- crackNum.vim +1/−1
- src/CrackNum/Decode.hs +8/−1
- src/CrackNum/Encode.hs +91/−0
- src/CrackNum/Formats.hs +2/−0
- src/CrackNum/Options.hs +6/−0
- src/CrackNum/Output.hs +105/−3
- src/CrackNum/TestSuite.hs +53/−0
- src/CrackNum/Types.hs +2/−0
CHANGES.md view
@@ -1,7 +1,34 @@ * Hackage: <http://hackage.haskell.org/package/crackNum> * GitHub: <http://github.com/LeventErkok/crackNum/> -* Latest Hackage released version: 4.5, 2026-09-01+* Latest Hackage released version: 4.6, 2026-09-03++### Version 4.6, 2026-09-03++ * New floating-point format: UE5M3, selected with `-fue5m3`. This is the unsigned+ FP8 scale format proposed for FP4 microscaling. It is `E4M3` with the sign bit --+ which a scale, being non-negative, never uses -- repurposed as the exponent's top+ bit, giving 5 exponent bits and 3 significand bits in the same 8. The extra+ exponent bit is what the format is for: it drops the smallest non-zero value from+ `E4M3`'s 2^-9 to the subnormal 2^-17, so a block of small-magnitude elements gets+ a scale that can actually represent it.++ * Being a variant of `E4M3`, UE5M3 inherits its deviations from IEEE rather than the+ IEEE reading of the same field widths: there are no infinities, and the all-ones+ pattern is the one and only NaN. Having no sign bit, that is a single pattern+ (`0xFF`) where `E4M3` has one per sign. The rest of the top binade therefore stays+ finite, so the largest representable value is 114688 -- `E4M3`'s 448 carried up the+ eight binades the extra exponent bit buys -- and not the 61440 an IEEE format of+ this shape would stop at. Below that it is ordinary: zero, subnormals, and normals+ all behave as IEEE says, with a bias of 15.++ * Negative inputs are rejected rather than clamped. With no sign bit there is no+ direction to saturate towards, and clamping would quietly turn the value positive;+ this is the same call `E8M0` already makes, and it applies to a negative zero too.+ Values above the range become NaN, following `E4M3`, which likewise has no infinity+ to saturate to.++ * All four GUIs offer the new format, in the "AI formats" group. ### Version 4.5, 2026-09-01
GUI/tclGUI/crackNum.tcl view
@@ -44,6 +44,7 @@ {fe4m3 "FP8 (E4M3)" fixed e4m3} {fe5m2 "FP8 (E5M2)" fixed e5m2} {fe8m0 "FP8 (E8M0)" fixed e8m0}+ {fue5m3 "FP8 (UE5M3)" fixed ue5m3} {fbp "Brain" fixed bp} {ftf32 "TF32" fixed tf32} }}@@ -602,6 +603,7 @@ fp4 { set state(selection) ffp4 } fp4e0m3 { set state(selection) ffp4e0m3 } e8m0 { set state(selection) fe8m0 }+ ue5m3 { set state(selection) fue5m3 } default { if {[regexp {^(\d+)\+(\d+)$} $v _ e s]} { set state(selection) fcs
README.md view
@@ -43,6 +43,7 @@ -ffp4 FP4 (E2M1) 2 2 -ffp4e0m3 FP4 (E0M3), sign-magnitude 0 3 -fe8m0 E8M0 (MX scale), exponent-only 8 0+-fue5m3 UE5M3 (FP8 scale), unsigned 5 4 -fa+b Arbitrary IEEE-754 float a b ``` @@ -116,6 +117,29 @@ Note: Original value of 10.0 was rounded to 8.0. ``` +### Example: Decode a UE5M3 FP8 scale+`UE5M3` is `E4M3` with the sign bit -- which a scale never uses -- repurposed as the+exponent's top bit. Like `E4M3` it has no infinities and exactly one `NaN`, so the top of+the exponent range stays finite: this pattern is 65536, not the infinity an IEEE format of+the same shape would read:+```+$ crackNum -fue5m3 0xF8+Satisfiable. Model:+ DECODED = 65536.0 :: UE5M3+ 76543 210+ -E5-- S3-+ Binary layout: 11111 000+ Hex layout: F8+ Precision: 5 exponent bits, 3 significand bits+ Sign: Positive (always)+ Exponent: 16 (Stored: 31, Bias: 15)+ Classification: FP_NORMAL+ Binary: 0b1p+16+ Octal: 0o2p+15+ Decimal: 65536.0+ Hex: 0x1p+16+```+ ### Example: Decode two half-precision lanes ``` $ crackNum -l2 -fhp 32\'hfdc71fc6@@ -202,6 +226,7 @@ fp4: FP4 format (E2M1) ( 2 + 2) fp4e0m3: FP4 format (E0M3) ( 0 + 3) e8m0: FP8 format (MX scale) ( 8 + 0)+ ue5m3: FP8 format (Unsigned) ( 5 + 4) Examples: Encoding:@@ -218,6 +243,7 @@ crackNum -ffp4 2.5 -- encode as an FP4 (E2M1) float crackNum -ffp4e0m3 3.5 -- encode as an FP4 (E0M3) sign-magnitude integer crackNum -fe8m0 2.5 -- encode as an E8M0 MX scale (power of two)+ crackNum -fue5m3 2.5 -- encode as a UE5M3 FP8 scale (unsigned) crackNum -fsp 0x3.2p5 -- encode as single-precision from hex-float Decoding:@@ -231,6 +257,7 @@ crackNum -ffp4 0b0111 -- decode as an FP4 (E2M1) float crackNum -ffp4e0m3 0b1101 -- decode as an FP4 (E0M3) sign-magnitude integer crackNum -fe8m0 0x7F -- decode as an E8M0 MX scale (power of two)+ crackNum -fue5m3 0x78 -- decode as a UE5M3 FP8 scale (unsigned) crackNum -l4 -fhp 64\'hbdffaaffdc71fc60 -- decode as half-precision float over 4 lanes using verilog notation GUI:@@ -252,6 +279,10 @@ so every value is a power of two, from 2^-127 to 2^127. It has no zero and no Inf, and 0xFF is its only NaN. Negative inputs are rejected; values outside the range saturate to the nearest end-point.+ - UE5M3 is E4M3 with the sign bit repurposed as the exponent's top bit: 5+ exponent bits and 3 significand bits, and no sign. Like E4M3 it has no+ Inf, and 0xFF is its only NaN, so the range runs [0, 114688]. Negative+ inputs are rejected, and values above the range become NaN. - 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 : 4.5+Version : 4.6 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. Along with a command-line interface on any platform, native MacOS and Windows GUIs, a Tcl-based Linux GUI, and a browser front-end are available as well:
crackNum.vim view
@@ -40,7 +40,7 @@ " Used only when crackNum is too old to know --list-formats, or is not on the PATH. " Anything crackNum has learned since is picked up from the executable, not from here. let s:crackNumFallbackFormats = [ "hp", "bp", "tf32", "sp", "dp", "qp"- \ , "e5m2", "e4m3", "fp4", "fp4e0m3", "e8m0"+ \ , "e5m2", "e4m3", "fp4", "fp4e0m3", "e8m0", "ue5m3" \ ] " The formats the executable reports, as -f flags. Asking it keeps this list from
src/CrackNum/Decode.hs view
@@ -123,7 +123,8 @@ E4M3 -> de4m3 config allBits FP4 -> dFP4 config allBits FP4E0M3 -> decodeFP4E0M3 allBits- E8M0 -> decodeE8M0 debug allBits+ E8M0 -> decodeE8M0 debug allBits+ UE5M3 -> decodeUE5M3 debug allBits dFloat :: [SBool] -> ConstraintSet dFloat bs = do x <- sFloat "DECODED"@@ -188,3 +189,9 @@ decodeE8M0 :: Bool -> [Bool] -> IO () decodeE8M0 debug bs@[_, _, _, _, _, _, _, _] = putStr $ unlines $ e8m0Layout debug "DECODED" (foldl (\sofar b -> 2 * sofar + (if b then 1 else 0)) 0 bs) decodeE8M0 _ bs = error $ "decodeE8M0: Unexpected bits: " ++ show bs -- Can't happen; the caller checks the width++-- | Decoding UE5M3: the byte is read straight off, five bits of exponent then three of+-- significand, with no sign bit in the way.+decodeUE5M3 :: Bool -> [Bool] -> IO ()+decodeUE5M3 debug bs@[_, _, _, _, _, _, _, _] = putStr $ unlines $ ue5m3Layout debug "DECODED" (foldl (\sofar b -> 2 * sofar + (if b then 1 else 0)) 0 bs)+decodeUE5M3 _ bs = error $ "decodeUE5M3: Unexpected bits: " ++ show bs -- Can't happen; the caller checks the width
src/CrackNum/Encode.hs view
@@ -152,6 +152,8 @@ ef E8M0 _ = encodeE8M0 debug rm inp + ef UE5M3 _ = encodeUE5M3 debug rm inp+ -- Encoding E4M3 is tricky, because of deviation from IEEE. So, we do a case analysis, mostly encodeE4M3 :: Bool -> RM -> String -> IO () encodeE4M3 debug rm inp = case reads (fixup True inp) of@@ -530,6 +532,95 @@ | isInfinite v || v > largest || v < smallest = do putStrLn $ " Note: Original value of " ++ show v ++ " is out of range, saturated to " ++ show t ++ "." putStrLn " The representable range is [2^-127, 2^127]."+ | v == t+ = exact+ | True+ = putStrLn $ " Note: Original value of " ++ show v ++ " was rounded to " ++ show t ++ "."++ exact = putStrLn $ " Note: Conversion from " ++ show inp ++ " was exact. No rounding happened."++-- | Encoding UE5M3. Every representable value is exact as a Double and the encodings run in+-- increasing order, so we round by hand against the table rather than going through LibBF.+-- We have to: the top seven encodings sit exactly where IEEE puts infinity and NaN, so no+-- amount of IEEE rounding would ever land on them. Ties break on the parity of the encoding+-- index, which for this format is precisely IEEE's ties-to-even -- stepping one encoding steps+-- the significand by one, across binade boundaries included -- and is what 'encodeFP4' and+-- 'encodeE8M0' already do.+encodeUE5M3 :: Bool -> RM -> String -> IO ()+encodeUE5M3 debug 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 largest :: Double+ largest = last ue5m3Mags -- 114688, the deviant encoding 0xFE++ -- The one and only NaN: all ones. Being unsigned, UE5M3 has a single such pattern+ -- where E4M3, which it otherwise follows, has one for each sign.+ nanBits :: Int+ nanBits = 0xFF++ analyze :: Double -> IO ()+ analyze v+ -- NaN is representable, and uniquely so.+ | isNaN v+ = out nanBits+ -- A negative is not an out-of-range magnitude: with no sign bit there is no direction+ -- to saturate towards, and clamping would quietly make it positive. A negative zero is+ -- still negative -- the same call 'encodeE8M0' makes.+ | v < 0 || isNegativeZero v+ = die [ "UE5M3 has no representation for negative values."+ , "The representable range is [0, 114688], plus NaN."+ ]+ -- Having no infinity to saturate to, E4M3 turns whatever it cannot represent into NaN+ -- rather than clamping; UE5M3 inherits that, and infinity is the limiting case of it.+ | isInfinite v || v > largest+ = out nanBits+ | True+ = out (roundMag v)+ where out stored = do putStr $ unlines $ ue5m3Layout debug "ENCODED" stored+ trailer v stored++ -- Round to the index of one of the representable magnitudes, honoring the rounding mode.+ -- Every value reaching here is non-negative, so RTZ and RTN necessarily agree, as do RTP+ -- and rounding away from zero.+ roundMag :: Double -> Int+ roundMag m+ | e : _ <- [i | (i, mv) <- zip [0..] ue5m3Mags, mv == m] -- Exactly representable+ = e+ | True+ = case rm of+ RTZ -> lo+ RTN -> lo+ RTP -> hi+ RNE -> nearest (if even lo then lo else hi)+ RNA -> nearest hi+ where lo = last [i | (i, mv) <- zip [0..] ue5m3Mags, mv < 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) (ue5m3Mags !! lo + ue5m3Mags !! hi) of+ LT -> lo+ GT -> hi+ EQ -> tie++ trailer :: Double -> Int -> IO ()+ trailer v stored = do putStrLn $ " Rounding mode: " ++ show rm+ note+ where t = ue5m3Value stored++ note+ | isNaN v+ = exact+ | isInfinite v || v > largest+ = do putStrLn $ " Note: The input value " ++ show v ++ " is out of bounds, and hence becomes NaN."+ putStrLn " The representable range is [0, 114688]." | v == t = exact | True
src/CrackNum/Formats.hs view
@@ -50,6 +50,7 @@ , ("fp4", "FP4 format (E2M1)", "( 2 + 2)", True ) , ("fp4e0m3", "FP4 format (E0M3)", "( 0 + 3)", True ) , ("e8m0", "FP8 format (MX scale)", "( 8 + 0)", True )+ , ("ue5m3", "FP8 format (Unsigned)", "( 5 + 4)", True ) ] -- | The formats that can actually be named, i.e., everything but the arbitrary a+b@@ -78,6 +79,7 @@ getFP "fp4" = Floating FP4 getFP "fp4e0m3" = Floating FP4E0M3 getFP "e8m0" = Floating E8M0+getFP "ue5m3" = Floating UE5M3 getFP ab = case span isDigit ab of (eb@(_:_), '+':r) -> case span isDigit r of (sp@(_:_), "") -> mkEBSB (read eb) (read sp)
src/CrackNum/Options.hs view
@@ -87,6 +87,7 @@ , " " ++ pn ++ " -ffp4 2.5 -- encode as an FP4 (E2M1) float" , " " ++ pn ++ " -ffp4e0m3 3.5 -- encode as an FP4 (E0M3) sign-magnitude integer" , " " ++ pn ++ " -fe8m0 2.5 -- encode as an E8M0 MX scale (power of two)"+ , " " ++ pn ++ " -fue5m3 2.5 -- encode as a UE5M3 FP8 scale (unsigned)" , " " ++ pn ++ " -fsp 0x3.2p5 -- encode as single-precision from hex-float" , "" , " Decoding:"@@ -100,6 +101,7 @@ , " " ++ pn ++ " -ffp4 0b0111 -- decode as an FP4 (E2M1) float" , " " ++ pn ++ " -ffp4e0m3 0b1101 -- decode as an FP4 (E0M3) sign-magnitude integer" , " " ++ pn ++ " -fe8m0 0x7F -- decode as an E8M0 MX scale (power of two)"+ , " " ++ pn ++ " -fue5m3 0x78 -- decode as a UE5M3 FP8 scale (unsigned)" , " " ++ pn ++ " -l4 -fhp 64\\'hbdffaaffdc71fc60 -- decode as half-precision float over 4 lanes using verilog notation" , "" , " GUI:"@@ -121,6 +123,10 @@ , " so every value is a power of two, from 2^-127 to 2^127. It has no zero" , " and no Inf, and 0xFF is its only NaN. Negative inputs are rejected;" , " values outside the range saturate to the nearest end-point."+ , " - UE5M3 is E4M3 with the sign bit repurposed as the exponent\'s top bit: 5"+ , " exponent bits and 3 significand bits, and no sign. Like E4M3 it has no"+ , " Inf, and 0xFF is its only NaN, so the range runs [0, 114688]. Negative"+ , " inputs are rejected, and values above the range become NaN." , " - For decoding:" , " - Use hexadecimal (0x) binary (0b), or N'h (verilog) notation as input." , " Input must have one of these prefixes."
src/CrackNum/Output.hs view
@@ -16,15 +16,18 @@ module CrackNum.Output( retype, printAs, modOut, isClassification, dropNaNUniquenessNote, canonicalNaN, ExtraE3M4(..), toD, inBases, fp4e0m3Layout, e8m0Bias, e8m0Value, e8m0Layout+ , ue5m3Bias, ue5m3Value, ue5m3Mags, ue5m3IsDeviant, ue5m3Layout ) where -import Data.Char (intToDigit, toUpper)-import Data.List (intercalate, isInfixOf)+import Data.Char (intToDigit, isSpace, toUpper)+import Data.List (dropWhileEnd, intercalate, isInfixOf) import Numeric (showIntAtBase) import Data.SBV import qualified Data.SBV as SBV+import Data.SBV.Float (fpFromRawRep)+import Data.SBV.Internals (SBV(..), SVal(..), CV(..), CVal(..)) import CrackNum.Types @@ -73,7 +76,7 @@ isClassification = ("Classification:" `isInfixOf`) -- | SBV notes that a NaN's representation is not unique. That holds for IEEE formats,--- but not for the ones here that have exactly one NaN pattern (E4M3 and E8M0), so drop+-- but not for the ones here that have exactly one NaN pattern (E4M3, E8M0 and UE5M3), so drop -- the note for those rather than claim an ambiguity the format does not have. dropNaNUniquenessNote :: [String] -> [String] dropNaNUniquenessNote = filter (not . ("Representation for NaN's is not unique" `isInfixOf`))@@ -186,3 +189,102 @@ inBase b x = showIntAtBase b intToDigit x "" pad n x = replicate (n - length x) '0' ++ x++-- | UE5M3 is the unsigned FP8 scale format proposed for FP4 microscaling. It is E4M3 with the+-- sign bit -- which a scale, being non-negative, never uses -- repurposed as the exponent's+-- top bit, giving 5 exponent bits and 3 significand bits in the same 8. Being a variant of+-- E4M3 it inherits E4M3's deviations from IEEE: there are no infinities, and the all-ones+-- pattern is the one and only NaN. Having no sign bit, that is a single pattern (0xFF) where+-- E4M3 has two. The rest of the top binade therefore stays finite, so the largest value is+-- 114688 rather than the 61440 an IEEE format with these field widths would stop at.+ue5m3Bias :: Int+ue5m3Bias = 15++-- | The encodings where UE5M3 parts company with IEEE: 0xF8 to 0xFE would be infinity and+-- NaN, but are read as ordinary finite numbers, 65536 through 114688. This is exactly E4M3's+-- deviation -- its 256 through 448 -- carried up the eight binades the extra exponent bit buys.+ue5m3IsDeviant :: Int -> Bool+ue5m3IsDeviant b = b >= 0xF8 && b <= 0xFE++-- | The value a UE5M3 encoding denotes. All 255 finite encodings are exactly representable as+-- a Double -- the smallest is the subnormal 2^-17 and the largest is 114688 -- so 'encodeFloat'+-- builds every one of them without rounding, which @2 **@ would not be guaranteed to do.+ue5m3Value :: Int -> Double+ue5m3Value 255 = 0/0+ue5m3Value b = case b `divMod` 8 of+ (0, m) -> encodeFloat (fromIntegral m) (-17) -- zero, then the subnormals+ (e, m) -> encodeFloat (fromIntegral (8 + m)) (e - 18) -- the normals, implicit bit restored++-- | Every finite UE5M3 magnitude, in increasing order. The index of each is precisely its+-- encoding, which is what the encoder's rounding search relies on: stepping one encoding+-- steps one representable value, so ties break on the parity of the index.+ue5m3Mags :: [Double]+ue5m3Mags = map ue5m3Value [0 .. 254]++-- | Lay out a UE5M3 value. There is no 8-bit IEEE look-alike with five exponent bits to lean+-- on -- adding the sign bit IEEE insists on would make it nine -- so the layout is built by+-- hand, following the shape crackNum prints for the other formats. Everything from the+-- precision down still comes from a look-alike, since that part describes the value rather+-- than where its bits sit: 'FP 5 4' says exactly the right thing for the 249 ordinary+-- encodings, including which of them are subnormal and which is NaN. Only its sign line has+-- to be overridden, since it has a sign bit and UE5M3 does not.+--+-- The seven deviants have no float look-alike at all -- that is what makes them deviant -- so+-- they take their value lines from the Double they are equal to, exactly as 'e8m0Layout' does+-- and for the same reason. That prints them exactly, which matters here: their spacing is+-- 8192, so a look-alike of UE5M3's own precision would render 65536 as "65540". E4M3 spells+-- its deviants out exactly for this same reason, in 'inBases'.+ue5m3Layout :: Bool -> String -> Int -> [String]+ue5m3Layout debug tag stored =+ [ "Satisfiable. Model:"+ , " " ++ tag ++ " = " ++ valStr ++ " :: " ++ show UE5M3+ , " 76543 210"+ , " -E5-- S3-"+ , " Binary layout: " ++ pad 5 (inBase 2 e) ++ " " ++ pad 3 (inBase 2 m)+ , " Hex layout: " ++ map toUpper (pad 2 (inBase 16 stored))+ ]+ ++ dropNaNUniquenessNote body+ where (e, m) = stored `divMod` 8++ -- How the value renders on the model line, and the lines describing it. A Double knows+ -- nothing of UE5M3's fields, so for a deviant the three lines between the layout and+ -- the classification are written out here rather than taken from it.+ (valStr, body)+ | ue5m3IsDeviant stored+ = ( show v+ , [ " Precision: 5 exponent bits, 3 significand bits"+ , " Sign: " ++ alwaysPositive+ , " Exponent: 16 (Stored: 31, Bias: " ++ show ue5m3Bias ++ ")"+ ]+ ++ dropWhile (not . isClassification) (cracked (literal v :: SDouble))+ )+ | True+ = ( untype (show lookAlike)+ , map fixSign $ dropWhile (not . ("Precision:" `isInfixOf`)) (cracked lookAlike)+ )+ where v = ue5m3Value stored+ lookAlike = mkFP 5 4 (fromIntegral e) (fromIntegral m) :: SFloatingPoint 5 4++ cracked :: SBV a -> [String]+ cracked = lines . SBV.crack debug++ -- NB. There is no sign bit: bit 7 is the exponent's MSB. We keep the line so the block+ -- has the same shape as every other format's, but say outright that it can never read+ -- anything else -- the same thing 'e8m0Layout' does, for the same reason.+ alwaysPositive = "Positive (always)"++ fixSign l | "Sign:" `isInfixOf` l = takeWhile (/= ':') l ++ ": " ++ alwaysPositive+ | True = l++ -- 'show' on a look-alike appends its own type, which is not the one the user asked for.+ untype = dropWhileEnd isSpace . takeWhile (/= ':')++ inBase b x = showIntAtBase b intToDigit x ""++ pad n x = replicate (n - length x) '0' ++ x++-- | A concrete float with the given field widths and stored fields, and a zero sign. Used only+-- as a stand-in for UE5M3, which has no look-alike of its own.+mkFP :: Int -> Int -> Integer -> Integer -> SBV a+mkFP eb sb e m = SBV (SVal k (Left (CV k (CFP (fpFromRawRep False (e, eb) (m, sb))))))+ where k = KFP eb sb
src/CrackNum/TestSuite.hs view
@@ -176,6 +176,41 @@ | rm <- ["RNE", "RNA", "RTP", "RTN", "RTZ"] , i :: Double <- [0.75, 1.5, 3, 6] ]+ , testGroup "EncodeUE5M3" [+ gold "encodeUE5M3_nan" "-fue5m3 nan" -- Representable, and uniquely so+ , gold "encodeUE5M3_+inf" "-fue5m3 inf" -- No Inf to saturate to, so it becomes NaN+ , gold "encodeUE5M3_-inf" "-fue5m3 -- -inf" -- Negative: rejected before the range check+ , gold "encodeUE5M3_neg" "-fue5m3 -- -5"+ , gold "encodeUE5M3_zero1" "-fue5m3 -- 0" -- Unlike E8M0, this format does have a zero+ , gold "encodeUE5M3_zero2" "-fue5m3 -- -0" -- But a negative zero is still negative+ , gold "encodeUE5M3_one" "-fue5m3 -- 1"+ , gold "encodeUE5M3_exact" "-fue5m3 -- 2.5"+ , gold "encodeUE5M3_sub" "-fue5m3 -- 0x1p-17" -- The smallest non-zero value there is+ , gold "encodeUE5M3_subrnd" "-fue5m3 -- 1e-10" -- Under it, so rounds away to zero+ , gold "encodeUE5M3_ieeemax" "-fue5m3 -- 61440" -- The largest value IEEE would have stopped at+ , gold "encodeUE5M3_ieeernd" "-fue5m3 -- 61441" -- IEEE would overflow here; UE5M3 does not+ , gold "encodeUE5M3_dev" "-fue5m3 -- 65536" -- First of the seven that deviate+ , gold "encodeUE5M3_max" "-fue5m3 -- 114688" -- Last of them, and the largest value+ , gold "encodeUE5M3_oob" "-fue5m3 -- 114689" -- Over the top: NaN, following E4M3+ , gold "encodeUE5M3_hex" "-fue5m3 -- 0x1.8p1"+ ]+ -- Every value that sits exactly half-way between two representable magnitudes, over+ -- all rounding modes. These pin down the RNE tie rule where it is easiest to get+ -- wrong: at the zero/subnormal boundary, across a binade, and -- for the last two --+ -- across the point where UE5M3 parts company with IEEE and its top seven encodings+ -- become ordinary numbers rather than infinity and NaN. All values are positive;+ -- negatives are rejected outright.+ , testGroup "EncodeUE5M3Ties" [+ gold ("encodeUE5M3_tie_" ++ rm ++ "_" ++ nm) ("-fue5m3 -r" ++ rm ++ " -- " ++ v)+ | rm <- ["RNE", "RNA", "RTP", "RTN", "RTZ"]+ , (nm, v) <- [ ("sub0", "0x1p-18") -- Between zero and the smallest subnormal+ , ("sub1", "0x3p-18") -- Between the two smallest subnormals+ , ("norm", "1.0625")+ , ("binade","1.9375") -- Straddles a binade boundary+ , ("dev0", "63488") -- Straddles the last IEEE-shaped value+ , ("dev1", "69632")+ ]+ ] , testGroup "Decode" [ gold "decode0" "-i4 0b0110" , gold "decode1" "-w4 0xE"@@ -243,6 +278,24 @@ , "80" , "FD" , "FE" -- Largest: 2^127+ , "FF" -- NaN, and the only one+ ]+ ]+ -- UE5M3 has 256 patterns, so we take a spread the way DecodeE8M0 does, covering+ -- each structural case: the zero, both ends of the subnormals, the first normal,+ -- the unit value, the last IEEE-shaped value, the deviants either side, and the+ -- sole NaN.+ , testGroup "DecodeUE5M3" [+ gold ("decodeUE5M3_" ++ bits) ("-fue5m3 0x" ++ bits)+ | bits <- [ "00" -- Zero+ , "01" -- Smallest subnormal: 2^-17+ , "07" -- Largest subnormal+ , "08" -- Smallest normal+ , "78" -- 1.0+ , "F7" -- 61440: the largest an IEEE format of this shape would reach+ , "F8" -- 65536: IEEE would call this infinity+ , "FB" -- 90112: IEEE would call this NaN+ , "FE" -- 114688, the largest value , "FF" -- NaN, and the only one ] ]
src/CrackNum/Types.hs view
@@ -29,6 +29,7 @@ | FP4 -- NVIDIA FP4 (E2M1) format with no infinities and no NaNs | FP4E0M3 -- 4-bit sign-magnitude integer format; no exponent at all | E8M0 -- OCP MX scale format; no sign and no significand at all+ | UE5M3 -- Unsigned FP8 scale format; E4M3 with the sign bit given to the exponent deriving (Show, Eq) -- | How many bits does this float occupy@@ -41,6 +42,7 @@ fpSize FP4 = 4 fpSize FP4E0M3 = 4 fpSize E8M0 = 8+fpSize UE5M3 = 8 -- | Kinds of numbers we understand data NKind = SInt Int -- ^ Signed integer of n bits