diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+1.2.2
+  - Add new `zigZag{Encode,Decode}` utilities
+
 1.2.1
   - Build against GHC 9.0
   - Build against `tasty` 1.3 and 1.4
diff --git a/proto3-wire.cabal b/proto3-wire.cabal
--- a/proto3-wire.cabal
+++ b/proto3-wire.cabal
@@ -1,5 +1,5 @@
 name:                proto3-wire
-version:             1.2.1
+version:             1.2.2
 synopsis:            A low-level implementation of the Protocol Buffers (version 3) wire format
 license:             Apache-2.0
 license-file:        LICENSE
diff --git a/src/Proto3/Wire/Decode.hs b/src/Proto3/Wire/Decode.hs
--- a/src/Proto3/Wire/Decode.hs
+++ b/src/Proto3/Wire/Decode.hs
@@ -72,6 +72,8 @@
     , repeated
     , embedded
     , embedded'
+      -- * ZigZag codec
+    , zigZagDecode
       -- * Exported For Doctest Only
     , toMap
     ) where
diff --git a/src/Proto3/Wire/Encode.hs b/src/Proto3/Wire/Encode.hs
--- a/src/Proto3/Wire/Encode.hs
+++ b/src/Proto3/Wire/Encode.hs
@@ -97,9 +97,12 @@
     , packedFloatsV
     , packedDoubles
     , packedDoublesV
+      -- * ZigZag codec
+    , zigZagEncode
     ) where
 
-import           Data.Bits                     ( (.|.), shiftL, shiftR, xor )
+import           Data.Bits                     ( (.|.), shiftL, shiftR, xor,
+                                                 FiniteBits, finiteBitSize )
 import qualified Data.ByteString               as B
 import qualified Data.ByteString.Lazy          as BL
 import           Data.Coerce                   ( coerce )
@@ -120,6 +123,12 @@
 -- >>> :set -XOverloadedStrings -XOverloadedLists
 -- >>> :module Proto3.Wire.Encode Proto3.Wire.Class Data.Word
 
+-- | zigzag-encoded numeric type.
+zigZagEncode :: (Num a, FiniteBits a) => a -> a
+zigZagEncode i = (i `shiftL` 1) `xor` (i `shiftR` n)
+  where n = finiteBitSize i - 1
+{-# INLINE zigZagEncode #-}
+
 -- | A `MessageBuilder` represents a serialized protobuf message
 --
 -- Use the utilities provided by this module to create `MessageBuilder`s
@@ -314,7 +323,7 @@
 -- Proto3.Wire.Encode.unsafeFromLazyByteString "\b\255\255\255\255\SI"
 sint32 :: FieldNumber -> Int32 -> MessageBuilder
 sint32 = \num i ->
-  uint32 num (fromIntegral ((i `shiftL` 1) `xor` (i `shiftR` 31)))
+  uint32 num (fromIntegral (zigZagEncode i))
 {-# INLINE sint32 #-}
 
 -- | Encode a 64-bit signed integer
@@ -329,7 +338,7 @@
 -- Proto3.Wire.Encode.unsafeFromLazyByteString "\b\255\255\255\255\255\255\255\255\255\SOH"
 sint64 :: FieldNumber -> Int64 -> MessageBuilder
 sint64 = \num i ->
-  uint64 num (fromIntegral ((i `shiftL` 1) `xor` (i `shiftR` 63)))
+  uint64 num (fromIntegral (zigZagEncode i))
 {-# INLINE sint64 #-}
 
 -- | Encode a fixed-width 32-bit integer
