crackNum 3.27 → 3.28
raw patch · 7 files changed
+283/−13 lines, 7 files
Files
- CHANGES.md +18/−1
- GUI/tclGUI/crackNum.tcl +2/−0
- README.md +56/−0
- crackNum.cabal +1/−1
- crackNum.vim +1/−1
- src/CrackNum/Main.hs +167/−10
- src/CrackNum/TestSuite.hs +38/−0
CHANGES.md view
@@ -1,7 +1,24 @@ * Hackage: <http://hackage.haskell.org/package/crackNum> * GitHub: <http://github.com/LeventErkok/crackNum/> -* Latest Hackage released version: 3.27, 2026-08-20+* Latest Hackage released version: 3.28, 2026-08-21++### Version 3.28, 2026-08-21++ * Add support for the E8M0 format, via `-fe8m0`. This is the shared scale of the OCP+ Microscaling (MX) formats: the value that scales a block of MXFP8/MXFP6/MXFP4+ elements, all of which crackNum already knew about. It is the mirror image of+ FP4 (E0M3): where that format is all significand and no exponent, E8M0 is all+ exponent and no significand, with no sign bit either. Every value it holds is+ therefore a power of two, from 2^-127 to 2^127.++ Note that it has no zero and no subnormals -- with nothing for the all-zero+ encoding to mean, it simply denotes 2^-127 -- and no infinities. `0xFF` is its+ one and only NaN. Negative inputs are rejected rather than saturated: with no+ sign bit there is no direction to saturate towards, and clamping would quietly+ turn a negative into a positive. Values outside the range, infinity included,+ saturate to the nearest end-point, and anything that is not a power of two+ rounds according to `-r`. ### Version 3.27, 2026-08-20
GUI/tclGUI/crackNum.tcl view
@@ -37,6 +37,7 @@ {ffp4e0m3 "FP4 (E0M3)" fixed fp4e0m3} {fe4m3 "FP8 (E4M3)" fixed e4m3} {fe5m2 "FP8 (E5M2)" fixed e5m2}+ {fe8m0 "FP8 (E8M0)" fixed e8m0} {fhp "Half" fixed hp} {fbp "Brain" fixed bp} {ftf32 "TF32" fixed tf32}@@ -480,6 +481,7 @@ e5m2 { set state(selection) fe5m2 } fp4 { set state(selection) ffp4 } fp4e0m3 { set state(selection) ffp4e0m3 }+ e8m0 { set state(selection) fe8m0 } default { if {[regexp {^(\d+)\+(\d+)$} $v _ e s]} { set state(selection) fcs
README.md view
@@ -70,6 +70,7 @@ -fe4m3 FP8, alternate (no infinities) 4 4 -ffp4 FP4 (E2M1) 2 2 -ffp4e0m3 FP4 (E0M3), sign-magnitude 0 3+-fe8m0 E8M0 (MX scale), exponent-only 8 0 -fa+b Arbitrary IEEE-754 float a b ``` @@ -79,6 +80,13 @@ 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. +E8M0 is the odd one out in the other direction: it is the shared scale of the OCP+Microscaling (MX) formats, and is *all* exponent. With no sign bit and no+significand, every value it holds is a power of two, from 2^-127 to 2^127. It has+no zero and no subnormals -- an all-zero encoding means 2^-127, not zero -- and no+infinities; `0xFF` is its one and only NaN. Negative inputs are rejected, and+values outside its range saturate to the nearest end-point.+ 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.@@ -228,6 +236,47 @@ Note: Conversion from "-5" was exact. No rounding happened. ``` +### Example: Decode an E8M0 MX scale+```+$ crackNum -fe8m0 0xFE+Satisfiable. Model:+ DECODED = 1.7014118346046923e38 :: E8M0+ 76543210+ ---E8---+ Binary layout: 11111110+ Hex layout: FE+ Precision: 8 exponent bits, no significand+ Sign: Positive (always)+ Exponent: 127 (Stored: 254, Bias: 127)+ Classification: FP_NORMAL+ Binary: 0b1p+127+ Octal: 0o2p+126+ Decimal: 1.7014118346046923e38+ Hex: 0x8p+124+```++### Example: Encode an E8M0 MX scale+Only powers of two are representable, so everything else rounds according to `-r`:+```+$ crackNum -fe8m0 -- 10+Satisfiable. Model:+ ENCODED = 8.0 :: E8M0+ 76543210+ ---E8---+ Binary layout: 10000010+ Hex layout: 82+ Precision: 8 exponent bits, no significand+ Sign: Positive (always)+ Exponent: 3 (Stored: 130, Bias: 127)+ Classification: FP_NORMAL+ Binary: 0b1p+3+ Octal: 0o1p+3+ Decimal: 8.0+ Hex: 0x8+ Rounding mode: RNE: Round nearest ties to even.+ Note: Original value of 10.0 was rounded to 8.0.+```+ ### Example: Encode a TensorFloat-32 number ``` $ crackNum -ftf32 2.5@@ -414,6 +463,7 @@ e4m3: FP8 format (Alternate) ( 4 + 4) fp4: FP4 format (E2M1) ( 2 + 2) fp4e0m3: FP4 format (E0M3) ( 0 + 3)+ e8m0: FP8 format (MX scale) ( 8 + 0) Examples: Encoding:@@ -429,6 +479,7 @@ 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 -fe8m0 2.5 -- encode as an E8M0 MX scale (power of two) crackNum -fsp 0x3.2p5 -- encode as single-precision from hex-float Decoding:@@ -441,6 +492,7 @@ 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 -fe8m0 0x7F -- decode as an E8M0 MX scale (power of two) crackNum -l4 -fhp 64\'hbdffaaffdc71fc60 -- decode as half-precision float over 4 lanes using verilog notation GUI:@@ -458,6 +510,10 @@ - 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.+ - E8M0 (MX scale) is all exponent: no sign bit and no significand at all,+ 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. - 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.27+Version : 3.28 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. .
crackNum.vim view
@@ -39,7 +39,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"+ \ , "e5m2", "e4m3", "fp4", "fp4e0m3", "e8m0" \ ] " The formats the executable reports, as -f flags. Asking it keeps this list from
src/CrackNum/Main.hs view
@@ -66,6 +66,7 @@ | 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+ | E8M0 -- OCP MX scale format; no sign and no significand at all deriving (Show, Eq) -- | How many bits does this float occupy@@ -77,6 +78,7 @@ fpSize E4M3 = 8 fpSize FP4 = 4 fpSize FP4E0M3 = 4+fpSize E8M0 = 8 kSize :: NKind -> Int kSize (SInt i) = i@@ -179,6 +181,7 @@ , ("e4m3", "FP8 format (Alternate)", "( 4 + 4)", True ) , ("fp4", "FP4 format (E2M1)", "( 2 + 2)", True ) , ("fp4e0m3", "FP4 format (E0M3)", "( 0 + 3)", True )+ , ("e8m0", "FP8 format (MX scale)", "( 8 + 0)", True ) ] -- | The formats that can actually be named, i.e., everything but the arbitrary a+b@@ -206,6 +209,7 @@ getFP "e4m3" = Floating E4M3 getFP "fp4" = Floating FP4 getFP "fp4e0m3" = Floating FP4E0M3+getFP "e8m0" = Floating E8M0 getFP ab = case span isDigit ab of (eb@(_:_), '+':r) -> case span isDigit r of (sp@(_:_), "") -> mkEBSB (read eb) (read sp)@@ -289,6 +293,7 @@ , " " ++ 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 ++ " -fe8m0 2.5 -- encode as an E8M0 MX scale (power of two)" , " " ++ pn ++ " -fsp 0x3.2p5 -- encode as single-precision from hex-float" , "" , " Decoding:"@@ -301,6 +306,7 @@ , " " ++ 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 ++ " -fe8m0 0x7F -- decode as an E8M0 MX scale (power of two)" , " " ++ pn ++ " -l4 -fhp 64\\'hbdffaaffdc71fc60 -- decode as half-precision float over 4 lanes using verilog notation" , "" , " GUI:"@@ -318,6 +324,10 @@ , " - 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."+ , " - E8M0 (MX scale) is all exponent: no sign bit and no significand at all,"+ , " 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." , " - For decoding:" , " - Use hexadecimal (0x) binary (0b), or N'h (verilog) notation as input." , " Input must have one of these prefixes."@@ -673,6 +683,7 @@ E4M3 -> de4m3 config allBits FP4 -> dFP4 config allBits FP4E0M3 -> decodeFP4E0M3 allBits+ E8M0 -> decodeE8M0 debug allBits dFloat :: [SBool] -> ConstraintSet dFloat bs = do x <- sFloat "DECODED"@@ -757,8 +768,6 @@ modifiedResult = SBV.crack debug (literal sval :: SDouble) - isClassification = ("Classification:" `isInfixOf`)- fixVal l = case [tag | tag <- ["ENCODED", "DECODED"], tag `isInfixOf` l] of tag : _ -> " " ++ tag ++ " = " ++ show sval ++ " :: " ++ show fmt [] -> l@@ -767,6 +776,19 @@ mapM_ (putStrLn . fixVal) $ takeWhile (not . isClassification) (lines (show ieeeResult)) mapM_ putStrLn $ dropWhile (not . isClassification) (lines modifiedResult) +-- | The line SBV's cracker prints the classification on. Everything from here down+-- describes the value itself rather than its layout, which is the split the formats+-- that deviate from IEEE need: they take the layout from the look-alike (or lay it+-- out by hand) and the rest from the value they actually mean.+isClassification :: String -> Bool+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+-- 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`))+ -- | The canonical quiet-NaN pattern for a float with @eb@ exponent bits and @sb@ -- significand bits (including the implicit one): sign 0, all-ones exponent, and only -- the leading stored significand bit set. For single-precision this is 0x7FC00000.@@ -885,6 +907,8 @@ ef FP4E0M3 _ = encodeFP4E0M3 rm inp + ef E8M0 _ = encodeE8M0 debug 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@@ -959,12 +983,9 @@ fixEncoded :: SatResult -> String fixEncoded = retype E4M3 - onEach f = intercalate "\n" . concatMap f . lines- -- nan representation is unique for E4M3- fixNaN :: String -> [String]- fixNaN s | "Representation for NaN's is not unique" `isInfixOf` s = []- | True = [s]+ fixNaN :: String -> String+ fixNaN = intercalate "\n" . dropNaNUniquenessNote . lines getNaN = satWith config{crackNumSurfaceVals = [("ENCODED", 0x7F)]} $ do x :: SFloatingPoint 4 4 <- sFloatingPoint "ENCODED"@@ -974,9 +995,9 @@ analyze v -- NaN has two representations, with surface value S.1111.111; we use 0x7F for simplicity | isNaN v- = getNaN >>= putStrLn . onEach fixNaN . fixEncoded+ = getNaN >>= putStrLn . fixNaN . fixEncoded | isInfinite v- = do getNaN >>= putStrLn . onEach fixNaN . fixEncoded+ = do getNaN >>= putStrLn . fixNaN . fixEncoded putStrLn " Note: The input value was infinite, which is not representable in E4M3." | True = range v@@ -1023,7 +1044,7 @@ range v | v < -448 || v > 448 -- Out-of-bounds becomes NaN- = do getNaN >>= putStrLn . onEach fixNaN . fixEncoded+ = do getNaN >>= putStrLn . fixNaN . fixEncoded putStrLn $ " Note: The input value " ++ show v ++ " is out of bounds, and hence becomes NaN" putStrLn " The representable range is [-448, 448]" @@ -1272,3 +1293,139 @@ = putStrLn $ " Note: Conversion from " ++ show inp ++ " was exact. No rounding happened." | True = putStrLn $ " Note: Original value of " ++ show v ++ " was rounded to " ++ t ++ "."++-- | E8M0 is the OCP Microscaling (MX) scale format: the value that scales a block of+-- MXFP8/MXFP6/MXFP4 elements. All 8 bits are exponent -- there is no sign bit and no+-- significand at all -- so every value is the power of two 2^(E-127), and 0xFF is its+-- one and only NaN. Having no significand, it has no zero and no subnormals either:+-- with nothing for the E=0 encoding to mean, it simply denotes 2^-127.+e8m0Bias :: Int+e8m0Bias = 127++-- | The value a stored E8M0 exponent denotes. All 254 finite values are exactly+-- representable as a Double, since 2^(+/-127) is nowhere near its range limits; note+-- that 'encodeFloat' builds them exactly, which @2 **@ would not be guaranteed to do.+e8m0Value :: Int -> Double+e8m0Value 255 = 0/0+e8m0Value e = encodeFloat 1 (e - e8m0Bias)++-- | Lay out an E8M0 value. With no sign and no significand there is no IEEE look-alike+-- to lean on, so the layout is built by hand, following the shape crackNum prints for+-- the other formats. Everything from the classification down describes the value rather+-- than its layout, so that part comes from cracking the equivalent Double -- the same+-- division of labor 'modOut' uses for the E4M3 and FP4 deviations.+e8m0Layout :: Bool -> String -> Int -> [String]+e8m0Layout debug tag stored =+ [ "Satisfiable. Model:"+ , " " ++ tag ++ " = " ++ show v ++ " :: " ++ show E8M0+ , " 76543210"+ , " ---E8---"+ , " Binary layout: " ++ pad 8 (inBase 2 stored)+ , " Hex layout: " ++ map toUpper (pad 2 (inBase 16 stored))+ , " Precision: 8 exponent bits, no significand"+ -- NB. There is no sign bit: bit 7 is the exponent's MSB. We print the line anyway,+ -- so the block keeps the same shape as every other format, but say outright that+ -- it can never read anything else.+ , " Sign: Positive (always)"+ , " Exponent: " ++ show (stored - e8m0Bias) ++ " (Stored: " ++ show stored ++ ", Bias: " ++ show e8m0Bias ++ ")"+ ]+ ++ dropNaNUniquenessNote (dropWhile (not . isClassification) (lines (SBV.crack debug (literal v :: SDouble))))+ where v = e8m0Value stored++ inBase b x = showIntAtBase b intToDigit x ""++ pad n x = replicate (n - length x) '0' ++ x++-- | Decoding E8M0: the entire byte is the stored exponent.+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++-- | Encoding E8M0. The representable values are the powers of two from 2^-127 to 2^127,+-- plus NaN, so we round the exponent by hand. Rounding is always between two adjacent+-- powers of two; we split them at the arithmetic midpoint (1.5 * 2^e, not the geometric+-- one) and break RNE ties toward the even /stored/ exponent. Both follow 'encodeFP4',+-- which ties on the parity of the encoding index rather than of the value's exponent.+encodeE8M0 :: Bool -> RM -> String -> IO ()+encodeE8M0 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 smallest, largest :: Double+ smallest = e8m0Value 0+ largest = e8m0Value 254++ analyze :: Double -> IO ()+ analyze v+ -- NaN is representable, and uniquely so.+ | isNaN v+ = out 255+ -- 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.+ | v < 0 || isNegativeZero v+ = die [ "E8M0 has no representation for negative values."+ , "The representable range is [2^-127, 2^127], plus NaN."+ ]+ -- Infinity is the limiting overflow, so it saturates along with anything else+ -- that is too large.+ | isInfinite v || v > largest+ = out 254+ -- The bottom of the range is a hard cliff: there is no zero and no subnormal+ -- below 2^-127, so zero and everything under it saturates up to it.+ | v < smallest+ = out 0+ | True+ = out (e8m0Bias + roundExp v)+ where out stored = do putStr $ unlines $ e8m0Layout debug "ENCODED" stored+ trailer v stored++ -- The exponent we land on, for a v already known to be in range. 'exponent'+ -- returns the e with v = m * 2^e and 0.5 <= m < 1, so lo is the exponent whose+ -- power of two sits at or just below v.+ roundExp :: Double -> Int+ roundExp v+ | v == twoTo lo -- Exactly representable+ = lo+ | True+ = case rm of+ RTZ -> lo -- Every value is positive, so RTZ and RTN necessarily agree+ RTN -> lo+ RTP -> hi+ RNE -> nearest (if even (lo + e8m0Bias) then lo else hi)+ RNA -> nearest hi+ where lo = exponent v - 1+ hi = lo + 1++ twoTo :: Int -> Double+ twoTo = encodeFloat 1++ -- Ties are broken by the given choice; note that comparing against the sum+ -- avoids any rounding of its own, since 2*v and 3*2^lo are both exact here.+ nearest tie = case compare (2 * v) (twoTo lo + twoTo hi) of+ LT -> lo+ GT -> hi+ EQ -> tie++ trailer :: Double -> Int -> IO ()+ trailer v stored = do putStrLn $ " Rounding mode: " ++ show rm+ note+ where t = e8m0Value stored++ note+ | isNaN v+ = exact+ | 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."
src/CrackNum/TestSuite.hs view
@@ -145,6 +145,31 @@ | rm <- ["RNE", "RNA", "RTP", "RTN", "RTZ"] , i :: Double <- [0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 8] ]+ , testGroup "EncodeE8M0" [+ gold "encodeE8M0_nan" "-fe8m0 nan" -- Representable, and uniquely so+ , gold "encodeE8M0_+inf" "-fe8m0 inf" -- The limiting overflow; saturates+ , gold "encodeE8M0_-inf" "-fe8m0 -- -inf" -- Negative: rejected, not saturated+ , gold "encodeE8M0_neg" "-fe8m0 -- -5"+ , gold "encodeE8M0_zero1" "-fe8m0 -- 0" -- No zero in the format; saturates up to 2^-127+ , gold "encodeE8M0_zero2" "-fe8m0 -- -0" -- But a negative zero is still negative+ , gold "encodeE8M0_one" "-fe8m0 -- 1"+ , gold "encodeE8M0_exact" "-fe8m0 -- 0.25"+ , gold "encodeE8M0_min" "-fe8m0 -- 0x1p-127"+ , gold "encodeE8M0_max" "-fe8m0 -- 0x1p+127"+ , gold "encodeE8M0_oob1" "-fe8m0 -- 1e40" -- Saturates+ , gold "encodeE8M0_oob2" "-fe8m0 -- 1e-40"+ , gold "encodeE8M0_hex" "-fe8m0 -- 0x1.8p1"+ ]+ -- Every value that sits exactly half-way between two representable powers of+ -- two, over all rounding modes. Note that 1.5 and 3 are what pin the RNE tie+ -- rule down: they straddle stored exponents of opposite parity, so reading+ -- "ties to even" as the unbiased exponent rather than the stored one would+ -- send them the other way. All values are positive; negatives are rejected.+ , testGroup "EncodeE8M0Ties" [+ gold ("encodeE8M0_tie_" ++ rm ++ "_+" ++ show i) ("-fe8m0 -r" ++ rm ++ " -- " ++ show i)+ | rm <- ["RNE", "RNA", "RTP", "RTN", "RTZ"]+ , i :: Double <- [0.75, 1.5, 3, 6]+ ] , testGroup "Decode" [ gold "decode0" "-i4 0b0110" , gold "decode1" "-w4 0xE"@@ -201,6 +226,19 @@ | s <- ["0", "1"] , m <- ["000", "001", "010", "011", "100", "101", "110", "111"] , let bits = s ++ m+ ]+ -- E8M0 is all exponent, so decoding is a table lookup; a spread of patterns+ -- covering both ends, the unit value, and the sole NaN is enough.+ , testGroup "DecodeE8M0" [+ gold ("decodeE8M0_" ++ bits) ("-fe8m0 0x" ++ bits)+ | bits <- [ "00" -- Smallest: 2^-127. Not zero: the format has none+ , "01"+ , "7F" -- 1.0+ , "80"+ , "FD"+ , "FE" -- Largest: 2^127+ , "FF" -- NaN, and the only one+ ] ] , testGroup "Bad" [ gold "badInvocation0" "-f3+4 0b01"