@marcuspuchalla/nachos 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/LICENSE +674 -0
- package/README.md +345 -0
- package/dist/chunk-2FUTHZQQ.cjs +755 -0
- package/dist/chunk-2FUTHZQQ.cjs.map +1 -0
- package/dist/chunk-2HBCILJS.cjs +2034 -0
- package/dist/chunk-2HBCILJS.cjs.map +1 -0
- package/dist/chunk-7CFYWHS6.js +742 -0
- package/dist/chunk-7CFYWHS6.js.map +1 -0
- package/dist/chunk-PD72MVTX.cjs +160 -0
- package/dist/chunk-PD72MVTX.cjs.map +1 -0
- package/dist/chunk-ZDZ2B5PE.js +149 -0
- package/dist/chunk-ZDZ2B5PE.js.map +1 -0
- package/dist/chunk-ZRPJUEIZ.js +2020 -0
- package/dist/chunk-ZRPJUEIZ.js.map +1 -0
- package/dist/encoder/index.cjs +57 -0
- package/dist/encoder/index.cjs.map +1 -0
- package/dist/encoder/index.d.cts +72 -0
- package/dist/encoder/index.d.ts +72 -0
- package/dist/encoder/index.js +4 -0
- package/dist/encoder/index.js.map +1 -0
- package/dist/index.cjs +606 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +494 -0
- package/dist/index.d.ts +494 -0
- package/dist/index.js +523 -0
- package/dist/index.js.map +1 -0
- package/dist/metafile-cjs.json +1 -0
- package/dist/metafile-esm.json +1 -0
- package/dist/parser/index.cjs +85 -0
- package/dist/parser/index.cjs.map +1 -0
- package/dist/parser/index.d.cts +72 -0
- package/dist/parser/index.d.ts +72 -0
- package/dist/parser/index.js +4 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/types-DvNlfbKB.d.cts +301 -0
- package/dist/types-DvNlfbKB.d.ts +301 -0
- package/dist/useCborSimpleEncoder-ButVU988.d.cts +268 -0
- package/dist/useCborSimpleEncoder-TVxzNJ_9.d.ts +268 -0
- package/dist/useCborTag-B_iaShG6.d.ts +142 -0
- package/dist/useCborTag-BfTIV8HM.d.cts +142 -0
- package/package.json +102 -0
- package/src/__tests__/public-api.test.ts +326 -0
- package/src/encoder/__tests__/cbor-collection-encoder.test.ts +331 -0
- package/src/encoder/__tests__/cbor-integer-encoder.test.ts +283 -0
- package/src/encoder/__tests__/cbor-simple-encoder.test.ts +224 -0
- package/src/encoder/__tests__/cbor-string-encoder.test.ts +345 -0
- package/src/encoder/__tests__/cbor-tag-encoder.test.ts +565 -0
- package/src/encoder/composables/#useCborTagEncoder.ts# +158 -0
- package/src/encoder/composables/useCborCollectionEncoder.ts +424 -0
- package/src/encoder/composables/useCborEncoder.ts +203 -0
- package/src/encoder/composables/useCborIntegerEncoder.ts +188 -0
- package/src/encoder/composables/useCborSimpleEncoder.ts +266 -0
- package/src/encoder/composables/useCborStringEncoder.ts +266 -0
- package/src/encoder/composables/useCborTagEncoder.ts +158 -0
- package/src/encoder/index.ts +35 -0
- package/src/encoder/types.ts +88 -0
- package/src/encoder/utils.ts +80 -0
- package/src/index.ts +434 -0
- package/src/parser/__tests__/ast-tree-structure.test.ts +311 -0
- package/src/parser/__tests__/cbor-collection-errors.test.ts +296 -0
- package/src/parser/__tests__/cbor-collection.test.ts +369 -0
- package/src/parser/__tests__/cbor-deterministic-encoding.test.ts +432 -0
- package/src/parser/__tests__/cbor-diagnostic.test.ts +333 -0
- package/src/parser/__tests__/cbor-duplicate-keys.test.ts +235 -0
- package/src/parser/__tests__/cbor-float-errors.test.ts +222 -0
- package/src/parser/__tests__/cbor-float.test.ts +502 -0
- package/src/parser/__tests__/cbor-integer-errors.test.ts +139 -0
- package/src/parser/__tests__/cbor-integer.test.ts +200 -0
- package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +403 -0
- package/src/parser/__tests__/cbor-parser-errors.test.ts +126 -0
- package/src/parser/__tests__/cbor-security-dos-protection.test.ts +503 -0
- package/src/parser/__tests__/cbor-sequences.test.ts +150 -0
- package/src/parser/__tests__/cbor-source-map.test.ts +321 -0
- package/src/parser/__tests__/cbor-standard-tags.test.ts +340 -0
- package/src/parser/__tests__/cbor-string-errors.test.ts +227 -0
- package/src/parser/__tests__/cbor-string.test.ts +224 -0
- package/src/parser/__tests__/cbor-tag-advanced.test.ts +500 -0
- package/src/parser/__tests__/cbor-tag-errors.test.ts +447 -0
- package/src/parser/__tests__/cbor-tag-source-map.test.ts +360 -0
- package/src/parser/__tests__/cbor-tag.test.ts +684 -0
- package/src/parser/__tests__/extreme-edge-cases.test.ts +146 -0
- package/src/parser/__tests__/pathBuilder.test.ts +256 -0
- package/src/parser/__tests__/rfc-test-vectors.test.ts +607 -0
- package/src/parser/__tests__/security-limits.test.ts +248 -0
- package/src/parser/__tests__/utils-errors.test.ts +127 -0
- package/src/parser/composables/useCborCollection.ts +509 -0
- package/src/parser/composables/useCborDiagnostic.ts +381 -0
- package/src/parser/composables/useCborFloat.ts +256 -0
- package/src/parser/composables/useCborInteger.ts +114 -0
- package/src/parser/composables/useCborParser.ts +951 -0
- package/src/parser/composables/useCborString.ts +330 -0
- package/src/parser/composables/useCborStringTypes.ts +129 -0
- package/src/parser/composables/useCborTag.ts +739 -0
- package/src/parser/index.ts +56 -0
- package/src/parser/types.ts +371 -0
- package/src/parser/utils/pathBuilder.ts +259 -0
- package/src/parser/utils.ts +398 -0
- package/src/utils/__tests__/logger.test.ts +186 -0
- package/src/utils/logger.ts +96 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Integer Encoder Composable
|
|
3
|
+
* Handles Major Type 0 (Unsigned) and Major Type 1 (Negative)
|
|
4
|
+
* Following RFC 8949 specification
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { EncodeResult } from '../types'
|
|
8
|
+
import { bytesToHex, writeUint, writeBigUint } from '../utils'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* CBOR Integer Encoder Composable
|
|
12
|
+
*
|
|
13
|
+
* Provides functions to encode integers to CBOR format:
|
|
14
|
+
* - Major Type 0: Unsigned integers (0 to 2^64-1)
|
|
15
|
+
* - Major Type 1: Negative integers (-1 to -2^64)
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const { encodeUnsignedInt, encodeNegativeInt, encodeInteger } = useCborIntegerEncoder()
|
|
20
|
+
*
|
|
21
|
+
* // Encode unsigned integer
|
|
22
|
+
* const result1 = encodeUnsignedInt(100)
|
|
23
|
+
* // result1: { bytes: Uint8Array([0x18, 0x64]), hex: '1864' }
|
|
24
|
+
*
|
|
25
|
+
* // Encode negative integer
|
|
26
|
+
* const result2 = encodeNegativeInt(-100)
|
|
27
|
+
* // result2: { bytes: Uint8Array([0x38, 0x63]), hex: '3863' }
|
|
28
|
+
*
|
|
29
|
+
* // Auto-detect integer type
|
|
30
|
+
* const result3 = encodeInteger(-100)
|
|
31
|
+
* // result3: { bytes: Uint8Array([0x38, 0x63]), hex: '3863' }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function useCborIntegerEncoder() {
|
|
35
|
+
/**
|
|
36
|
+
* Encode unsigned integer (Major Type 0)
|
|
37
|
+
*
|
|
38
|
+
* Encoding rules:
|
|
39
|
+
* - 0-23: Direct encoding in initial byte (0x00-0x17)
|
|
40
|
+
* - 24-255: 0x18 + 1 byte
|
|
41
|
+
* - 256-65535: 0x19 + 2 bytes
|
|
42
|
+
* - 65536-4294967295: 0x1a + 4 bytes
|
|
43
|
+
* - 4294967296-2^64-1: 0x1b + 8 bytes
|
|
44
|
+
*
|
|
45
|
+
* @param value - Unsigned integer (0 to 2^64-1)
|
|
46
|
+
* @returns Encoded CBOR bytes and hex string
|
|
47
|
+
* @throws Error if value is negative or >= 2^64
|
|
48
|
+
*/
|
|
49
|
+
const encodeUnsignedInt = (value: number | bigint): EncodeResult => {
|
|
50
|
+
// Convert to BigInt for consistent handling
|
|
51
|
+
const bigValue = typeof value === 'bigint' ? value : BigInt(value)
|
|
52
|
+
|
|
53
|
+
// Validate value is non-negative
|
|
54
|
+
if (bigValue < 0n) {
|
|
55
|
+
throw new Error('Cannot encode negative value as unsigned integer')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Validate value doesn't exceed 2^64-1
|
|
59
|
+
const MAX_UINT64 = 18446744073709551615n // 2^64 - 1
|
|
60
|
+
if (bigValue > MAX_UINT64) {
|
|
61
|
+
throw new Error('Value exceeds maximum unsigned integer (2^64-1)')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let bytes: Uint8Array
|
|
65
|
+
|
|
66
|
+
// Direct encoding (0-23)
|
|
67
|
+
if (bigValue <= 23n) {
|
|
68
|
+
bytes = new Uint8Array([Number(bigValue)])
|
|
69
|
+
}
|
|
70
|
+
// 1-byte encoding (24-255)
|
|
71
|
+
else if (bigValue <= 255n) {
|
|
72
|
+
bytes = new Uint8Array([0x18, Number(bigValue)])
|
|
73
|
+
}
|
|
74
|
+
// 2-byte encoding (256-65535)
|
|
75
|
+
else if (bigValue <= 65535n) {
|
|
76
|
+
const valueBytes = writeUint(Number(bigValue), 2)
|
|
77
|
+
bytes = new Uint8Array([0x19, ...valueBytes])
|
|
78
|
+
}
|
|
79
|
+
// 4-byte encoding (65536-4294967295)
|
|
80
|
+
else if (bigValue <= 4294967295n) {
|
|
81
|
+
const valueBytes = writeUint(Number(bigValue), 4)
|
|
82
|
+
bytes = new Uint8Array([0x1a, ...valueBytes])
|
|
83
|
+
}
|
|
84
|
+
// 8-byte encoding (> 4294967295)
|
|
85
|
+
else {
|
|
86
|
+
const valueBytes = writeBigUint(bigValue, 8)
|
|
87
|
+
bytes = new Uint8Array([0x1b, ...valueBytes])
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
bytes,
|
|
92
|
+
hex: bytesToHex(bytes)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Encode negative integer (Major Type 1)
|
|
98
|
+
*
|
|
99
|
+
* CBOR encodes negative integers as: -1 - N
|
|
100
|
+
* where N is the encoded value
|
|
101
|
+
*
|
|
102
|
+
* Encoding rules:
|
|
103
|
+
* - -1 to -24: Direct encoding in initial byte (0x20-0x37)
|
|
104
|
+
* - -25 to -256: 0x38 + 1 byte
|
|
105
|
+
* - -257 to -65536: 0x39 + 2 bytes
|
|
106
|
+
* - -65537 to -4294967296: 0x3a + 4 bytes
|
|
107
|
+
* - -4294967297 to -2^64: 0x3b + 8 bytes
|
|
108
|
+
*
|
|
109
|
+
* @param value - Negative integer (-1 to -2^64)
|
|
110
|
+
* @returns Encoded CBOR bytes and hex string
|
|
111
|
+
* @throws Error if value is non-negative or < -2^64
|
|
112
|
+
*/
|
|
113
|
+
const encodeNegativeInt = (value: number | bigint): EncodeResult => {
|
|
114
|
+
// Convert to BigInt for consistent handling
|
|
115
|
+
const bigValue = typeof value === 'bigint' ? value : BigInt(value)
|
|
116
|
+
|
|
117
|
+
// Validate value is negative
|
|
118
|
+
if (bigValue >= 0n) {
|
|
119
|
+
throw new Error('Cannot encode positive value as negative integer')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Validate value doesn't exceed -2^64
|
|
123
|
+
const MIN_INT64 = -18446744073709551616n // -2^64
|
|
124
|
+
if (bigValue < MIN_INT64) {
|
|
125
|
+
throw new Error('Value exceeds minimum negative integer (-2^64)')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// CBOR encodes negative as: -1 - N
|
|
129
|
+
// So for value V, we encode N = -1 - V
|
|
130
|
+
const encoded = -1n - bigValue
|
|
131
|
+
|
|
132
|
+
let bytes: Uint8Array
|
|
133
|
+
|
|
134
|
+
// Direct encoding (-1 to -24, encoded as 0-23)
|
|
135
|
+
if (encoded <= 23n) {
|
|
136
|
+
bytes = new Uint8Array([0x20 + Number(encoded)])
|
|
137
|
+
}
|
|
138
|
+
// 1-byte encoding (-25 to -256, encoded as 24-255)
|
|
139
|
+
else if (encoded <= 255n) {
|
|
140
|
+
bytes = new Uint8Array([0x38, Number(encoded)])
|
|
141
|
+
}
|
|
142
|
+
// 2-byte encoding (-257 to -65536, encoded as 256-65535)
|
|
143
|
+
else if (encoded <= 65535n) {
|
|
144
|
+
const valueBytes = writeUint(Number(encoded), 2)
|
|
145
|
+
bytes = new Uint8Array([0x39, ...valueBytes])
|
|
146
|
+
}
|
|
147
|
+
// 4-byte encoding (-65537 to -4294967296, encoded as 65536-4294967295)
|
|
148
|
+
else if (encoded <= 4294967295n) {
|
|
149
|
+
const valueBytes = writeUint(Number(encoded), 4)
|
|
150
|
+
bytes = new Uint8Array([0x3a, ...valueBytes])
|
|
151
|
+
}
|
|
152
|
+
// 8-byte encoding (< -4294967296)
|
|
153
|
+
else {
|
|
154
|
+
const valueBytes = writeBigUint(encoded, 8)
|
|
155
|
+
bytes = new Uint8Array([0x3b, ...valueBytes])
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
bytes,
|
|
160
|
+
hex: bytesToHex(bytes)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Encode integer (auto-detect type)
|
|
166
|
+
*
|
|
167
|
+
* Automatically selects Major Type 0 (unsigned) or Major Type 1 (negative)
|
|
168
|
+
* based on the sign of the value.
|
|
169
|
+
*
|
|
170
|
+
* @param value - Any integer
|
|
171
|
+
* @returns Encoded CBOR bytes and hex string
|
|
172
|
+
*/
|
|
173
|
+
const encodeInteger = (value: number | bigint): EncodeResult => {
|
|
174
|
+
const bigValue = typeof value === 'bigint' ? value : BigInt(value)
|
|
175
|
+
|
|
176
|
+
if (bigValue < 0n) {
|
|
177
|
+
return encodeNegativeInt(bigValue)
|
|
178
|
+
} else {
|
|
179
|
+
return encodeUnsignedInt(bigValue)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
encodeUnsignedInt,
|
|
185
|
+
encodeNegativeInt,
|
|
186
|
+
encodeInteger
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Simple Values and Floats Encoder Composable
|
|
3
|
+
* Handles Major Type 7 (Floats and Simple Values)
|
|
4
|
+
* Following RFC 8949 specification
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { EncodeResult, EncodeOptions } from '../types'
|
|
8
|
+
import { bytesToHex } from '../utils'
|
|
9
|
+
import { useCborIntegerEncoder } from './useCborIntegerEncoder'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* CBOR Simple Values and Floats Encoder Composable
|
|
13
|
+
*
|
|
14
|
+
* Provides functions to encode:
|
|
15
|
+
* - Simple values: false, true, null, undefined
|
|
16
|
+
* - Floating-point numbers: float16, float32, float64
|
|
17
|
+
*
|
|
18
|
+
* @param options - Global encoder options
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* const { encodeSimple, encodeFloat } = useCborSimpleEncoder()
|
|
23
|
+
*
|
|
24
|
+
* // Encode simple values
|
|
25
|
+
* const result1 = encodeSimple(true)
|
|
26
|
+
* // result1: { bytes: Uint8Array([0xf5]), hex: 'f5' }
|
|
27
|
+
*
|
|
28
|
+
* // Encode float
|
|
29
|
+
* const result2 = encodeFloat(1.1)
|
|
30
|
+
* // result2: { bytes: Uint8Array([0xfb, ...]), hex: 'fb...' }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function useCborSimpleEncoder(_globalOptions?: Partial<EncodeOptions>) {
|
|
34
|
+
const { encodeInteger } = useCborIntegerEncoder()
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Encode simple values (booleans, null, undefined)
|
|
38
|
+
*
|
|
39
|
+
* @param value - Simple value to encode
|
|
40
|
+
* @returns Encoded CBOR bytes and hex string
|
|
41
|
+
*/
|
|
42
|
+
const encodeSimple = (value: boolean | null | undefined): EncodeResult => {
|
|
43
|
+
let byte: number
|
|
44
|
+
|
|
45
|
+
if (value === false) {
|
|
46
|
+
byte = 0xf4
|
|
47
|
+
} else if (value === true) {
|
|
48
|
+
byte = 0xf5
|
|
49
|
+
} else if (value === null) {
|
|
50
|
+
byte = 0xf6
|
|
51
|
+
} else if (value === undefined) {
|
|
52
|
+
byte = 0xf7
|
|
53
|
+
} else {
|
|
54
|
+
throw new Error(`Unsupported simple value: ${value}`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
bytes: new Uint8Array([byte]),
|
|
59
|
+
hex: bytesToHex(new Uint8Array([byte]))
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Encode float16 (IEEE 754 half precision)
|
|
65
|
+
*
|
|
66
|
+
* @param value - Number to encode
|
|
67
|
+
* @returns Uint8Array with float16 bytes (2 bytes)
|
|
68
|
+
*/
|
|
69
|
+
const encodeFloat16Bytes = (value: number): Uint8Array => {
|
|
70
|
+
// Handle special cases
|
|
71
|
+
if (value === 0) return new Uint8Array([0x00, 0x00])
|
|
72
|
+
if (value === -0) return new Uint8Array([0x80, 0x00])
|
|
73
|
+
if (Number.isNaN(value)) return new Uint8Array([0x7e, 0x00])
|
|
74
|
+
if (value === Infinity) return new Uint8Array([0x7c, 0x00])
|
|
75
|
+
if (value === -Infinity) return new Uint8Array([0xfc, 0x00])
|
|
76
|
+
|
|
77
|
+
// Convert float64 to float16
|
|
78
|
+
const sign = value < 0 ? 1 : 0
|
|
79
|
+
const absValue = Math.abs(value)
|
|
80
|
+
|
|
81
|
+
// Get exponent and mantissa from float64
|
|
82
|
+
const buffer = new ArrayBuffer(8)
|
|
83
|
+
const view = new DataView(buffer)
|
|
84
|
+
view.setFloat64(0, absValue, false)
|
|
85
|
+
|
|
86
|
+
const bits = view.getBigUint64(0, false)
|
|
87
|
+
const exp64 = Number((bits >> 52n) & 0x7ffn) - 1023
|
|
88
|
+
const mant64 = Number(bits & 0xfffffffffffffn)
|
|
89
|
+
|
|
90
|
+
// Convert to float16 range
|
|
91
|
+
let exp16: number
|
|
92
|
+
let mant16: number
|
|
93
|
+
|
|
94
|
+
if (exp64 < -14) {
|
|
95
|
+
// Subnormal or zero
|
|
96
|
+
exp16 = 0
|
|
97
|
+
mant16 = 0
|
|
98
|
+
} else if (exp64 > 15) {
|
|
99
|
+
// Overflow to infinity
|
|
100
|
+
exp16 = 31
|
|
101
|
+
mant16 = 0
|
|
102
|
+
} else {
|
|
103
|
+
// Normal number
|
|
104
|
+
exp16 = exp64 + 15
|
|
105
|
+
// Take top 10 bits of mantissa
|
|
106
|
+
mant16 = mant64 >> 42
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const float16 = (sign << 15) | (exp16 << 10) | mant16
|
|
110
|
+
|
|
111
|
+
return new Uint8Array([
|
|
112
|
+
(float16 >> 8) & 0xff,
|
|
113
|
+
float16 & 0xff
|
|
114
|
+
])
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Encode float32 (IEEE 754 single precision)
|
|
119
|
+
*
|
|
120
|
+
* @param value - Number to encode
|
|
121
|
+
* @returns Uint8Array with float32 bytes (4 bytes)
|
|
122
|
+
*/
|
|
123
|
+
const encodeFloat32Bytes = (value: number): Uint8Array => {
|
|
124
|
+
const buffer = new ArrayBuffer(4)
|
|
125
|
+
const view = new DataView(buffer)
|
|
126
|
+
view.setFloat32(0, value, false) // Big-endian
|
|
127
|
+
|
|
128
|
+
return new Uint8Array(buffer)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Encode float64 (IEEE 754 double precision)
|
|
133
|
+
*
|
|
134
|
+
* @param value - Number to encode
|
|
135
|
+
* @returns Uint8Array with float64 bytes (8 bytes)
|
|
136
|
+
*/
|
|
137
|
+
const encodeFloat64Bytes = (value: number): Uint8Array => {
|
|
138
|
+
const buffer = new ArrayBuffer(8)
|
|
139
|
+
const view = new DataView(buffer)
|
|
140
|
+
view.setFloat64(0, value, false) // Big-endian
|
|
141
|
+
|
|
142
|
+
return new Uint8Array(buffer)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Check if a number can be exactly represented as float16
|
|
147
|
+
*
|
|
148
|
+
* @param value - Number to check
|
|
149
|
+
* @returns True if can be represented as float16
|
|
150
|
+
*/
|
|
151
|
+
const canBeFloat16 = (value: number): boolean => {
|
|
152
|
+
// Special values
|
|
153
|
+
if (!Number.isFinite(value) || value === 0 || value === -0) {
|
|
154
|
+
return true
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check range
|
|
158
|
+
const absValue = Math.abs(value)
|
|
159
|
+
if (absValue < 0.00006103515625 || absValue > 65504) {
|
|
160
|
+
return false
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Encode and decode to check for precision loss
|
|
164
|
+
const bytes = encodeFloat16Bytes(value)
|
|
165
|
+
const view = new DataView(bytes.buffer)
|
|
166
|
+
const bits = view.getUint16(0, false)
|
|
167
|
+
|
|
168
|
+
const sign = (bits >> 15) & 0x1
|
|
169
|
+
const exp = (bits >> 10) & 0x1f
|
|
170
|
+
const mant = bits & 0x3ff
|
|
171
|
+
|
|
172
|
+
let decoded: number
|
|
173
|
+
|
|
174
|
+
if (exp === 0) {
|
|
175
|
+
decoded = (sign ? -1 : 1) * Math.pow(2, -14) * (mant / 1024)
|
|
176
|
+
} else if (exp === 31) {
|
|
177
|
+
decoded = mant === 0 ? (sign ? -Infinity : Infinity) : NaN
|
|
178
|
+
} else {
|
|
179
|
+
decoded = (sign ? -1 : 1) * Math.pow(2, exp - 15) * (1 + mant / 1024)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return decoded === value
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Check if a number can be exactly represented as float32
|
|
187
|
+
*
|
|
188
|
+
* @param value - Number to check
|
|
189
|
+
* @returns True if can be represented as float32
|
|
190
|
+
*/
|
|
191
|
+
const canBeFloat32 = (value: number): boolean => {
|
|
192
|
+
// Special values
|
|
193
|
+
if (!Number.isFinite(value) || value === 0 || value === -0) {
|
|
194
|
+
return true
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Encode as float32 and decode back to check for precision loss
|
|
198
|
+
const buffer = new ArrayBuffer(4)
|
|
199
|
+
const view = new DataView(buffer)
|
|
200
|
+
view.setFloat32(0, value, false)
|
|
201
|
+
const decoded = view.getFloat32(0, false)
|
|
202
|
+
|
|
203
|
+
return decoded === value
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Check if a number is an integer
|
|
208
|
+
*
|
|
209
|
+
* @param value - Number to check
|
|
210
|
+
* @returns True if value is an integer
|
|
211
|
+
*/
|
|
212
|
+
const isInteger = (value: number): boolean => {
|
|
213
|
+
return Number.isInteger(value) && Number.isSafeInteger(value)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Encode floating-point number
|
|
218
|
+
*
|
|
219
|
+
* @param value - Number to encode
|
|
220
|
+
* @param precision - Optional precision (16, 32, or 64). If not specified, uses smallest possible.
|
|
221
|
+
* @returns Encoded CBOR bytes and hex string
|
|
222
|
+
*/
|
|
223
|
+
const encodeFloat = (value: number, precision?: 16 | 32 | 64): EncodeResult => {
|
|
224
|
+
let bytes: Uint8Array
|
|
225
|
+
|
|
226
|
+
// If value is an integer, encode as integer
|
|
227
|
+
if (isInteger(value) && !precision) {
|
|
228
|
+
return encodeInteger(value)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// If precision is specified, use it
|
|
232
|
+
if (precision === 16) {
|
|
233
|
+
const floatBytes = encodeFloat16Bytes(value)
|
|
234
|
+
bytes = new Uint8Array([0xf9, ...floatBytes])
|
|
235
|
+
} else if (precision === 32) {
|
|
236
|
+
const floatBytes = encodeFloat32Bytes(value)
|
|
237
|
+
bytes = new Uint8Array([0xfa, ...floatBytes])
|
|
238
|
+
} else if (precision === 64) {
|
|
239
|
+
const floatBytes = encodeFloat64Bytes(value)
|
|
240
|
+
bytes = new Uint8Array([0xfb, ...floatBytes])
|
|
241
|
+
} else {
|
|
242
|
+
// Auto-detect smallest precision
|
|
243
|
+
if (canBeFloat16(value)) {
|
|
244
|
+
const floatBytes = encodeFloat16Bytes(value)
|
|
245
|
+
bytes = new Uint8Array([0xf9, ...floatBytes])
|
|
246
|
+
} else if (canBeFloat32(value)) {
|
|
247
|
+
const floatBytes = encodeFloat32Bytes(value)
|
|
248
|
+
bytes = new Uint8Array([0xfa, ...floatBytes])
|
|
249
|
+
} else {
|
|
250
|
+
// Use float64 for best precision
|
|
251
|
+
const floatBytes = encodeFloat64Bytes(value)
|
|
252
|
+
bytes = new Uint8Array([0xfb, ...floatBytes])
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
bytes,
|
|
258
|
+
hex: bytesToHex(bytes)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
encodeSimple,
|
|
264
|
+
encodeFloat
|
|
265
|
+
}
|
|
266
|
+
}
|