diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@
 
 ```bash
 cabal update
-cabal install --overwrite-policy=always phino-0.0.113
+cabal install --overwrite-policy=always phino-0.0.114
 phino --version
 ```
 
@@ -520,55 +520,55 @@
 === parse/phi ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      1843412.235 μs
-  avg:        184341.224 μs
-  min:        170612.548 μs
-  max:        215203.225 μs
-  std dev:    16818.121 μs
+  total:      1543475.232 μs
+  avg:        154347.523 μs
+  min:        142171.737 μs
+  max:        181971.002 μs
+  std dev:    14975.901 μs
 === parse/xmir ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      7584564.832 μs
-  avg:        758456.483 μs
-  min:        674321.184 μs
-  max:        885832.048 μs
-  std dev:    58868.246 μs
+  total:      7819406.638 μs
+  avg:        781940.664 μs
+  min:        699138.670 μs
+  max:        933188.047 μs
+  std dev:    65608.137 μs
 === rewrite/normalize ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      530792.056 μs
-  avg:        53079.206 μs
-  min:        52059.402 μs
-  max:        55045.049 μs
-  std dev:    788.817 μs
+  total:      651966.123 μs
+  avg:        65196.612 μs
+  min:        61413.741 μs
+  max:        76515.744 μs
+  std dev:    4139.047 μs
 === print/sweet/multiline ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      3859233.707 μs
-  avg:        385923.371 μs
-  min:        366896.885 μs
-  max:        399481.094 μs
-  std dev:    8247.212 μs
+  total:      4663282.066 μs
+  avg:        466328.207 μs
+  min:        449248.604 μs
+  max:        488747.559 μs
+  std dev:    11022.030 μs
 === print/sweet/flat ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      3805981.640 μs
-  avg:        380598.164 μs
-  min:        363776.755 μs
-  max:        397781.223 μs
-  std dev:    8200.622 μs
+  total:      4595372.700 μs
+  avg:        459537.270 μs
+  min:        419405.034 μs
+  max:        505827.470 μs
+  std dev:    27196.223 μs
 === print/salty/multiline ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      14154416.526 μs
-  avg:        1415441.653 μs
-  min:        1380466.817 μs
-  max:        1440553.138 μs
-  std dev:    15889.438 μs
+  total:      14529614.517 μs
+  avg:        1452961.452 μs
+  min:        1402771.195 μs
+  max:        1489996.347 μs
+  std dev:    26769.638 μs
 ```
 
 The results were calculated in [this GHA job][benchmark-gha]
-on 2026-09-02 at 14:36,
+on 2026-09-03 at 07:49,
 on Linux with 4 CPUs.
 
 <!-- benchmark_end -->
@@ -617,4 +617,4 @@
 [jna]: https://github.com/java-native-access/jna
 [jna-native]: https://github.com/java-native-access/jna/blob/master/src/com/sun/jna/Native.java
 [jeo]: https://github.com/objectionary/jeo-maven-plugin
-[benchmark-gha]: https://github.com/objectionary/phino/actions/runs/33642610207
+[benchmark-gha]: https://github.com/objectionary/phino/actions/runs/33729718905
diff --git a/phino.cabal b/phino.cabal
--- a/phino.cabal
+++ b/phino.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: phino
-version: 0.0.114
+version: 0.0.115
 license: MIT
 synopsis: Command-Line Manipulator of 𝜑-Calculus Expressions
 description: Please see the README on GitHub at <https://github.com/objectionary/phino#readme>
diff --git a/src/Bytes.hs b/src/Bytes.hs
--- a/src/Bytes.hs
+++ b/src/Bytes.hs
@@ -12,6 +12,7 @@
   , strToBts
   , bytesToBts
   , btsToStr
+  , unescapeStr
   , btsToNum
   , btsToUnescapedStr
   , btsAnd
@@ -22,6 +23,12 @@
   , btsSize
   , btsSlice
   , btsShift
+  , nonFinites
+  , nonFiniteName
+  , nonFiniteBts
+  , btsToNonFinite
+  , nonFiniteOf
+  , NonFinite (..)
   )
 where
 
@@ -33,6 +40,7 @@
 import Data.ByteString.Lazy (unpack)
 import qualified Data.ByteString.Lazy.UTF8 as U
 import Data.Char (chr, isDigit, isPrint, ord)
+import Data.List (find)
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as T
 import Data.Word (Word64, Word8)
@@ -135,6 +143,58 @@
 numToBts :: Double -> Bytes
 numToBts num = word8ToBytes (unpack (toLazyByteString (word64BE (doubleToWord num))))
 
+-- The three IEEE-754 doubles that are not finite numbers. None of them has a
+-- numeric literal to be written with, so the printer spells each one as a
+-- dispatch off the root — 'Φ.nan', 'Φ.pinf', 'Φ.ninf' — and the parser reads
+-- those names back into the very bytes they stand for (see #1065)
+data NonFinite = NfNan | NfPinf | NfNinf
+  deriving (Eq, Show)
+
+-- All the non-finite doubles, in the order they are documented in
+nonFinites :: [NonFinite]
+nonFinites = [NfNan, NfPinf, NfNinf]
+
+-- The attribute name the value is dispatched on
+-- >>> nonFiniteName NfPinf
+-- "pinf"
+nonFiniteName :: NonFinite -> T.Text
+nonFiniteName NfNan = "nan"
+nonFiniteName NfPinf = "pinf"
+nonFiniteName NfNinf = "ninf"
+
+-- The canonical byte form of a non-finite double. The patterns are spelled out
+-- instead of being derived from '0 / 0' and '1 / 0' because the sign bit and
+-- the payload of a computed NaN are platform-dependent, while the printer and
+-- the parser have to agree on one exact pattern
+-- >>> nonFiniteBts NfNan
+-- BtMany ["7F","F8","00","00","00","00","00","00"]
+nonFiniteBts :: NonFinite -> Bytes
+nonFiniteBts NfNan = BtMany ["7F", "F8", "00", "00", "00", "00", "00", "00"]
+nonFiniteBts NfPinf = BtMany ["7F", "F0", "00", "00", "00", "00", "00", "00"]
+nonFiniteBts NfNinf = BtMany ["FF", "F0", "00", "00", "00", "00", "00", "00"]
+
+-- Which non-finite double the given bytes encode, if they encode one at all.
+-- Only the three canonical patterns qualify: a NaN carrying a payload, or the
+-- negative quiet NaN, has no name of its own and keeps its byte form, so that
+-- printing never drops a bit
+-- >>> btsToNonFinite (BtMany ["FF", "F0", "00", "00", "00", "00", "00", "00"])
+-- Just NfNinf
+-- >>> btsToNonFinite (BtMany ["40", "45", "00", "00", "00", "00", "00", "00"])
+-- Nothing
+-- >>> btsToNonFinite (BtMany ["7F", "F8", "00", "00", "00", "00", "00", "01"])
+-- Nothing
+btsToNonFinite :: Bytes -> Maybe NonFinite
+btsToNonFinite (BtMeta _) = Nothing
+btsToNonFinite bts = find (btsEqual bts . nonFiniteBts) nonFinites
+
+-- The non-finite double the given name stands for, if it names one at all
+-- >>> nonFiniteOf "ninf"
+-- Just NfNinf
+-- >>> nonFiniteOf "number"
+-- Nothing
+nonFiniteOf :: T.Text -> Maybe NonFinite
+nonFiniteOf name = find ((== name) . nonFiniteName) nonFinites
+
 -- >>> strToBts "hello"
 -- BtMany ["68","65","6C","6C","6F"]
 -- >>> strToBts "world"
@@ -197,6 +257,39 @@
         escapeChar c
           | isPrint c && c /= '\\' && c /= '"' = [c]
           | otherwise = printf "\\x%02x" (ord c)
+
+-- The inverse of the escaping that 'btsToStr' applies, so that a sweet string
+-- literal can be turned back into the very bytes it was printed from. A
+-- backslash that starts no escape 'btsToStr' can produce is kept as it stands,
+-- together with the character behind it
+-- >>> unescapeStr "hello"
+-- "hello"
+-- >>> unescapeStr "h\\\""
+-- "h\""
+-- >>> unescapeStr "e\\ne"
+-- "e\ne"
+-- >>> unescapeStr "\\\\"
+-- "\\"
+-- >>> unescapeStr "\\t"
+-- "\t"
+-- >>> unescapeStr "\\x01"
+-- "\SOH"
+unescapeStr :: String -> String
+unescapeStr = go
+  where
+    go :: String -> String
+    go "" = ""
+    go ('\\' : 'x' : high : low : rest)
+      | Just code <- hexPair high low = chr code : go rest
+    go ('\\' : escaped : rest)
+      | Just unescaped <- lookup escaped escapes = unescaped : go rest
+    go (char : rest) = char : go rest
+    hexPair :: Char -> Char -> Maybe Int
+    hexPair high low = case readHex [high, low] of
+      [(code, "")] -> Just code
+      _ -> Nothing
+    escapes :: [(Char, Char)]
+    escapes = [('"', '"'), ('\\', '\\'), ('n', '\n'), ('t', '\t')]
 
 -- >>> btsToUnescapedStr (BtMany ["01", "02"])
 -- "\SOH\STX"
diff --git a/src/CST.hs b/src/CST.hs
--- a/src/CST.hs
+++ b/src/CST.hs
@@ -11,7 +11,8 @@
 module CST where
 
 import AST
-import Bytes (btsToNum, btsToStr)
+import Bytes (NonFinite, btsToNonFinite, btsToNum, btsToStr)
+import Data.Maybe (isJust)
 import qualified Data.Text as T
 import qualified Yaml as Y
 
@@ -174,6 +175,7 @@
   | EX_APPLICATION {expr :: EXPRESSION, space :: SPACE, eol :: EOL, tab :: TAB, argument :: APP_ARGUMENT, eol' :: EOL, tab' :: TAB, indent :: Int} -- e(...)
   | EX_STRING {str :: String, tab :: TAB, rhos :: [Argument]}
   | EX_NUMBER {num :: Either Int Double, tab :: TAB, rhos :: [Argument]}
+  | EX_NONFINITE {global :: GLOBAL, nonfinite :: NonFinite, tab :: TAB, rhos :: [Argument]} -- Φ.nan, Φ.pinf and Φ.ninf (see #1065)
   | EX_META {meta :: META}
   | EX_PHI_MEET {prefix :: Maybe String, idx :: Int, expr :: EXPRESSION}
   | EX_PHI_AGAIN {prefix :: Maybe String, idx :: Int, expr :: EXPRESSION}
@@ -260,14 +262,17 @@
 expressionToCSTFrom :: Int -> Expression -> EXPRESSION
 expressionToCSTFrom tabs expr = toCST expr (tabs, EOL)
 
--- A number can be rendered with the sweet numeric literal only when it is
--- finite. NaN and the infinities have no such literal — and the bare `show`
--- tokens (`NaN`, `Infinity`, `-Infinity`) would collide with object/function
--- names — so they are kept in their byte form instead.
+-- A number can be rendered in sweet form when it is either finite, and so has
+-- a numeric literal, or one of the three canonical non-finite doubles, which
+-- get the root dispatches `Φ.nan`, `Φ.pinf` and `Φ.ninf` instead (the bare
+-- `show` tokens `NaN`, `Infinity` and `-Infinity` would collide with
+-- object/function names, hence the dispatch — see #1065). Any other non-finite
+-- pattern, such as a NaN carrying a payload, is kept in its byte form so that
+-- no bit of it is lost.
 sweetNumber :: Bytes -> Bool
 sweetNumber bts = case btsToNum bts of
-  Right d -> not (isNaN d || isInfinite d)
-  Left _ -> True
+  Right dbl | isNaN dbl || isInfinite dbl -> isJust (btsToNonFinite bts)
+  _ -> True
 
 -- Whether a data object may be collapsed into its sweet literal form.
 sweetCollapsible :: Expression -> Bool
@@ -341,10 +346,11 @@
       withoutLastVoidRho [BiVoid AtRho] = []
       withoutLastVoidRho (bd : bds') = bd : withoutLastVoidRho bds'
   toCST (DataString bts) (tabs, _) = EX_STRING (btsToStr bts) (TAB tabs) []
-  -- NaN and the infinities have no sweet numeric literal (and printing the bare
-  -- `show` tokens would collide with object/function names), so they are left in
-  -- their byte form `Φ.number(Φ.bytes(⟦ Δ ⤍ … ⟧))` by falling through to the
-  -- generic application clause below.
+  -- The three canonical non-finite doubles have no sweet numeric literal, so
+  -- they become the root dispatches `Φ.nan`, `Φ.pinf` and `Φ.ninf`. Any other
+  -- non-finite pattern is left in its byte form `Φ.number(Φ.bytes(⟦ Δ ⤍ … ⟧))`
+  -- by falling through to the generic application clause below.
+  toCST (DataNumber bts) (tabs, _) | Just nonfinite <- btsToNonFinite bts = EX_NONFINITE Φ nonfinite (TAB tabs) []
   toCST (DataNumber bts) (tabs, _) | sweetNumber bts = EX_NUMBER (btsToNum bts) (TAB tabs) []
   toCST (ExDispatch ExXi attr) ctx = EX_ATTR (toCST attr ctx)
   toCST (ExDispatch expr attr) ctx = EX_DISPATCH (toCST expr ctx) NO_SPACE (toCST attr ctx)
@@ -409,9 +415,11 @@
         | otherwise = (bds, [])
       withoutRhosInPrimitives _ bds = (bds, [])
       applicationToPrimitive :: Expression -> Int -> [Argument] -> EXPRESSION
-      applicationToPrimitive (DataNumber bts) tabs = EX_NUMBER (btsToNum bts) (TAB tabs)
-      applicationToPrimitive (DataString bts) tabs = EX_STRING (btsToStr bts) (TAB tabs)
-      applicationToPrimitive _ _ = error "applicationToPrimitive expects DataNumber or DataString"
+      applicationToPrimitive (DataNumber bts) tabs rhos = case btsToNonFinite bts of
+        Just nonfinite -> EX_NONFINITE Φ nonfinite (TAB tabs) rhos
+        Nothing -> EX_NUMBER (btsToNum bts) (TAB tabs) rhos
+      applicationToPrimitive (DataString bts) tabs rhos = EX_STRING (btsToStr bts) (TAB tabs) rhos
+      applicationToPrimitive _ _ _ = error "applicationToPrimitive expects DataNumber or DataString"
       -- Here we unroll nested application sequence into flat structure
       -- The returned tuple consists of:
       -- 1. deepest start expression
diff --git a/src/Encoding.hs b/src/Encoding.hs
--- a/src/Encoding.hs
+++ b/src/Encoding.hs
@@ -25,6 +25,7 @@
   toASCII EX_FORMATION{..} = EX_FORMATION LSB' eol tab (toASCII binding) eol' tab' RSB'
   toASCII EX_DISPATCH{..} = EX_DISPATCH (toASCII expr) space (toASCII attr)
   toASCII EX_APPLICATION{..} = EX_APPLICATION (toASCII expr) space eol tab (toASCII argument) eol' tab' indent
+  toASCII EX_NONFINITE{..} = EX_NONFINITE Q nonfinite tab rhos
   toASCII EX_META{meta = META{hd = N, ..}} = EX_META (META EXCL N' rest)
   toASCII EX_META{meta = META{hd = K, ..}} = EX_META (META EXCL K' rest)
   toASCII EX_META{..} = EX_META (META EXCL E' (rest meta))
diff --git a/src/LaTeX.hs b/src/LaTeX.hs
--- a/src/LaTeX.hs
+++ b/src/LaTeX.hs
@@ -25,6 +25,7 @@
   ) where
 
 import AST
+import Bytes (nonFiniteName)
 import CST
 import Canonizer (canonize, canonizeExpr)
 import Data.List (intercalate, nub)
@@ -267,6 +268,9 @@
   toLaTeX EX_PHI_AGAIN{..} = EX_PHI_AGAIN prefix idx (toLaTeX expr)
   toLaTeX EX_META{..} = EX_META (toLaTeX meta)
   toLaTeX EX_XI{} = EX_XI XI'
+  -- A non-finite double is printed as a dispatch off the root, so it becomes
+  -- one here too, with its name piped the way any other label is (see #1065)
+  toLaTeX EX_NONFINITE{..} = EX_DISPATCH (EX_GLOBAL global) SPACE (toLaTeX (AT_LABEL (nonFiniteName nonfinite)))
   toLaTeX EX_BYTES{..} = EX_BYTES (toLaTeX bytes)
   toLaTeX expr = expr
 
diff --git a/src/Parser.hs b/src/Parser.hs
--- a/src/Parser.hs
+++ b/src/Parser.hs
@@ -22,7 +22,7 @@
 where
 
 import AST
-import Bytes (numToBts, strToBts)
+import Bytes (nonFiniteBts, nonFiniteOf, numToBts, strToBts)
 import Control.Exception (Exception)
 import Control.Monad (guard)
 import Data.Char (isAsciiLower, isDigit)
@@ -193,6 +193,26 @@
         )
     )
 
+-- An expression head that starts with the root: either one of the three
+-- non-finite doubles named off it — `Φ.nan`, `Φ.pinf` and `Φ.ninf`, read back
+-- into the very 'DataNumber' the sweet printer collapsed, which keeps
+-- print-then-parse idempotent (see #1065) — or the root itself. The label after
+-- the root is parsed once, here, so an ordinary dispatch such as `Φ.number`
+-- costs no more than it did before the three names existed; an attribute the
+-- label parser rejects (ρ, φ, a meta) is left to 'exTail', as is any further
+-- dispatch or application
+root :: Parser Expression
+root = do
+  _ <- global
+  option ExRoot (try labelled)
+  where
+    labelled :: Parser Expression
+    labelled = do
+      _ <- symbol "."
+      named <$> label'
+    named :: T.Text -> Expression
+    named name = maybe (ExDispatch ExRoot (AtLabel name)) (DataNumber . nonFiniteBts) (nonFiniteOf name)
+
 quotedStr :: Parser String
 quotedStr = char '"' >> manyTill (choice [escapedChar, noneOf ['\\', '"']]) (char '"')
   where
@@ -384,7 +404,7 @@
 -- head part of expression
 -- 1. formation
 -- 2. this
--- 3. global
+-- 3. global, or an attribute or non-finite double named off it
 -- 4. termination
 -- 5. meta expression
 -- 6. full attribute -> sugar for $.attr
@@ -397,9 +417,7 @@
     , do
         _ <- choice [symbol "$", symbol "ξ"]
         return ExXi
-    , do
-        _ <- global
-        return ExRoot
+    , root
     , do
         _ <- choice [symbol "T", symbol "⊥"]
         return ExTermination
diff --git a/src/Render.hs b/src/Render.hs
--- a/src/Render.hs
+++ b/src/Render.hs
@@ -8,6 +8,7 @@
 
 module Render where
 
+import Bytes (nonFiniteName)
 import CST
 import Data.Text (Text)
 import qualified Data.Text as T
@@ -204,6 +205,7 @@
   render EX_APPLICATION{..} = render expr <> render space <> "(" <> render eol <> render tab <> render argument <> render eol' <> render tab' <> ")"
   render EX_STRING{..} = "\"" <> render str <> "\""
   render EX_NUMBER{..} = either (T.pack . show) (T.pack . show) num
+  render EX_NONFINITE{..} = render global <> "." <> nonFiniteName nonfinite
   render EX_META{..} = render meta
   render EX_PHI_MEET{..} = "\\phinoMeet{" <> maybe "" (\p -> T.pack p <> ":") prefix <> render idx <> "}{ " <> render expr <> " }"
   render EX_PHI_AGAIN{..} = "\\phinoAgain{" <> maybe "" (\p -> T.pack p <> ":") prefix <> render idx <> "}"
diff --git a/src/Sugar.hs b/src/Sugar.hs
--- a/src/Sugar.hs
+++ b/src/Sugar.hs
@@ -10,7 +10,7 @@
 module Sugar (toSalty, withSugarType, withoutRho, SugarType (..), ToSalty) where
 
 import AST
-import Bytes (numToBts, strToBts)
+import Bytes (nonFiniteBts, numToBts, strToBts, unescapeStr)
 import CST
 import Misc (toDouble)
 
@@ -127,6 +127,7 @@
 --  |----------------------------|-----------------------------------------------------|
 --  | a1 -> a2                   | a1 ↦ $.a2                                           |
 --  | a -> 42                    | Q.number(Q.bytes([[ D> 40-45-00-00-00-00-00-00 ]])) |
+--  | a -> Q.nan                 | Q.number(Q.bytes([[ D> 7F-F8-00-00-00-00-00-00 ]])) |
 --  | a -> "Hey"                 | Q.number(Q.bytes([[ D> 48-65-79 ]]))                |
 --  | [[ B ]]                    | [[ B, ^ -> ? ]], if rho is absent in 'B'            |
 --  | a1(a2, a3, ...) -> [[ B ]] | a1 -> [[ a2 -> ?, a3 -> ?, ..., B ]]                |
@@ -176,11 +177,18 @@
       (toCST (ExFormation [BiDelta (numToBts (either toDouble id num))]) (indent + 2, EOL))
       tab
       rhos
+  toSalty EX_NONFINITE{nonfinite, tab = tab@TAB{..}, rhos} =
+    saltifyPrimitive
+      (toCST (BaseObject "number") (indent + 1, EOL))
+      (toCST (BaseObject "bytes") (indent + 2, EOL))
+      (toCST (ExFormation [BiDelta (nonFiniteBts nonfinite)]) (indent + 2, EOL))
+      tab
+      rhos
   toSalty EX_STRING{str, tab = tab@TAB{..}, rhos} =
     saltifyPrimitive
       (toCST (BaseObject "string") (indent + 1, EOL))
       (toCST (BaseObject "bytes") (indent + 2, EOL))
-      (toCST (ExFormation [BiDelta (strToBts str)]) (indent + 2, EOL))
+      (toCST (ExFormation [BiDelta (strToBts (unescapeStr str))]) (indent + 2, EOL))
       tab
       rhos
   toSalty EX_PHI_MEET{..} = EX_PHI_MEET prefix idx (toSalty expr)
diff --git a/test/BytesSpec.hs b/test/BytesSpec.hs
--- a/test/BytesSpec.hs
+++ b/test/BytesSpec.hs
@@ -7,7 +7,8 @@
 
 import AST
 import Bytes
-  ( btsAnd
+  ( NonFinite (..)
+  , btsAnd
   , btsConcat
   , btsEqual
   , btsNot
@@ -15,15 +16,22 @@
   , btsShift
   , btsSize
   , btsSlice
+  , btsToNonFinite
   , btsToNum
   , btsToStr
   , btsToUnescapedStr
   , bytesToBts
+  , nonFiniteBts
+  , nonFiniteName
+  , nonFiniteOf
+  , nonFinites
   , numToBts
   , strToBts
+  , unescapeStr
   )
 import Control.Exception (evaluate)
 import Control.Monad (forM_)
+import Data.Text qualified as T
 import Test.Hspec (Spec, anyErrorCall, describe, it, shouldBe, shouldSatisfy, shouldThrow)
 
 spec :: Spec
@@ -53,6 +61,42 @@
       ]
       (\(desc, num, predicate) -> it desc (btsToNum (numToBts num) `shouldSatisfy` predicate))
 
+  describe "nonFiniteBts and btsToNonFinite round trip" $
+    forM_
+      nonFinites
+      (\value -> it (T.unpack (nonFiniteName value)) (btsToNonFinite (nonFiniteBts value) `shouldBe` Just value))
+
+  describe "btsToNonFinite tells the named patterns from every other one" $
+    forM_
+      [ ("a finite integral value", BtMany ["40", "45", "00", "00", "00", "00", "00", "00"], Nothing)
+      , ("a NaN carrying a payload", BtMany ["7F", "F8", "00", "00", "00", "00", "00", "01"], Nothing)
+      , ("the negative quiet NaN", BtMany ["FF", "F8", "00", "00", "00", "00", "00", "00"], Nothing)
+      , ("a byte array of the wrong size", BtOne "7F", Nothing)
+      , ("meta bytes", BtMeta "d", Nothing)
+      , ("NaN", BtMany ["7F", "F8", "00", "00", "00", "00", "00", "00"], Just NfNan)
+      , ("positive infinity", BtMany ["7F", "F0", "00", "00", "00", "00", "00", "00"], Just NfPinf)
+      , ("negative infinity", BtMany ["FF", "F0", "00", "00", "00", "00", "00", "00"], Just NfNinf)
+      ]
+      (\(desc, bts, expected) -> it desc (btsToNonFinite bts `shouldBe` expected))
+
+  describe "nonFiniteOf reads back the name the printer gives" $
+    forM_
+      [ ("nan", Just NfNan)
+      , ("pinf", Just NfPinf)
+      , ("ninf", Just NfNinf)
+      , ("number", Nothing)
+      , ("", Nothing)
+      ]
+      (\(name, expected) -> it (T.unpack name) (nonFiniteOf name `shouldBe` expected))
+
+  describe "btsToNum on the named non-finite patterns" $
+    forM_
+      [ ("NaN", NfNan, either (const False) isNaN)
+      , ("positive infinity", NfPinf, either (const False) (\num -> isInfinite num && num > 0))
+      , ("negative infinity", NfNinf, either (const False) (\num -> isInfinite num && num < 0))
+      ]
+      (\(desc, value, predicate) -> it desc (btsToNum (nonFiniteBts value) `shouldSatisfy` predicate))
+
   describe "btsToNum with a byte array that is not 8 bytes long" $
     it "errors out" $
       evaluate (btsToNum (BtMany ["40", "45"])) `shouldThrow` anyErrorCall
@@ -86,6 +130,40 @@
       ]
       ( \(desc, bts, str) ->
           it desc $ btsToStr bts `shouldBe` str
+      )
+
+  describe "unescapeStr" $
+    forM_
+      [ ("empty", "", "")
+      , ("nothing to unescape", "hello", "hello")
+      , ("double quote", "h\\\"", "h\"")
+      , ("backslash", "\\\\", "\\")
+      , ("newline", "e\\ne", "e\ne")
+      , ("tab", "\\t", "\t")
+      , ("hex escape", "\\x01", "\SOH")
+      , ("uppercase hex escape", "\\xFF", "\255")
+      , ("backslash before an escape", "\\\\n", "\\n")
+      , ("unknown escape is kept as it stands", "\\q", "\\q")
+      , ("trailing backslash is kept", "a\\", "a\\")
+      , ("truncated hex escape is kept", "\\x0", "\\x0")
+      ]
+      ( \(desc, escaped, unescaped) ->
+          it desc $ unescapeStr escaped `shouldBe` unescaped
+      )
+
+  describe "btsToStr/unescapeStr round trip" $
+    forM_
+      [ ("empty", BtEmpty)
+      , ("plain word", BtMany ["68", "65", "6C", "6C", "6F"])
+      , ("double quote", BtOne "22")
+      , ("backslash", BtOne "5C")
+      , ("newline", BtOne "0A")
+      , ("tab", BtOne "09")
+      , ("non-printable", BtMany ["01", "02"])
+      , ("text around a newline", BtMany ["65", "0A", "65"])
+      ]
+      ( \(desc, bts) ->
+          it desc $ strToBts (unescapeStr (btsToStr bts)) `shouldBe` bts
       )
 
   describe "btsToUnescapedStr" $
diff --git a/test/CLISpec.hs b/test/CLISpec.hs
--- a/test/CLISpec.hs
+++ b/test/CLISpec.hs
@@ -412,6 +412,10 @@
       withStdin "[[foo ↦ x]]" $
         testCLISucceeded ["rewrite"] ["⟦ foo ↦ ξ.x, ρ ↦ ∅ ⟧"]
 
+    it "keeps the bytes of a string intact while desugaring it" $
+      withStdin "⟦ φ ↦ Φ.string(as-bytes ↦ Φ.bytes(data ↦ ⟦ Δ ⤍ 65-0A-65, ρ ↦ ∅ ⟧)), ρ ↦ ∅ ⟧" $
+        testCLISucceeded ["rewrite", "--flat"] ["Δ ⤍ 65-0A-65"]
+
     it "rewrites with single rule" $
       withStdin "T(x -> Q.y)" $
         testCLISucceeded ["rewrite", "--rule=resources/normalize/dc.yaml"] ["⊥"]
diff --git a/test/CSTSpec.hs b/test/CSTSpec.hs
--- a/test/CSTSpec.hs
+++ b/test/CSTSpec.hs
@@ -10,6 +10,7 @@
 module CSTSpec (spec) where
 
 import AST
+import Bytes (NonFinite (..))
 import CST
 import Control.Monad (forM_)
 import Data.Aeson
@@ -144,9 +145,11 @@
     forM_
       [ ("is true for a finite integral value", BtMany ["40", "45", "00", "00", "00", "00", "00", "00"], True)
       , ("is true for a finite fractional value", BtMany ["BF", "D0", "00", "00", "00", "00", "00", "00"], True)
-      , ("is false for NaN", BtMany ["7F", "F8", "00", "00", "00", "00", "00", "00"], False)
-      , ("is false for positive infinity", BtMany ["7F", "F0", "00", "00", "00", "00", "00", "00"], False)
-      , ("is false for negative infinity", BtMany ["FF", "F0", "00", "00", "00", "00", "00", "00"], False)
+      , ("is true for NaN", BtMany ["7F", "F8", "00", "00", "00", "00", "00", "00"], True)
+      , ("is true for positive infinity", BtMany ["7F", "F0", "00", "00", "00", "00", "00", "00"], True)
+      , ("is true for negative infinity", BtMany ["FF", "F0", "00", "00", "00", "00", "00", "00"], True)
+      , ("is false for a NaN carrying a payload", BtMany ["7F", "F8", "00", "00", "00", "00", "00", "01"], False)
+      , ("is false for the negative quiet NaN", BtMany ["FF", "F8", "00", "00", "00", "00", "00", "00"], False)
       ]
       (\(desc, bts, expected) -> it desc (sweetNumber bts `shouldBe` expected))
 
@@ -154,7 +157,7 @@
     forM_
       [
         ( "delegates to sweetNumber for a data number"
-        , DataNumber (BtMany ["7F", "F8", "00", "00", "00", "00", "00", "00"])
+        , DataNumber (BtMany ["7F", "F8", "00", "00", "00", "00", "00", "01"])
         , False
         )
       ,
@@ -182,6 +185,24 @@
       ]
       (\(desc, metaName, expected) -> it desc (exMetaHead metaName `shouldBe` expected))
 
+  describe "expressionToCST on the non-finite doubles" $
+    forM_
+      [ ("NaN becomes Φ.nan", BtMany ["7F", "F8", "00", "00", "00", "00", "00", "00"], NfNan)
+      , ("positive infinity becomes Φ.pinf", BtMany ["7F", "F0", "00", "00", "00", "00", "00", "00"], NfPinf)
+      , ("negative infinity becomes Φ.ninf", BtMany ["FF", "F0", "00", "00", "00", "00", "00", "00"], NfNinf)
+      ]
+      ( \(desc, bts, value) ->
+          it desc (expressionToCST (DataNumber bts) `shouldBe` EX_NONFINITE Φ value (TAB 0) [])
+      )
+
+  describe "expressionToCST keeps a non-canonical non-finite double in byte form" $
+    it "leaves a NaN carrying a payload as an application" $ do
+      let payloaded = DataNumber (BtMany ["7F", "F8", "00", "00", "00", "00", "00", "01"])
+          isApplication :: EXPRESSION -> Bool
+          isApplication EX_APPLICATION{} = True
+          isApplication _ = False
+      expressionToCST payloaded `shouldSatisfy` isApplication
+
   describe "expressionToCST on rendering-only and meta nodes" $
     forM_
       [ ("ExBytes becomes a bare EX_BYTES chain node", ExBytes (BtOne "1F"), EX_BYTES (BT_ONE "1F"))
@@ -587,6 +608,7 @@
               }
           exString = EX_STRING{str = "hi", tab = TAB 0, rhos = []}
           exNumber = EX_NUMBER{num = Left 5, tab = TAB 0, rhos = []}
+          exNonFinite = EX_NONFINITE{global = Φ, nonfinite = NfNan, tab = TAB 0, rhos = []}
           exMeta = EX_META{meta = metaVal}
           exPhiMeet = EX_PHI_MEET{prefix = Just "p", idx = 1, expr = exGlobal}
           exBytes = EX_BYTES{bytes = BT_ONE "40"}
@@ -618,6 +640,10 @@
       exNumber.num `shouldBe` Left 5
       exNumber.tab `shouldBe` TAB 0
       exNumber.rhos `shouldBe` []
+      exNonFinite.global `shouldBe` Φ
+      exNonFinite.nonfinite `shouldBe` NfNan
+      exNonFinite.tab `shouldBe` TAB 0
+      exNonFinite.rhos `shouldBe` []
       exMeta.meta `shouldBe` metaVal
       exPhiMeet.prefix `shouldBe` Just "p"
       exPhiMeet.idx `shouldBe` 1
@@ -632,6 +658,7 @@
       shouldShowAndEqSelf "EX_APPLICATION" exApplication
       shouldShowAndEqSelf "EX_STRING" exString
       shouldShowAndEqSelf "EX_NUMBER" exNumber
+      shouldShowAndEqSelf "EX_NONFINITE" exNonFinite
       shouldShowAndEqSelf "EX_META" exMeta
       shouldShowAndEqSelf "EX_PHI_MEET" exPhiMeet
       shouldShowAndEqSelf "EX_BYTES" exBytes
diff --git a/test/EncodingSpec.hs b/test/EncodingSpec.hs
--- a/test/EncodingSpec.hs
+++ b/test/EncodingSpec.hs
@@ -5,6 +5,7 @@
 
 module EncodingSpec where
 
+import Bytes (NonFinite (..))
 import CST
 import Control.Monad (forM_)
 import Encoding (Encoding (..), toASCII, withEncoding)
@@ -59,6 +60,11 @@
         ( "EX_META with any other head becomes E'"
         , toASCII (EX_META (META NO_EXCL E "abc"))
         , EX_META (META EXCL E' "abc")
+        )
+      ,
+        ( "EX_NONFINITE switches the root to its ASCII spelling"
+        , toASCII (EX_NONFINITE Φ NfNan (TAB 1) [])
+        , EX_NONFINITE Q NfNan (TAB 1) []
         )
       ,
         ( "EX_PHI_MEET recurses into its expression"
diff --git a/test/LaTeXSpec.hs b/test/LaTeXSpec.hs
--- a/test/LaTeXSpec.hs
+++ b/test/LaTeXSpec.hs
@@ -119,6 +119,11 @@
           expressionToLaTeX expr (adjustContext defaultLatexContext) `shouldBe` expected
       )
 
+    it "renders a non-finite double as a piped dispatch off the root" $ do
+      nan <- parseExpressionThrows "[[ x -> Q.number(Q.bytes([[ D> 7F-F8-00-00-00-00-00-00 ]])) ]]"
+      expressionToLaTeX nan defaultLatexContext
+        `shouldBe` "\\begin{phiquation}\n[[ |x| -> Q . |nan| ]]{.}\n\\end{phiquation}"
+
     it "escapes '@' and '^' in an attribute label, same as '$' and '_'" $ do
       let weird = ExFormation [BiTau (AtLabel "a@b^c") ExRoot, BiVoid AtRho]
       expressionToLaTeX weird defaultLatexContext
diff --git a/test/LiningSpec.hs b/test/LiningSpec.hs
--- a/test/LiningSpec.hs
+++ b/test/LiningSpec.hs
@@ -5,6 +5,7 @@
 
 module LiningSpec where
 
+import Bytes (NonFinite (..))
 import CST
 import Control.Monad (forM_)
 import Lining (LineFormat (..), toSingleLine, withLineFormat)
@@ -60,6 +61,7 @@
         , EX_TERMINATION DEAD
         , EX_STRING "hi" (TAB 2) []
         , EX_NUMBER (Left 5) (TAB 2) []
+        , EX_NONFINITE Φ NfNan (TAB 2) []
         , EX_META (META NO_EXCL E "x")
         , EX_BYTES BT_EMPTY
         ]
diff --git a/test/ParserSpec.hs b/test/ParserSpec.hs
--- a/test/ParserSpec.hs
+++ b/test/ParserSpec.hs
@@ -417,6 +417,24 @@
       , ("", Nothing)
       ]
 
+  describe "parse the non-finite doubles named off the root" $
+    test
+      parseExpression
+      [ ("Q.nan", Just (DataNumber (BtMany ["7F", "F8", "00", "00", "00", "00", "00", "00"])))
+      , ("Φ.nan", Just (DataNumber (BtMany ["7F", "F8", "00", "00", "00", "00", "00", "00"])))
+      , ("Q.pinf", Just (DataNumber (BtMany ["7F", "F0", "00", "00", "00", "00", "00", "00"])))
+      , ("Φ.pinf", Just (DataNumber (BtMany ["7F", "F0", "00", "00", "00", "00", "00", "00"])))
+      , ("Q.ninf", Just (DataNumber (BtMany ["FF", "F0", "00", "00", "00", "00", "00", "00"])))
+      , ("Φ.ninf", Just (DataNumber (BtMany ["FF", "F0", "00", "00", "00", "00", "00", "00"])))
+      , -- only the exact names are special, everything else stays an ordinary dispatch
+        ("Q.number", Just (ExDispatch ExRoot (AtLabel "number")))
+      , ("Q.nanny", Just (ExDispatch ExRoot (AtLabel "nanny")))
+      , ("Q.x.nan", Just (ExDispatch (ExDispatch ExRoot (AtLabel "x")) (AtLabel "nan")))
+      , -- a bare name is still a ξ dispatch, as it always was
+        ("nan", Just (ExDispatch ExXi (AtLabel "nan")))
+      , ("$.nan", Just (ExDispatch ExXi (AtLabel "nan")))
+      ]
+
   describe "parseExpressionThrows" $
     forM_
       [ ("returns expression on valid input 'T'", "T", Just ExTermination)
diff --git a/test/PrinterSpec.hs b/test/PrinterSpec.hs
--- a/test/PrinterSpec.hs
+++ b/test/PrinterSpec.hs
@@ -66,11 +66,31 @@
     it "meta lambda becomes 𝑓" $
       printExpression' (ExFormation [BiLambda (FnMeta "F")]) (SWEET, UNICODE, SINGLELINE, defaultMargin) `shouldBe` "⟦ λ ⤍ 𝑓 ⟧"
 
-  describe "printExpression keeps special double values in byte form so they re-parse" $
+  describe "printExpression names the non-finite doubles instead of spelling their bytes" $
     forM_
-      [ ("NaN", BtMany ["7F", "F8", "00", "00", "00", "00", "00", "00"])
-      , ("positive infinity", BtMany ["7F", "F0", "00", "00", "00", "00", "00", "00"])
-      , ("negative infinity", BtMany ["FF", "F0", "00", "00", "00", "00", "00", "00"])
+      [ ("NaN", BtMany ["7F", "F8", "00", "00", "00", "00", "00", "00"], "Q.nan", "Φ.nan")
+      , ("positive infinity", BtMany ["7F", "F0", "00", "00", "00", "00", "00", "00"], "Q.pinf", "Φ.pinf")
+      , ("negative infinity", BtMany ["FF", "F0", "00", "00", "00", "00", "00", "00"], "Q.ninf", "Φ.ninf")
+      ]
+      ( \(desc, bts, ascii, unicode) ->
+          it desc $ do
+            let expr = DataNumber bts
+                printed = printExpression' expr (SWEET, ASCII, SINGLELINE, defaultMargin)
+            printed `shouldBe` ascii
+            printExpression' expr (SWEET, UNICODE, SINGLELINE, defaultMargin) `shouldBe` unicode
+            -- the name is read back into the very same number
+            parseExpression printed `shouldBe` Right expr
+            -- and --salty expands it back into the byte form
+            let salty = printExpression' expr (SALTY, ASCII, SINGLELINE, defaultMargin)
+            salty `shouldContain` "Q.number("
+            salty `shouldContain` "Q.bytes("
+            salty `shouldContain` printBytes bts
+      )
+
+  describe "printExpression keeps a non-finite double without a name in byte form" $
+    forM_
+      [ ("a NaN carrying a payload", BtMany ["7F", "F8", "00", "00", "00", "00", "00", "01"])
+      , ("the negative quiet NaN", BtMany ["FF", "F8", "00", "00", "00", "00", "00", "00"])
       ]
       ( \(desc, bts) ->
           it desc $ do
diff --git a/test/RenderSpec.hs b/test/RenderSpec.hs
--- a/test/RenderSpec.hs
+++ b/test/RenderSpec.hs
@@ -5,6 +5,7 @@
 
 module RenderSpec (spec) where
 
+import Bytes (NonFinite (..))
 import CST
 import Control.Monad (forM_)
 import Data.Text qualified as T
@@ -215,6 +216,9 @@
       , ("EX_STRING", EX_STRING "hi" (TAB 0) [], "\"hi\"")
       , ("EX_NUMBER integer", EX_NUMBER (Left 42) (TAB 0) [], "42")
       , ("EX_NUMBER double", EX_NUMBER (Right 3.5) (TAB 0) [], "3.5")
+      , ("EX_NONFINITE nan", EX_NONFINITE Φ NfNan (TAB 0) [], "Φ.nan")
+      , ("EX_NONFINITE pinf", EX_NONFINITE Φ NfPinf (TAB 0) [], "Φ.pinf")
+      , ("EX_NONFINITE ninf ascii", EX_NONFINITE Q NfNinf (TAB 0) [], "Q.ninf")
       , ("EX_META", EX_META (META NO_EXCL E "x"), "𝑒x")
       , ("EX_PHI_MEET without prefix", EX_PHI_MEET Nothing 5 xiExpr, "\\phinoMeet{5}{ ξ }")
       , ("EX_PHI_MEET with prefix", EX_PHI_MEET (Just "p") 5 xiExpr, "\\phinoMeet{p:5}{ ξ }")
diff --git a/test/SugarSpec.hs b/test/SugarSpec.hs
--- a/test/SugarSpec.hs
+++ b/test/SugarSpec.hs
@@ -6,6 +6,7 @@
 module SugarSpec (spec) where
 
 import AST
+import Bytes (NonFinite (..))
 import CST
 import Control.Monad (forM_)
 import Encoding (Encoding (UNICODE))
@@ -218,9 +219,39 @@
         , "Φ.number(\n    as-bytes ↦ Φ.bytes(\n      data ↦ ⟦\n        Δ ⤍ 40-45-00-00-00-00-00-00,\n        ρ ↦ ∅\n      ⟧\n    )\n  )(\n    ρ ↦ ξ.y\n  )"
         )
       ,
+        ( "EX_NONFINITE nan expands into the Q.number(Q.bytes(...)) form"
+        , EX_NONFINITE Φ NfNan (TAB 1) []
+        , "Φ.number(\n    as-bytes ↦ Φ.bytes(\n      data ↦ ⟦\n        Δ ⤍ 7F-F8-00-00-00-00-00-00,\n        ρ ↦ ∅\n      ⟧\n    )\n  )"
+        )
+      ,
+        ( "EX_NONFINITE pinf expands into the Q.number(Q.bytes(...)) form"
+        , EX_NONFINITE Φ NfPinf (TAB 1) []
+        , "Φ.number(\n    as-bytes ↦ Φ.bytes(\n      data ↦ ⟦\n        Δ ⤍ 7F-F0-00-00-00-00-00-00,\n        ρ ↦ ∅\n      ⟧\n    )\n  )"
+        )
+      ,
+        ( "EX_NONFINITE ninf keeps an extra rho argument carried alongside the primitive"
+        , EX_NONFINITE Φ NfNinf (TAB 1) [ArTau AtRho (ExDispatch ExXi (AtLabel "y"))]
+        , "Φ.number(\n    as-bytes ↦ Φ.bytes(\n      data ↦ ⟦\n        Δ ⤍ FF-F0-00-00-00-00-00-00,\n        ρ ↦ ∅\n      ⟧\n    )\n  )(\n    ρ ↦ ξ.y\n  )"
+        )
+      ,
         ( "EX_STRING expands into the Q.string(Q.bytes(...)) form"
         , EX_STRING "hi" (TAB 1) []
         , "Φ.string(\n    as-bytes ↦ Φ.bytes(\n      data ↦ ⟦\n        Δ ⤍ 68-69,\n        ρ ↦ ∅\n      ⟧\n    )\n  )"
+        )
+      ,
+        ( "EX_STRING unescapes a newline instead of taking its escape literally"
+        , EX_STRING "e\\ne" (TAB 1) []
+        , "Φ.string(\n    as-bytes ↦ Φ.bytes(\n      data ↦ ⟦\n        Δ ⤍ 65-0A-65,\n        ρ ↦ ∅\n      ⟧\n    )\n  )"
+        )
+      ,
+        ( "EX_STRING unescapes a quote and a backslash into single bytes"
+        , EX_STRING "\\\"\\\\" (TAB 1) []
+        , "Φ.string(\n    as-bytes ↦ Φ.bytes(\n      data ↦ ⟦\n        Δ ⤍ 22-5C,\n        ρ ↦ ∅\n      ⟧\n    )\n  )"
+        )
+      ,
+        ( "EX_STRING unescapes a hex escape back into its byte"
+        , EX_STRING "\\x01" (TAB 1) []
+        , "Φ.string(\n    as-bytes ↦ Φ.bytes(\n      data ↦ ⟦\n        Δ ⤍ 01-,\n        ρ ↦ ∅\n      ⟧\n    )\n  )"
         )
       ]
       (\(desc, sweetExpr, expected) -> it desc (render (toSalty sweetExpr) `shouldBe` expected))
