packages feed

crackNum 4.5 → 4.6

raw patch · 12 files changed

+330/−7 lines, 12 files

Files

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