diff --git a/README.md b/README.md
index 5cebb1f..b849519 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,7 @@ deepStrictEqual(decode(encoded), object);
     - [`EncoderOptions`](#encoderoptions)
   - [`decode(buffer: ArrayLike<number> | BufferSource, options?: DecoderOptions): unknown`](#decodebuffer-arraylikenumber--buffersource-options-decoderoptions-unknown)
     - [`DecoderOptions`](#decoderoptions)
+      - [`IntMode`](#intmode)
   - [`decodeMulti(buffer: ArrayLike<number> | BufferSource, options?: DecoderOptions): Generator<unknown, void, unknown>`](#decodemultibuffer-arraylikenumber--buffersource-options-decoderoptions-generatorunknown-void-unknown)
   - [`decodeAsync(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecoderOptions): Promise<unknown>`](#decodeasyncstream-readablestreamlikearraylikenumber--buffersource-options-decoderoptions-promiseunknown)
   - [`decodeArrayStream(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecoderOptions): AsyncIterable<unknown>`](#decodearraystreamstream-readablestreamlikearraylikenumber--buffersource-options-decoderoptions-asynciterableunknown)
@@ -51,7 +52,7 @@ deepStrictEqual(decode(encoded), object);
   - [Reusing Encoder and Decoder instances](#reusing-encoder-and-decoder-instances)
 - [Extension Types](#extension-types)
     - [ExtensionCodec context](#extensioncodec-context)
-    - [Handling BigInt with ExtensionCodec](#handling-bigint-with-extensioncodec)
+    - [Handling BigInt](#handling-bigint)
     - [The temporal module as timestamp extensions](#the-temporal-module-as-timestamp-extensions)
 - [Decoding a Blob](#decoding-a-blob)
 - [MessagePack Specification](#messagepack-specification)
@@ -113,7 +114,7 @@ Name|Type|Default
 ----|----|----
 extensionCodec | ExtensionCodec | `ExtensionCodec.defaultCodec`
 context | user-defined | -
-useBigInt64 | boolean | false
+useInt64 | boolean | false
 maxDepth | number | `100`
 initialBufferSize | number | `2048`
 sortKeys | boolean | false
@@ -148,14 +149,27 @@ Name|Type|Default
 extensionCodec | ExtensionCodec | `ExtensionCodec.defaultCodec`
 context | user-defined | -
 useBigInt64 | boolean | false
+intMode | IntMode | IntMode.BIGINT if useBigInt64 is true or IntMode.UNSAFE_NUMBER otherwise
 maxStrLength | number | `4_294_967_295` (UINT32_MAX)
 maxBinLength | number | `4_294_967_295` (UINT32_MAX)
 maxArrayLength | number | `4_294_967_295` (UINT32_MAX)
 maxMapLength | number | `4_294_967_295` (UINT32_MAX)
 maxExtLength | number | `4_294_967_295` (UINT32_MAX)
+intMode | `IntMode` | `IntMode.UNSAFE_NUMBER`
 
 You can use `max${Type}Length` to limit the length of each type decoded.
 
+`intMode` determines whether decoded integers should be returned as numbers or bigints. The possible values are [described below](#intmode).
+
+##### `IntMode`
+
+The `IntMode` enum defines different options for decoding integers. They are described below:
+
+- `IntMode.UNSAFE_NUMBER`: Always returns the value as a number. Be aware that there will be a loss of precision if the value is outside the range of `Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`.
+- `IntMode.SAFE_NUMBER`: Always returns the value as a number, but throws an error if the value is outside of the range of `Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`.
+- `IntMode.MIXED`: Returns all values inside the range of `Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER` as numbers and all values outside that range as bigints.
+- `IntMode.BIGINT`: Always returns the value as a bigint, even if it is small enough to safely fit in a number.
+
 ### `decodeMulti(buffer: ArrayLike<number> | BufferSource, options?: DecoderOptions): Generator<unknown, void, unknown>`
 
 It decodes `buffer` that includes multiple MessagePack-encoded objects, and returns decoded objects as a generator. See also `decodeMultiStream()`, which is an asynchronous variant of this function.
@@ -352,7 +366,7 @@ const encoded = = encode({myType: new MyType<any>()}, { extensionCodec, context
 const decoded = decode(encoded, { extensionCodec, context });
 ```
 
-#### Handling BigInt with ExtensionCodec
+#### Handling BigInt
 
 This library does not handle BigInt by default, but you have two options to handle it:
 
@@ -488,28 +502,27 @@ Note that as of June 2019 there're no official "version" on the MessagePack spec
 
 The following table shows how JavaScript values are mapped to [MessagePack formats](https://github.com/msgpack/msgpack/blob/master/spec.md) and vice versa.
 
-The mapping of integers varies on the setting of `useBigInt64`.
-
-The default, `useBigInt64: false` is:
+The mapping of integers varies on the setting of `intMode`.
 
 Source Value|MessagePack Format|Value Decoded
 ----|----|----
 null, undefined|nil|null (*1)
 boolean (true, false)|bool family|boolean (true, false)
-number (53-bit int)|int family|number
-number (64-bit float)|float family|number
+number (53-bit int)|int family|number or bigint (*2)
+number (64-bit float)|float family|number (64-bit float)
+bigint|int family|number or bigint (*2)
 string|str family|string
-ArrayBufferView |bin family|Uint8Array (*2)
+ArrayBufferView |bin family|Uint8Array (*3)
 Array|array family|Array
-Object|map family|Object (*3)
-Date|timestamp ext family|Date (*4)
-bigint|N/A|N/A (*5)
+Object|map family|Object (*4)
+Date|timestamp ext family|Date (*5)
+bigint|int family|bigint
 
 * *1 Both `null` and `undefined` are mapped to `nil` (`0xC0`) type, and are decoded into `null`
-* *2 Any `ArrayBufferView`s including NodeJS's `Buffer` are mapped to `bin` family, and are decoded into `Uint8Array`
-* *3 In handling `Object`, it is regarded as `Record<string, unknown>` in terms of TypeScript
-* *4 MessagePack timestamps may have nanoseconds, which will lost when it is decoded into JavaScript `Date`. This behavior can be overridden by registering `-1` for the extension codec.
-* *5 bigint is not supported in `useBigInt64: false` mode, but you can define an extension codec for it.
+* *2 MessagePack ints are decoded as either numbers or bigints depending on the [IntMode](#intmode) used during decoding.
+* *3 Any `ArrayBufferView`s including NodeJS's `Buffer` are mapped to `bin` family, and are decoded into `Uint8Array`
+* *4 In handling `Object`, it is regarded as `Record<string, unknown>` in terms of TypeScript
+* *5 MessagePack timestamps may have nanoseconds, which will lost when it is decoded into JavaScript `Date`. This behavior can be overridden by registering `-1` for the extension codec.
 
 If you set `useBigInt64: true`, the following mapping is used:
 
@@ -519,7 +532,7 @@ null, undefined|nil|null
 boolean (true, false)|bool family|boolean (true, false)
 **number (32-bit int)**|int family|number
 **number (except for the above)**|float family|number
-**bigint**|int64 / uint64|bigint (*6)
+**bigint**|int64 / uint64|bigint (*5)
 string|str family|string
 ArrayBufferView |bin family|Uint8Array
 Array|array family|Array
@@ -527,7 +540,7 @@ Object|map family|Object
 Date|timestamp ext family|Date
 
 
-* *6 If the bigint is larger than the max value of uint64 or smaller than the min value of int64, then the behavior is undefined.
+* *5 If the bigint is larger than the max value of uint64 or smaller than the min value of int64, then the behavior is undefined.
 
 ## Prerequisites
 
diff --git a/src/Decoder.ts b/src/Decoder.ts
index eedb0fb..3614a02 100644
--- a/src/Decoder.ts
+++ b/src/Decoder.ts
@@ -1,6 +1,6 @@
 import { prettyByte } from "./utils/prettyByte";
 import { ExtensionCodec, ExtensionCodecType } from "./ExtensionCodec";
-import { getInt64, getUint64, UINT32_MAX } from "./utils/int";
+import { IntMode, getInt64, getUint64, convertSafeIntegerToMode, UINT32_MAX } from "./utils/int";
 import { utf8Decode } from "./utils/utf8";
 import { createDataView, ensureUint8Array } from "./utils/typedArrays";
 import { CachedKeyDecoder, KeyDecoder } from "./CachedKeyDecoder";
@@ -16,10 +16,17 @@ export type DecoderOptions<ContextType = undefined> = Readonly<
      * Depends on ES2020's {@link DataView#getBigInt64} and
      * {@link DataView#getBigUint64}.
      *
-     * Defaults to false.
+     * Defaults to false. If true, equivalent to intMode: IntMode.BIGINT.
      */
     useBigInt64: boolean;
 
+    /**
+     * Allows for more fine-grained control of BigInt handling, overrides useBigInt64.
+     *
+     * Defaults to IntMode.BIGINT if useBigInt64 is true or IntMode.UNSAFE_NUMBER otherwise.
+     */
+    intMode?: IntMode,
+
     /**
      * Maximum string length.
      *
@@ -194,7 +201,7 @@ const sharedCachedKeyDecoder = new CachedKeyDecoder();
 export class Decoder<ContextType = undefined> {
   private readonly extensionCodec: ExtensionCodecType<ContextType>;
   private readonly context: ContextType;
-  private readonly useBigInt64: boolean;
+  private readonly intMode: IntMode;
   private readonly maxStrLength: number;
   private readonly maxBinLength: number;
   private readonly maxArrayLength: number;
@@ -214,7 +221,7 @@ export class Decoder<ContextType = undefined> {
     this.extensionCodec = options?.extensionCodec ?? (ExtensionCodec.defaultCodec as ExtensionCodecType<ContextType>);
     this.context = (options as { context: ContextType } | undefined)?.context as ContextType; // needs a type assertion because EncoderOptions has no context property when ContextType is undefined
 
-    this.useBigInt64 = options?.useBigInt64 ?? false;
+    this.intMode = options?.intMode ?? options?.useBigInt64 ? IntMode.BIGINT : IntMode.UNSAFE_NUMBER
     this.maxStrLength = options?.maxStrLength ?? UINT32_MAX;
     this.maxBinLength = options?.maxBinLength ?? UINT32_MAX;
     this.maxArrayLength = options?.maxArrayLength ?? UINT32_MAX;
@@ -371,11 +378,11 @@ export class Decoder<ContextType = undefined> {
 
       if (headByte >= 0xe0) {
         // negative fixint (111x xxxx) 0xe0 - 0xff
-        object = headByte - 0x100;
+        object = this.convertNumber(headByte - 0x100);
       } else if (headByte < 0xc0) {
         if (headByte < 0x80) {
           // positive fixint (0xxx xxxx) 0x00 - 0x7f
-          object = headByte;
+          object = this.convertNumber(headByte);
         } else if (headByte < 0x90) {
           // fixmap (1000 xxxx) 0x80 - 0x8f
           const size = headByte - 0x80;
@@ -418,36 +425,28 @@ export class Decoder<ContextType = undefined> {
         object = this.readF64();
       } else if (headByte === 0xcc) {
         // uint 8
-        object = this.readU8();
+        object = this.convertNumber(this.readU8());
       } else if (headByte === 0xcd) {
         // uint 16
-        object = this.readU16();
+        object = this.convertNumber(this.readU16());
       } else if (headByte === 0xce) {
         // uint 32
-        object = this.readU32();
+        object = this.convertNumber(this.readU32());
       } else if (headByte === 0xcf) {
         // uint 64
-        if (this.useBigInt64) {
-          object = this.readU64AsBigInt();
-        } else {
-          object = this.readU64();
-        }
+        object = this.readU64();
       } else if (headByte === 0xd0) {
         // int 8
-        object = this.readI8();
+        object = this.convertNumber(this.readI8());
       } else if (headByte === 0xd1) {
         // int 16
-        object = this.readI16();
+        object = this.convertNumber(this.readI16());
       } else if (headByte === 0xd2) {
         // int 32
-        object = this.readI32();
+        object = this.convertNumber(this.readI32());
       } else if (headByte === 0xd3) {
         // int 64
-        if (this.useBigInt64) {
-          object = this.readI64AsBigInt();
-        } else {
           object = this.readI64();
-        }
       } else if (headByte === 0xd9) {
         // str 8
         const byteLength = this.lookU8();
@@ -692,6 +691,10 @@ export class Decoder<ContextType = undefined> {
     return this.extensionCodec.decode(data, extType, this.context);
   }
 
+  private convertNumber(value: number): number | bigint {
+    return convertSafeIntegerToMode(value, this.intMode);
+  }
+
   private lookU8() {
     return this.view.getUint8(this.pos);
   }
@@ -740,26 +743,14 @@ export class Decoder<ContextType = undefined> {
     return value;
   }
 
-  private readU64(): number {
-    const value = getUint64(this.view, this.pos);
-    this.pos += 8;
-    return value;
-  }
-
-  private readI64(): number {
-    const value = getInt64(this.view, this.pos);
-    this.pos += 8;
-    return value;
-  }
-
-  private readU64AsBigInt(): bigint {
-    const value = this.view.getBigUint64(this.pos);
+  private readU64(): number | bigint {
+    const value = getUint64(this.view, this.pos, this.intMode);
     this.pos += 8;
     return value;
   }
 
-  private readI64AsBigInt(): bigint {
-    const value = this.view.getBigInt64(this.pos);
+  private readI64(): number | bigint {
+    const value = getInt64(this.view, this.pos, this.intMode);
     this.pos += 8;
     return value;
   }
diff --git a/src/Encoder.ts b/src/Encoder.ts
index cda0822..e985dd4 100644
--- a/src/Encoder.ts
+++ b/src/Encoder.ts
@@ -1,9 +1,9 @@
 import { utf8Count, utf8Encode } from "./utils/utf8";
 import { ExtensionCodec, ExtensionCodecType } from "./ExtensionCodec";
-import { setInt64, setUint64 } from "./utils/int";
 import { ensureUint8Array } from "./utils/typedArrays";
 import type { ExtData } from "./ExtData";
 import type { ContextOf } from "./context";
+import { setInt64, setUint64 } from "./utils/int";
 
 export const DEFAULT_MAX_DEPTH = 100;
 export const DEFAULT_INITIAL_BUFFER_SIZE = 2048;
@@ -13,14 +13,11 @@ export type EncoderOptions<ContextType = undefined> = Partial<
     extensionCodec: ExtensionCodecType<ContextType>;
 
     /**
-     * Encodes bigint as Int64 or Uint64 if it's set to true.
-     * {@link forceIntegerToFloat} does not affect bigint.
-     * Depends on ES2020's {@link DataView#setBigInt64} and
-     * {@link DataView#setBigUint64}.
+     * Encodes a `number` greater than 32-bit as Int64 or Uint64 if it's set to true, otherwise encode as float64.
      *
      * Defaults to false.
      */
-    useBigInt64: boolean;
+    useInt64: boolean;
 
     /**
      * The maximum depth in nested objects and arrays.
@@ -43,6 +40,7 @@ export type EncoderOptions<ContextType = undefined> = Partial<
      * Defaults to `false`. If enabled, it spends more time in encoding objects.
      */
     sortKeys: boolean;
+
     /**
      * If `true`, non-integer numbers are encoded in float32, not in float64 (the default).
      *
@@ -74,7 +72,7 @@ export type EncoderOptions<ContextType = undefined> = Partial<
 export class Encoder<ContextType = undefined> {
   private readonly extensionCodec: ExtensionCodecType<ContextType>;
   private readonly context: ContextType;
-  private readonly useBigInt64: boolean;
+  private readonly useInt64: boolean;
   private readonly maxDepth: number;
   private readonly initialBufferSize: number;
   private readonly sortKeys: boolean;
@@ -90,7 +88,7 @@ export class Encoder<ContextType = undefined> {
     this.extensionCodec = options?.extensionCodec ?? (ExtensionCodec.defaultCodec as ExtensionCodecType<ContextType>);
     this.context = (options as { context: ContextType } | undefined)?.context as ContextType; // needs a type assertion because EncoderOptions has no context property when ContextType is undefined
 
-    this.useBigInt64 = options?.useBigInt64 ?? false;
+    this.useInt64 = options?.useInt64 ?? false;
     this.maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
     this.initialBufferSize = options?.initialBufferSize ?? DEFAULT_INITIAL_BUFFER_SIZE;
     this.sortKeys = options?.sortKeys ?? false;
@@ -144,8 +142,6 @@ export class Encoder<ContextType = undefined> {
       }
     } else if (typeof object === "string") {
       this.encodeString(object);
-    } else if (this.useBigInt64 && typeof object === "bigint") {
-      this.encodeBigInt64(object);
     } else {
       this.encodeObject(object, depth);
     }
@@ -200,7 +196,7 @@ export class Encoder<ContextType = undefined> {
           // uint 32
           this.writeU8(0xce);
           this.writeU32(object);
-        } else if (!this.useBigInt64) {
+        } else if (this.useInt64) {
           // uint 64
           this.writeU8(0xcf);
           this.writeU64(object);
@@ -223,7 +219,7 @@ export class Encoder<ContextType = undefined> {
           // int 32
           this.writeU8(0xd2);
           this.writeI32(object);
-        } else if (!this.useBigInt64) {
+        } else if (this.useInt64) {
           // int 64
           this.writeU8(0xd3);
           this.writeI64(object);
@@ -248,15 +244,29 @@ export class Encoder<ContextType = undefined> {
     }
   }
 
-  private encodeBigInt64(object: bigint): void {
-    if (object >= BigInt(0)) {
-      // uint 64
-      this.writeU8(0xcf);
-      this.writeBigUint64(object);
+  private encodeBigInt(object: bigint) {
+    if (object >= 0) {
+      if (object < 0x100000000 || this.forceIntegerToFloat) {
+        // uint 32 or lower, or force to float
+        this.encodeNumber(Number(object))
+      } else if (object < BigInt("0x10000000000000000")) {
+        // uint 64
+        this.writeU8(0xcf);
+        this.writeBigUint64(object);
+      } else {
+        throw new Error(`Bigint is too large for uint64: ${object}`);
+      }
     } else {
-      // int 64
-      this.writeU8(0xd3);
-      this.writeBigInt64(object);
+      if (object >= -0x80000000 || this.forceIntegerToFloat) {
+        // int 32 or lower, or force to float
+        this.encodeNumber(Number(object));
+      } else if (object >= BigInt(-1) * BigInt("0x8000000000000000")) {
+        // int 64
+        this.writeU8(0xd3);
+        this.writeBigInt64(object);
+      } else {
+        throw new Error(`Bigint is too small for int64: ${object}`);
+      }
     }
   }
 
@@ -296,6 +306,10 @@ export class Encoder<ContextType = undefined> {
     const ext = this.extensionCodec.tryToEncode(object, this.context);
     if (ext != null) {
       this.encodeExtension(ext);
+    } else if (typeof object === "bigint") {
+      // this is here instead of in doEncode so that we can try encoding with an extension first,
+      // otherwise we would break existing extensions for bigints
+      this.encodeBigInt(object);
     } else if (Array.isArray(object)) {
       this.encodeArray(object, depth);
     } else if (ArrayBuffer.isView(object)) {
diff --git a/src/index.ts b/src/index.ts
index 4141ad4..9b58b9e 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -9,6 +9,8 @@ import { decode, decodeMulti } from "./decode";
 export { decode, decodeMulti };
 import type { DecodeOptions } from "./decode";
 export type { DecodeOptions };
+import { IntMode } from './utils/int';
+export { IntMode };
 
 import { decodeAsync, decodeArrayStream, decodeMultiStream, decodeStream } from "./decodeAsync";
 export { decodeAsync, decodeArrayStream, decodeMultiStream, decodeStream };
diff --git a/src/timestamp.ts b/src/timestamp.ts
index e3fe015..2687361 100644
--- a/src/timestamp.ts
+++ b/src/timestamp.ts
@@ -1,6 +1,6 @@
 // https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type
 import { DecodeError } from "./DecodeError";
-import { getInt64, setInt64 } from "./utils/int";
+import { IntMode, getInt64, setInt64 } from "./utils/int";
 
 export const EXT_TIMESTAMP = -1;
 
@@ -87,7 +87,7 @@ export function decodeTimestampToTimeSpec(data: Uint8Array): TimeSpec {
     case 12: {
       // timestamp 96 = { nsec32 (unsigned), sec64 (signed) }
 
-      const sec = getInt64(view, 4);
+      const sec = getInt64(view, 4, IntMode.UNSAFE_NUMBER);
       const nsec = view.getUint32(0);
       return { sec, nsec };
     }
diff --git a/src/utils/int.ts b/src/utils/int.ts
index 7fa93fb..c85b10c 100644
--- a/src/utils/int.ts
+++ b/src/utils/int.ts
@@ -1,5 +1,30 @@
 // Integer Utility
 
+/**
+ * An enum of different options for decoding integers.
+ */
+export enum IntMode {
+  /**
+   * Always returns the value as a number. Be aware that there will be a loss of precision if the
+   * value is outside the range of Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER.
+   */
+  UNSAFE_NUMBER,
+  /**
+   * Always returns the value as a number, but throws an error if the value is outside of the range
+   * of Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER.
+   */
+  SAFE_NUMBER,
+  /**
+   * Returns all values inside the range of Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER as
+   * numbers and all values outside that range as bigints.
+   */
+  MIXED,
+  /**
+   * Always returns the value as a bigint, even if it is small enough to safely fit in a number.
+   */
+  BIGINT,
+}
+
 export const UINT32_MAX = 0xffff_ffff;
 
 // DataView extension to handle int64 / uint64,
@@ -19,14 +44,71 @@ export function setInt64(view: DataView, offset: number, value: number): void {
   view.setUint32(offset + 4, low);
 }
 
-export function getInt64(view: DataView, offset: number): number {
-  const high = view.getInt32(offset);
-  const low = view.getUint32(offset + 4);
-  return high * 0x1_0000_0000 + low;
+export function getInt64(view: DataView, offset: number, mode: IntMode.UNSAFE_NUMBER | IntMode.SAFE_NUMBER): number
+export function getInt64(view: DataView, offset: number, mode: IntMode.BIGINT): bigint
+export function getInt64(view: DataView, offset: number, mode: IntMode): number | bigint
+export function getInt64(view: DataView, offset: number, mode: IntMode): number | bigint {
+  if (mode === IntMode.UNSAFE_NUMBER || mode === IntMode.SAFE_NUMBER) {
+    // for compatibility, don't use view.getBigInt64 if the user hasn't told us to use BigInts
+    const high = view.getInt32(offset);
+    const low = view.getUint32(offset + 4);
+
+    if (mode === IntMode.SAFE_NUMBER && (
+      high < Math.floor(Number.MIN_SAFE_INTEGER / 0x1_0000_0000) ||
+      (high === Math.floor(Number.MIN_SAFE_INTEGER / 0x1_0000_0000) && low === 0) ||
+      high > (Number.MAX_SAFE_INTEGER - low) / 0x1_0000_0000
+    )) {
+      const hexValue = `${high < 0 ? "-" : ""}0x${Math.abs(high).toString(16)}${low.toString(16).padStart(8, "0")}`;
+      throw new Error(`Mode is IntMode.SAFE_NUMBER and value is not a safe integer: ${hexValue}`);
+    }
+
+    return high * 0x1_0000_0000 + low;
+  }
+
+  const value = view.getBigInt64(offset);
+
+  if (mode === IntMode.MIXED && value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER) {
+    return Number(value);
+  }
+
+  return value;
 }
 
-export function getUint64(view: DataView, offset: number): number {
-  const high = view.getUint32(offset);
-  const low = view.getUint32(offset + 4);
-  return high * 0x1_0000_0000 + low;
+export function getUint64(view: DataView, offset: number, mode: IntMode.UNSAFE_NUMBER | IntMode.SAFE_NUMBER): number
+export function getUint64(view: DataView, offset: number, mode: IntMode.BIGINT): bigint
+export function getUint64(view: DataView, offset: number, mode: IntMode): number | bigint
+export function getUint64(view: DataView, offset: number, mode: IntMode): number | bigint {
+  if (mode === IntMode.UNSAFE_NUMBER || mode === IntMode.SAFE_NUMBER) {
+    // for compatibility, don't use view.getBigUint64 if the user hasn't told us to use BigInts
+    const high = view.getUint32(offset);
+    const low = view.getUint32(offset + 4);
+
+    if (mode === IntMode.SAFE_NUMBER && high > (Number.MAX_SAFE_INTEGER - low) / 0x1_0000_0000) {
+      const hexValue = `0x${high.toString(16)}${low.toString(16).padStart(8, "0")}`;
+      throw new Error(`Mode is IntMode.SAFE_NUMBER and value is not a safe integer: ${hexValue}`);
+    }
+
+    return high * 0x1_0000_0000 + low;
+  }
+
+  const value = view.getBigUint64(offset);
+
+  if (mode === IntMode.MIXED && value <= Number.MAX_SAFE_INTEGER) {
+    return Number(value);
+  }
+
+  return value;
+}
+
+/**
+ * Convert a safe integer Number (i.e. in the range Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER)
+ * with respect to the given IntMode. For all modes except IntMode.BIGINT, this returns the original
+ * Number unmodified.
+ */
+export function convertSafeIntegerToMode(value: number, mode: IntMode): number | bigint {
+  if (mode === IntMode.BIGINT) {
+    return BigInt(value);
+  }
+
+  return value;
 }
diff --git a/test/bigint64.test.ts b/test/bigint64.test.ts
index fabf0f5..32eba21 100644
--- a/test/bigint64.test.ts
+++ b/test/bigint64.test.ts
@@ -10,19 +10,19 @@ describe("useBigInt64: true", () => {
 
   it("encodes and decodes 0n", () => {
     const value = BigInt(0);
-    const encoded = encode(value, { useBigInt64: true });
+    const encoded = encode(value);
     assert.deepStrictEqual(decode(encoded, { useBigInt64: true }), value);
   });
 
   it("encodes and decodes MAX_SAFE_INTEGER+1", () => {
     const value = BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1);
-    const encoded = encode(value, { useBigInt64: true });
+    const encoded = encode(value);
     assert.deepStrictEqual(decode(encoded, { useBigInt64: true }), value);
   });
 
   it("encodes and decodes MIN_SAFE_INTEGER-1", () => {
     const value = BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1);
-    const encoded = encode(value, { useBigInt64: true });
+    const encoded = encode(value);
     assert.deepStrictEqual(decode(encoded, { useBigInt64: true }), value);
   });
 
@@ -32,7 +32,7 @@ describe("useBigInt64: true", () => {
       nums: [Number.NaN, Math.PI, Math.E, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY],
       bigints: [BigInt(0), BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1), BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1)],
     };
-    const encoded = encode(value, { useBigInt64: true });
+    const encoded = encode(value);
     assert.deepStrictEqual(decode(encoded, { useBigInt64: true }), value);
   });
 });
diff --git a/test/codec-bigint.test.ts b/test/codec-bigint.test.ts
index 4ed6aff..408e6fe 100644
--- a/test/codec-bigint.test.ts
+++ b/test/codec-bigint.test.ts
@@ -1,5 +1,6 @@
 import assert from "assert";
 import { encode, decode, ExtensionCodec, DecodeError } from "../src";
+import { IntMode, getInt64, getUint64 } from "../src/utils/int";
 
 // There's a built-in `useBigInt64: true` option, but a custom codec might be
 // better if you'd like to encode bigint to reduce the size of binaries.
@@ -29,11 +30,229 @@ extensionCodec.register({
   },
 });
 
+interface TestCase {
+  input: bigint,
+  expected: Map<IntMode, number | bigint | "error">,
+}
+
+// declared as a function to delay referencing the BigInt constructor
+function BIGINTSPECS(): Record<string, TestCase> {
+  return {
+    ZERO: {
+      input: BigInt(0),
+      expected: new Map<IntMode, number | bigint>([
+        [IntMode.UNSAFE_NUMBER, 0],
+        [IntMode.SAFE_NUMBER, 0],
+        [IntMode.MIXED, 0],
+        [IntMode.BIGINT, BigInt(0)],
+      ])
+    },
+    ONE: {
+      input: BigInt(1),
+      expected: new Map<IntMode, number | bigint>([
+        [IntMode.UNSAFE_NUMBER, 1],
+        [IntMode.SAFE_NUMBER, 1],
+        [IntMode.MIXED, 1],
+        [IntMode.BIGINT, BigInt(1)],
+      ])
+    },
+    MINUS_ONE: {
+      input: BigInt(-1),
+      expected: new Map<IntMode, number | bigint>([
+        [IntMode.UNSAFE_NUMBER, -1],
+        [IntMode.SAFE_NUMBER, -1],
+        [IntMode.MIXED, -1],
+        [IntMode.BIGINT, BigInt(-1)],
+      ])
+    },
+    X_FF: {
+      input: BigInt(0xff),
+      expected: new Map<IntMode, number | bigint>([
+        [IntMode.UNSAFE_NUMBER, 0xff],
+        [IntMode.SAFE_NUMBER, 0xff],
+        [IntMode.MIXED, 0xff],
+        [IntMode.BIGINT, BigInt(0xff)],
+      ])
+    },
+    MINUS_X_FF: {
+      input: BigInt(-0xff),
+      expected: new Map<IntMode, number | bigint>([
+        [IntMode.UNSAFE_NUMBER, -0xff],
+        [IntMode.SAFE_NUMBER, -0xff],
+        [IntMode.MIXED, -0xff],
+        [IntMode.BIGINT, BigInt(-0xff)],
+      ])
+    },
+    INT32_MAX: {
+      input: BigInt(0x7fffffff),
+      expected: new Map<IntMode, number | bigint>([
+        [IntMode.UNSAFE_NUMBER, 0x7fffffff],
+        [IntMode.SAFE_NUMBER, 0x7fffffff],
+        [IntMode.MIXED, 0x7fffffff],
+        [IntMode.BIGINT, BigInt(0x7fffffff)],
+      ])
+    },
+    INT32_MIN: {
+      input: BigInt(-0x7fffffff - 1),
+      expected: new Map<IntMode, number | bigint>([
+        [IntMode.UNSAFE_NUMBER, -0x7fffffff - 1],
+        [IntMode.SAFE_NUMBER, -0x7fffffff - 1],
+        [IntMode.MIXED, -0x7fffffff - 1],
+        [IntMode.BIGINT, BigInt(-0x7fffffff - 1)],
+      ])
+    },
+    MAX_SAFE_INTEGER: {
+      input: BigInt(Number.MAX_SAFE_INTEGER),
+      expected: new Map<IntMode, number | bigint>([
+        [IntMode.UNSAFE_NUMBER, Number.MAX_SAFE_INTEGER],
+        [IntMode.SAFE_NUMBER, Number.MAX_SAFE_INTEGER],
+        [IntMode.MIXED, Number.MAX_SAFE_INTEGER],
+        [IntMode.BIGINT, BigInt(Number.MAX_SAFE_INTEGER)],
+      ])
+    },
+    MAX_SAFE_INTEGER_PLUS_ONE: {
+      input: BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1),
+      expected: new Map<IntMode, number | bigint | "error">([
+        // exclude IntMode.UNSAFE_NUMBER, behavior will not be exact
+        [IntMode.SAFE_NUMBER, "error"],
+        [IntMode.MIXED, BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1)],
+        [IntMode.BIGINT, BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1)],
+      ])
+    },
+    MIN_SAFE_INTEGER: {
+      input: BigInt(Number.MIN_SAFE_INTEGER),
+      expected: new Map<IntMode, number | bigint>([
+        [IntMode.UNSAFE_NUMBER, Number.MIN_SAFE_INTEGER],
+        [IntMode.SAFE_NUMBER, Number.MIN_SAFE_INTEGER],
+        [IntMode.MIXED, Number.MIN_SAFE_INTEGER],
+        [IntMode.BIGINT, BigInt(Number.MIN_SAFE_INTEGER)],
+      ])
+    },
+    MIN_SAFE_INTEGER_MINUS_ONE: {
+      input: BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1),
+      expected: new Map<IntMode, number | bigint | "error">([
+        // exclude IntMode.UNSAFE_NUMBER, behavior will not be exact
+        [IntMode.SAFE_NUMBER, "error"],
+        [IntMode.MIXED, BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1)],
+        [IntMode.BIGINT, BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1)],
+      ]),
+    },
+    INT64_MAX: {
+      input: BigInt("0x7fffffffffffffff"),
+      expected: new Map<IntMode, number | bigint | "error">([
+        // exclude IntMode.UNSAFE_NUMBER, behavior will not be exact
+        [IntMode.SAFE_NUMBER, "error"],
+        [IntMode.MIXED, BigInt("0x7fffffffffffffff")],
+        [IntMode.BIGINT, BigInt("0x7fffffffffffffff")],
+      ])
+    },
+    INT64_MIN: {
+      input: BigInt(-1) * BigInt("0x8000000000000000"),
+      expected: new Map<IntMode, number | bigint | "error">([
+        // exclude IntMode.UNSAFE_NUMBER, behavior will not be exact
+        [IntMode.SAFE_NUMBER, "error"],
+        [IntMode.MIXED, BigInt(-1) * BigInt("0x8000000000000000")],
+        [IntMode.BIGINT, BigInt(-1) * BigInt("0x8000000000000000")],
+      ]),
+    },
+  }
+}
+
+// declared as a function to delay referencing the BigInt constructor
+function BIGUINTSPECS(): Record<string, TestCase> {
+  return {
+    ZERO: {
+      input: BigInt(0),
+      expected: new Map<IntMode, number | bigint>([
+        [IntMode.UNSAFE_NUMBER, 0],
+        [IntMode.SAFE_NUMBER, 0],
+        [IntMode.MIXED, 0],
+        [IntMode.BIGINT, BigInt(0)],
+      ])
+    },
+    ONE: {
+      input: BigInt(1),
+      expected: new Map<IntMode, number | bigint>([
+        [IntMode.UNSAFE_NUMBER, 1],
+        [IntMode.SAFE_NUMBER, 1],
+        [IntMode.MIXED, 1],
+        [IntMode.BIGINT, BigInt(1)],
+      ])
+    },
+    X_FF: {
+      input: BigInt(0xff),
+      expected: new Map<IntMode, number | bigint>([
+        [IntMode.UNSAFE_NUMBER, 0xff],
+        [IntMode.SAFE_NUMBER, 0xff],
+        [IntMode.MIXED, 0xff],
+        [IntMode.BIGINT, BigInt(0xff)],
+      ])
+    },
+    UINT32_MAX: {
+      input: BigInt(0xffffffff),
+      expected: new Map<IntMode, number | bigint>([
+        [IntMode.UNSAFE_NUMBER, 0xffffffff],
+        [IntMode.SAFE_NUMBER, 0xffffffff],
+        [IntMode.MIXED, 0xffffffff],
+        [IntMode.BIGINT, BigInt(0xffffffff)],
+      ])
+    },
+    MAX_SAFE_INTEGER: {
+      input: BigInt(Number.MAX_SAFE_INTEGER),
+      expected: new Map<IntMode, number | bigint>([
+        [IntMode.UNSAFE_NUMBER, Number.MAX_SAFE_INTEGER],
+        [IntMode.SAFE_NUMBER, Number.MAX_SAFE_INTEGER],
+        [IntMode.MIXED, Number.MAX_SAFE_INTEGER],
+        [IntMode.BIGINT, BigInt(Number.MAX_SAFE_INTEGER)],
+      ])
+    },
+    MAX_SAFE_INTEGER_PLUS_ONE: {
+      input: BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1),
+      expected: new Map<IntMode, number | bigint | "error">([
+        // exclude IntMode.UNSAFE_NUMBER, behavior will not be exact
+        [IntMode.SAFE_NUMBER, "error"],
+        [IntMode.MIXED, BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1)],
+        [IntMode.BIGINT, BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1)],
+      ])
+    },
+    UINT64_MAX: {
+      input: BigInt("0xffffffffffffffff"),
+      expected: new Map<IntMode, number | bigint | "error">([
+        // exclude IntMode.UNSAFE_NUMBER, behavior will not be exact
+        [IntMode.SAFE_NUMBER, "error"],
+        [IntMode.MIXED, BigInt("0xffffffffffffffff")],
+        [IntMode.BIGINT, BigInt("0xffffffffffffffff")],
+      ])
+    },
+  }
+}
+
+function abs(value: bigint): bigint {
+  if (value < 0) {
+    return BigInt(-1) * value;
+  }
+  return value;
+}
+
 describe("codec BigInt", () => {
-  it("encodes and decodes 0n", () => {
-    const value = BigInt(0);
-    const encoded = encode(value, { extensionCodec });
-    assert.deepStrictEqual(decode(encoded, { extensionCodec }), value);
+  context("extension", () => {
+    it("encodes and decodes 0n", () => {
+      const value = BigInt(0);
+      const encoded = encode(value, { extensionCodec });
+      assert.deepStrictEqual(decode(encoded, { extensionCodec }), value);
+    });
+  
+    it("encodes and decodes MAX_SAFE_INTEGER+1", () => {
+      const value = BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1);
+      const encoded = encode(value, { extensionCodec });
+      assert.deepStrictEqual(decode(encoded, { extensionCodec }), value);
+    });
+  
+    it("encodes and decodes MIN_SAFE_INTEGER-1", () => {
+      const value = BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1);
+      const encoded = encode(value, { extensionCodec });
+      assert.deepStrictEqual(decode(encoded, { extensionCodec }), value);
+    });
   });
 
   it("encodes and decodes 100n", () => {
@@ -53,10 +272,47 @@ describe("codec BigInt", () => {
     const encoded = encode(value, { extensionCodec });
     assert.deepStrictEqual(decode(encoded, { extensionCodec }), value);
   });
+  context("native", () => {
+    context("int 64", () => {
+      const specs = BIGINTSPECS();
 
-  it("encodes and decodes MIN_SAFE_INTEGER-1", () => {
-    const value = BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1);
-    const encoded = encode(value, { extensionCodec });
-    assert.deepStrictEqual(decode(encoded, { extensionCodec }), value);
+      for (const name of Object.keys(specs)) {
+        const testCase = specs[name]!;
+  
+        it(`sets and gets ${testCase.input} (${testCase.input < 0 ? "-" : ""}0x${abs(testCase.input).toString(16)})`, () => {
+          const b = new Uint8Array(8);
+          const view = new DataView(b.buffer);
+          view.setBigInt64(0, testCase.input);
+          for (const [mode, expected] of testCase.expected) {
+            if (expected === "error") {
+              assert.throws(() => getInt64(view, 0, mode), new RegExp(`Mode is IntMode\\.SAFE_NUMBER and value is not a safe integer: ${testCase.input < 0 ? "-" : ""}0x${abs(testCase.input).toString(16)}$`));
+              continue;
+            }
+            assert.deepStrictEqual(getInt64(view, 0, mode), expected);
+          }
+        });
+      }
+    });
+
+    context("uint 64", () => {
+      const specs = BIGUINTSPECS();
+
+      for (const name of Object.keys(specs)) {
+        const testCase = specs[name]!;
+  
+        it(`sets and gets ${testCase.input} (0x${testCase.input.toString(16)})`, () => {
+          const b = new Uint8Array(8);
+          const view = new DataView(b.buffer);
+          view.setBigUint64(0, testCase.input);
+          for (const [mode, expected] of testCase.expected) {
+            if (expected === "error") {
+              assert.throws(() => getUint64(view, 0, mode), new RegExp(`Mode is IntMode\\.SAFE_NUMBER and value is not a safe integer: 0x${testCase.input.toString(16)}$`));
+              continue;
+            }
+            assert.deepStrictEqual(getUint64(view, 0, mode), expected);
+          }
+        });
+      }
+    });
   });
 });
diff --git a/test/codec-int.test.ts b/test/codec-int.test.ts
index 486f93b..7a1f106 100644
--- a/test/codec-int.test.ts
+++ b/test/codec-int.test.ts
@@ -1,5 +1,5 @@
 import assert from "assert";
-import { setInt64, getInt64, getUint64, setUint64 } from "../src/utils/int";
+import { IntMode, setInt64, getInt64, getUint64, setUint64 } from "../src/utils/int";
 
 const INT64SPECS = {
   ZERO: 0,
@@ -22,7 +22,12 @@ describe("codec: int64 / uint64", () => {
         const b = new Uint8Array(8);
         const view = new DataView(b.buffer);
         setInt64(view, 0, value);
-        assert.deepStrictEqual(getInt64(view, 0), value);
+        assert.deepStrictEqual(getInt64(view, 0, IntMode.UNSAFE_NUMBER), value);
+        assert.deepStrictEqual(getInt64(view, 0, IntMode.SAFE_NUMBER), value);
+        if (typeof BigInt !== "undefined") {
+          assert.deepStrictEqual(getInt64(view, 0, IntMode.MIXED), value);
+          assert.deepStrictEqual(getInt64(view, 0, IntMode.BIGINT), BigInt(value));
+        }
       });
     }
   });
@@ -32,14 +37,24 @@ describe("codec: int64 / uint64", () => {
       const b = new Uint8Array(8);
       const view = new DataView(b.buffer);
       setUint64(view, 0, 0);
-      assert.deepStrictEqual(getUint64(view, 0), 0);
+      assert.deepStrictEqual(getUint64(view, 0, IntMode.UNSAFE_NUMBER), 0);
+      assert.deepStrictEqual(getUint64(view, 0, IntMode.SAFE_NUMBER), 0);
+      if (typeof BigInt !== "undefined") {
+        assert.deepStrictEqual(getUint64(view, 0, IntMode.MIXED), 0);
+        assert.deepStrictEqual(getUint64(view, 0, IntMode.BIGINT), BigInt(0));
+      }
     });
 
     it(`sets and gets MAX_SAFE_INTEGER`, () => {
       const b = new Uint8Array(8);
       const view = new DataView(b.buffer);
       setUint64(view, 0, Number.MAX_SAFE_INTEGER);
-      assert.deepStrictEqual(getUint64(view, 0), Number.MAX_SAFE_INTEGER);
+      assert.deepStrictEqual(getUint64(view, 0, IntMode.UNSAFE_NUMBER), Number.MAX_SAFE_INTEGER);
+      assert.deepStrictEqual(getUint64(view, 0, IntMode.SAFE_NUMBER), Number.MAX_SAFE_INTEGER);
+      if (typeof BigInt !== "undefined") {
+        assert.deepStrictEqual(getUint64(view, 0, IntMode.MIXED), Number.MAX_SAFE_INTEGER);
+        assert.deepStrictEqual(getUint64(view, 0, IntMode.BIGINT), BigInt(Number.MAX_SAFE_INTEGER));
+      }
     });
   });
 });
diff --git a/test/encode.test.ts b/test/encode.test.ts
index d2679d5..cfa3efe 100644
--- a/test/encode.test.ts
+++ b/test/encode.test.ts
@@ -28,6 +28,10 @@ describe("encode", () => {
   context("forceFloat", () => {
     it("encodes integers as integers without forceIntegerToFloat", () => {
       assert.deepStrictEqual(encode(3), Uint8Array.from([0x3]));
+
+      if (typeof BigInt !== "undefined") {
+        assert.deepStrictEqual(encode(BigInt(3)), Uint8Array.from([0x3]));
+      }
     });
 
     it("encodes integers as floating point when forceIntegerToFloat=true", () => {
@@ -35,6 +39,13 @@ describe("encode", () => {
         encode(3, { forceIntegerToFloat: true }),
         Uint8Array.from([0xcb, 0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
       );
+
+      if (typeof BigInt !== "undefined") {
+        assert.deepStrictEqual(
+          encode(BigInt(3), { forceIntegerToFloat: true }),
+          Uint8Array.from([0xcb, 0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
+        );
+      }
     });
 
     it("encodes integers as float32 when forceIntegerToFloat=true and forceFloat32=true", () => {
@@ -42,10 +53,21 @@ describe("encode", () => {
         encode(3, { forceIntegerToFloat: true, forceFloat32: true }),
         Uint8Array.from([0xca, 0x40, 0x40, 0x00, 0x00]),
       );
+
+      if (typeof BigInt !== "undefined") {
+        assert.deepStrictEqual(
+          encode(BigInt(3), { forceIntegerToFloat: true, forceFloat32: true }),
+          Uint8Array.from([0xca, 0x40, 0x40, 0x00, 0x00]),
+        );
+      }
     });
 
     it("encodes integers as integers when forceIntegerToFloat=false", () => {
       assert.deepStrictEqual(encode(3, { forceIntegerToFloat: false }), Uint8Array.from([0x3]));
+
+      if (typeof BigInt !== "undefined") {
+        assert.deepStrictEqual(encode(BigInt(3), { forceIntegerToFloat: false }), Uint8Array.from([0x3]));
+      }
     });
   });
 
@@ -71,4 +93,14 @@ describe("encode", () => {
     const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteLength);
     assert.deepStrictEqual(decode(arrayBuffer), decode(buffer));
   });
+
+  context("Bigint that exceeds 64 bits", () => {
+    if (typeof BigInt !== "undefined") {
+      const MAX_UINT64_PLUS_ONE = BigInt("0x10000000000000000");
+      assert.throws(() => encode(MAX_UINT64_PLUS_ONE), /Bigint is too large for uint64: 18446744073709551616$/);
+
+      const MIN_INT64_MINUS_ONE = BigInt(-1) * BigInt("0x8000000000000001");
+      assert.throws(() => encode(MIN_INT64_MINUS_ONE), /Bigint is too small for int64: -9223372036854775809$/);
+    }
+  });
 });
diff --git a/test/msgpack-test-suite.test.ts b/test/msgpack-test-suite.test.ts
index 6800973..42c220c 100644
--- a/test/msgpack-test-suite.test.ts
+++ b/test/msgpack-test-suite.test.ts
@@ -2,7 +2,8 @@ import assert from "assert";
 import util from "util";
 import { Exam } from "msgpack-test-js";
 import { MsgTimestamp } from "msg-timestamp";
-import { encode, decode, ExtensionCodec, EXT_TIMESTAMP, encodeTimeSpecToTimestamp } from "@msgpack/msgpack";
+import { MsgUInt64, MsgInt64 } from "msg-int64";
+import { encode, decode, ExtensionCodec, EXT_TIMESTAMP, encodeTimeSpecToTimestamp, IntMode } from "@msgpack/msgpack";
 
 const extensionCodec = new ExtensionCodec();
 extensionCodec.register({
@@ -24,7 +25,7 @@ extensionCodec.register({
 
 const TEST_TYPES = {
   array: 1,
-  bignum: 0, // TODO
+  bignum: typeof BigInt !== "undefined",
   binary: 1,
   bool: 1,
   map: 1,
@@ -34,6 +35,22 @@ const TEST_TYPES = {
   timestamp: 1,
 };
 
+function convertValueForEncoding(value: unknown): unknown {
+  if (value instanceof MsgInt64 || value instanceof MsgUInt64) {
+    return BigInt(value.toString());
+  }
+
+  return value;
+}
+
+function convertValueForDecoding(value: unknown): unknown {
+  if (typeof value === "bigint") {
+    return value.toString();
+  }
+
+  return value;
+}
+
 describe("msgpack-test-suite", () => {
   Exam.getExams(TEST_TYPES).forEach((exam) => {
     const types = exam.getTypes(TEST_TYPES);
@@ -41,7 +58,7 @@ describe("msgpack-test-suite", () => {
     const title = `${first}: ${exam.stringify(first)}`;
     it(`encodes ${title}`, () => {
       types.forEach((type) => {
-        const value = exam.getValue(type);
+        const value = convertValueForEncoding(exam.getValue(type));
         const buffer = Buffer.from(encode(value, { extensionCodec }));
 
         if (exam.matchMsgpack(buffer)) {
@@ -58,7 +75,7 @@ describe("msgpack-test-suite", () => {
     it(`decodes ${title}`, () => {
       const msgpacks = exam.getMsgpacks();
       msgpacks.forEach((encoded, idx) => {
-        const value = decode(encoded, { extensionCodec });
+        const value = convertValueForDecoding(decode(encoded, { extensionCodec, intMode: IntMode.MIXED }));
         if (exam.matchValue(value)) {
           assert(true, exam.stringify(idx));
         } else {