arithcode 0.0.0-stage → 0.0.1
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/LICENSE +21 -0
- package/README.md +2 -2
- package/dist/index.d.mts +133 -0
- package/dist/index.mjs +386 -0
- package/package.json +40 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 aqu3180
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
#
|
|
1
|
+
# arithcode
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Arithmetic coding + String coder
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export declare function frac(num: number | bigint, den: number | bigint): Fraction;
|
|
2
|
+
export declare function frac(value: number | bigint | Fraction): Fraction;
|
|
3
|
+
export declare class Fraction {
|
|
4
|
+
private static readonly view;
|
|
5
|
+
static commonDenominator(a: Fraction, b: Fraction): {
|
|
6
|
+
den: bigint;
|
|
7
|
+
a: bigint;
|
|
8
|
+
b: bigint;
|
|
9
|
+
};
|
|
10
|
+
num: bigint;
|
|
11
|
+
den: bigint;
|
|
12
|
+
constructor(num: number | bigint, den: number | bigint);
|
|
13
|
+
constructor(value: number | bigint | Fraction);
|
|
14
|
+
isLessThan(b: Fraction): boolean;
|
|
15
|
+
isGreaterThan(b: Fraction): boolean;
|
|
16
|
+
isLessThanOrEqualTo(b: Fraction): boolean;
|
|
17
|
+
isGreaterThanOrEqualTo(b: Fraction): boolean;
|
|
18
|
+
reduce(): void;
|
|
19
|
+
toReduced(): Fraction;
|
|
20
|
+
toString(): string;
|
|
21
|
+
}
|
|
22
|
+
export declare class ArithmeticRange {
|
|
23
|
+
den: bigint;
|
|
24
|
+
low: bigint;
|
|
25
|
+
high: bigint;
|
|
26
|
+
constructor(den?: bigint, low?: bigint, high?: bigint);
|
|
27
|
+
get isValid(): boolean;
|
|
28
|
+
get denAbs(): bigint;
|
|
29
|
+
get lowAbs(): bigint;
|
|
30
|
+
get highAbs(): bigint;
|
|
31
|
+
toString(): string;
|
|
32
|
+
}
|
|
33
|
+
export declare class ArithmeticValue extends Fraction {
|
|
34
|
+
get isValid(): boolean;
|
|
35
|
+
get denAbs(): bigint;
|
|
36
|
+
get numAbs(): bigint;
|
|
37
|
+
}
|
|
38
|
+
export declare abstract class StringCoder {
|
|
39
|
+
abstract encode(range: ArithmeticRange): string;
|
|
40
|
+
abstract decode(code: string): ArithmeticValue;
|
|
41
|
+
}
|
|
42
|
+
export declare abstract class DigitCoder extends StringCoder {
|
|
43
|
+
protected abstract charMap: string;
|
|
44
|
+
encode(range: ArithmeticRange): string;
|
|
45
|
+
decode(code: string): ArithmeticValue;
|
|
46
|
+
}
|
|
47
|
+
export declare class Base64Coder extends DigitCoder {
|
|
48
|
+
protected charMap: string;
|
|
49
|
+
}
|
|
50
|
+
export declare const BASE64_CODER: Base64Coder;
|
|
51
|
+
export declare class Base64UrlCoder extends DigitCoder {
|
|
52
|
+
protected charMap: string;
|
|
53
|
+
}
|
|
54
|
+
export declare const BASE64URL_CODER: Base64UrlCoder;
|
|
55
|
+
export declare class Z85Coder extends DigitCoder {
|
|
56
|
+
protected charMap: string;
|
|
57
|
+
}
|
|
58
|
+
export declare const Z85_CODER: Z85Coder;
|
|
59
|
+
export declare class Base94Coder extends DigitCoder {
|
|
60
|
+
protected charMap: string;
|
|
61
|
+
}
|
|
62
|
+
export declare const BASE94_CODER: Base94Coder;
|
|
63
|
+
export interface DistributionLike<T = number[]> {
|
|
64
|
+
encode(input: T): ArithmeticRange;
|
|
65
|
+
decode(value: ArithmeticValue): T;
|
|
66
|
+
}
|
|
67
|
+
export type DistributionInput<T extends DistributionLike<unknown>> = T extends DistributionLike<infer I> ? I : never;
|
|
68
|
+
export declare class Protocol<T extends DistributionLike<unknown> = Distribution> {
|
|
69
|
+
distribution: T;
|
|
70
|
+
coder: StringCoder;
|
|
71
|
+
constructor(distribution: T, coder: StringCoder);
|
|
72
|
+
encode(data: DistributionInput<T>): string;
|
|
73
|
+
decode(code: string): DistributionInput<T>;
|
|
74
|
+
}
|
|
75
|
+
type _TupleIndicesRecursive<T extends unknown[], R extends unknown[]> = T['length'] extends R['length'] ? R[number] : _TupleIndicesRecursive<T, [...R, R['length']]>;
|
|
76
|
+
type TupleIndices<T extends unknown[]> = number extends T['length'] ? number : _TupleIndicesRecursive<T, []>;
|
|
77
|
+
export declare abstract class Distribution implements DistributionLike {
|
|
78
|
+
private currentInput;
|
|
79
|
+
private currentOutput;
|
|
80
|
+
private currentArrIdx;
|
|
81
|
+
encode(data: number[]): ArithmeticRange;
|
|
82
|
+
decode(value: Fraction): number[];
|
|
83
|
+
private encodeRange;
|
|
84
|
+
private decodeRange;
|
|
85
|
+
/**
|
|
86
|
+
* @example
|
|
87
|
+
* class MyDistribution extends Distribution {
|
|
88
|
+
* protected override distribution() {
|
|
89
|
+
* const kind = this.range([
|
|
90
|
+
* // Vegetables
|
|
91
|
+
* frac(1, 5),
|
|
92
|
+
* // Fast foods
|
|
93
|
+
* frac(2, 3),
|
|
94
|
+
* // Fruits
|
|
95
|
+
* ])
|
|
96
|
+
* if (kind === 2) {
|
|
97
|
+
* const fruit = this.range([
|
|
98
|
+
* // Apple
|
|
99
|
+
* 0.25,
|
|
100
|
+
* // Banana
|
|
101
|
+
* 0.625,
|
|
102
|
+
* // Orange
|
|
103
|
+
* 0.75,
|
|
104
|
+
* // Strawberry
|
|
105
|
+
* ])
|
|
106
|
+
* // ...
|
|
107
|
+
* }
|
|
108
|
+
* }
|
|
109
|
+
* }
|
|
110
|
+
*/
|
|
111
|
+
protected range<const T extends readonly (number | Fraction)[]>(range: T): TupleIndices<[0, ...T]>;
|
|
112
|
+
/**
|
|
113
|
+
* @example
|
|
114
|
+
* class MyDistribution extends Distribution {
|
|
115
|
+
* protected override distribution() {
|
|
116
|
+
* this.range([0.333, 0.667])
|
|
117
|
+
* this.range([frac(1, 3), frac(2, 3)])
|
|
118
|
+
* // ...
|
|
119
|
+
* }
|
|
120
|
+
* // ...
|
|
121
|
+
* }
|
|
122
|
+
*/
|
|
123
|
+
protected abstract distribution(): void;
|
|
124
|
+
}
|
|
125
|
+
export declare abstract class Serializer<T> implements DistributionLike<T> {
|
|
126
|
+
readonly distribution: Distribution;
|
|
127
|
+
constructor(distribution: Distribution);
|
|
128
|
+
encode(input: T): ArithmeticRange;
|
|
129
|
+
decode(value: ArithmeticValue): T;
|
|
130
|
+
protected abstract serialize(input: T): number[];
|
|
131
|
+
protected abstract deserialize(serialized: number[]): T;
|
|
132
|
+
}
|
|
133
|
+
export {};
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
// src/utils.ts
|
|
2
|
+
function isPositiveIntPowerOfTwo(positiveInt) {
|
|
3
|
+
return !(positiveInt & positiveInt - 1n);
|
|
4
|
+
}
|
|
5
|
+
function gcd(a, b) {
|
|
6
|
+
a = abs(a);
|
|
7
|
+
b = abs(b);
|
|
8
|
+
if (a < b) [a, b] = [b, a];
|
|
9
|
+
while (b) [a, b] = [b, a % b];
|
|
10
|
+
return a;
|
|
11
|
+
}
|
|
12
|
+
function ceilFraction(num, den) {
|
|
13
|
+
return (num > 0n && den > 0n || num < 0n && den < 0n) && num % den ? num / den + 1n : num / den;
|
|
14
|
+
}
|
|
15
|
+
function abs(v) {
|
|
16
|
+
return v < 0n ? -v : v;
|
|
17
|
+
}
|
|
18
|
+
// src/fraction.ts
|
|
19
|
+
function frac(a, b) {
|
|
20
|
+
return new Fraction(a, b);
|
|
21
|
+
}
|
|
22
|
+
var Fraction = class Fraction {
|
|
23
|
+
static view = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(8));
|
|
24
|
+
static commonDenominator(a, b) {
|
|
25
|
+
if (!a.den || !b.den) throw RangeError(`Division by zero (getting commonDenominator of ${a} and ${b})`);
|
|
26
|
+
let den, aCommonNum, bCommonNum;
|
|
27
|
+
const aSign = a.den < 0n ? -1n : 1n;
|
|
28
|
+
const aNum = a.num * aSign;
|
|
29
|
+
const aDen = a.den * aSign;
|
|
30
|
+
const bSign = b.den < 0n ? -1n : 1n;
|
|
31
|
+
const bNum = b.num * bSign;
|
|
32
|
+
const bDen = b.den * bSign;
|
|
33
|
+
if (isPositiveIntPowerOfTwo(aDen) && isPositiveIntPowerOfTwo(bDen)) {
|
|
34
|
+
if (aDen > bDen) {
|
|
35
|
+
den = aDen;
|
|
36
|
+
aCommonNum = aNum;
|
|
37
|
+
bCommonNum = aDen / bDen * bNum;
|
|
38
|
+
} else {
|
|
39
|
+
den = bDen;
|
|
40
|
+
aCommonNum = bDen / aDen * aNum;
|
|
41
|
+
bCommonNum = bNum;
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
const gcdValue = gcd(aDen, bDen);
|
|
45
|
+
den = aDen / gcdValue * bDen;
|
|
46
|
+
aCommonNum = bDen / gcdValue * aNum;
|
|
47
|
+
bCommonNum = aDen / gcdValue * bNum;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
den,
|
|
51
|
+
a: aCommonNum,
|
|
52
|
+
b: bCommonNum
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
num;
|
|
56
|
+
den;
|
|
57
|
+
constructor(a, b) {
|
|
58
|
+
if (b !== void 0) {
|
|
59
|
+
this.num = BigInt(a);
|
|
60
|
+
this.den = BigInt(b);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (typeof a == "bigint" || Number.isInteger(a)) {
|
|
64
|
+
this.num = BigInt(a);
|
|
65
|
+
this.den = 1n;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (a instanceof Fraction) {
|
|
69
|
+
this.num = a.num;
|
|
70
|
+
this.den = a.den;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!Number.isFinite(a)) throw TypeError("Only finite numbers can be converted to fraction; received " + a);
|
|
74
|
+
Fraction.view.setFloat64(0, a);
|
|
75
|
+
const bits = Fraction.view.getBigUint64(0);
|
|
76
|
+
const sign = bits >> 63n ? -1n : 1n;
|
|
77
|
+
const exponent = bits >> 52n & 2047n;
|
|
78
|
+
const fraction = bits & 4503599627370495n;
|
|
79
|
+
let num = sign * (exponent ? 4503599627370496n | fraction : fraction);
|
|
80
|
+
let log2den = exponent ? 1075n - exponent : 1074n;
|
|
81
|
+
while (log2den && !(num & 1n)) {
|
|
82
|
+
num >>= 1n;
|
|
83
|
+
log2den -= 1n;
|
|
84
|
+
}
|
|
85
|
+
this.num = num;
|
|
86
|
+
this.den = 2n ** log2den;
|
|
87
|
+
}
|
|
88
|
+
isLessThan(b) {
|
|
89
|
+
if (!this.den || !b.den) throw RangeError(`Division by zero (checking isLessThan for ${this} and ${b})`);
|
|
90
|
+
const aSign = this.den < 0n ? -1n : 1n;
|
|
91
|
+
const aNum = this.num * aSign;
|
|
92
|
+
const aDen = this.den * aSign;
|
|
93
|
+
const bSign = b.den < 0n ? -1n : 1n;
|
|
94
|
+
const bNum = b.num * bSign;
|
|
95
|
+
return aNum * (b.den * bSign) < bNum * aDen;
|
|
96
|
+
}
|
|
97
|
+
isGreaterThan(b) {
|
|
98
|
+
if (!this.den || !b.den) throw RangeError(`Division by zero (checking isGreaterThan for ${this} and ${b})`);
|
|
99
|
+
return b.isLessThan(this);
|
|
100
|
+
}
|
|
101
|
+
isLessThanOrEqualTo(b) {
|
|
102
|
+
if (!this.den || !b.den) throw RangeError(`Division by zero (checking isLessThanOrEqualTo for ${this} and ${b})`);
|
|
103
|
+
return !b.isLessThan(this);
|
|
104
|
+
}
|
|
105
|
+
isGreaterThanOrEqualTo(b) {
|
|
106
|
+
if (!this.den || !b.den) throw RangeError(`Division by zero (checking isGreaterThanOrEqualTo for ${this} and ${b})`);
|
|
107
|
+
return !this.isLessThan(b);
|
|
108
|
+
}
|
|
109
|
+
reduce() {
|
|
110
|
+
const gcdValue = gcd(this.num, this.den);
|
|
111
|
+
this.num /= gcdValue;
|
|
112
|
+
this.den /= gcdValue;
|
|
113
|
+
}
|
|
114
|
+
toReduced() {
|
|
115
|
+
const gcdValue = gcd(this.num, this.den);
|
|
116
|
+
return new Fraction(this.num / gcdValue, this.den / gcdValue);
|
|
117
|
+
}
|
|
118
|
+
toString() {
|
|
119
|
+
return `${this.num}/${this.den}`;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
// src/values.ts
|
|
123
|
+
var ArithmeticRange = class {
|
|
124
|
+
den;
|
|
125
|
+
low;
|
|
126
|
+
high;
|
|
127
|
+
constructor(den = 1n, low = 0n, high = den) {
|
|
128
|
+
this.den = den;
|
|
129
|
+
this.low = low;
|
|
130
|
+
this.high = high;
|
|
131
|
+
}
|
|
132
|
+
get isValid() {
|
|
133
|
+
const sign = this.den < 0n ? -1n : 1n;
|
|
134
|
+
const den = this.den * sign;
|
|
135
|
+
const low = this.low * sign;
|
|
136
|
+
const high = this.high * sign;
|
|
137
|
+
return 0n <= low && low < high && high <= den;
|
|
138
|
+
}
|
|
139
|
+
get denAbs() {
|
|
140
|
+
if (!this.isValid) throw RangeError(`Cannot get ArithmeticRange.denAbs when not valid (received ${this})`);
|
|
141
|
+
return abs(this.den);
|
|
142
|
+
}
|
|
143
|
+
get lowAbs() {
|
|
144
|
+
if (!this.isValid) throw RangeError(`Cannot get ArithmeticRange.lowAbs when not valid (received ${this})`);
|
|
145
|
+
return abs(this.low);
|
|
146
|
+
}
|
|
147
|
+
get highAbs() {
|
|
148
|
+
if (!this.isValid) throw RangeError(`Cannot get ArithmeticRange.highAbs when not valid (received ${this})`);
|
|
149
|
+
return abs(this.high);
|
|
150
|
+
}
|
|
151
|
+
toString() {
|
|
152
|
+
return `${this.low} ~ ${this.high} / ${this.den}`;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
var ArithmeticValue = class extends Fraction {
|
|
156
|
+
get isValid() {
|
|
157
|
+
const sign = this.den < 0n ? -1n : 1n;
|
|
158
|
+
const num = this.num * sign;
|
|
159
|
+
const den = this.den * sign;
|
|
160
|
+
return den !== 0n && 0n <= num && num <= den;
|
|
161
|
+
}
|
|
162
|
+
get denAbs() {
|
|
163
|
+
if (!this.isValid) throw RangeError(`Cannot get ArithmeticValue.denAbs when not valid (received ${this})`);
|
|
164
|
+
return abs(this.den);
|
|
165
|
+
}
|
|
166
|
+
get numAbs() {
|
|
167
|
+
if (!this.isValid) throw RangeError(`Cannot get ArithmeticValue.numAbs when not valid (received ${this})`);
|
|
168
|
+
return abs(this.num);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
// src/distribution.ts
|
|
172
|
+
const zeroFraction = new ArithmeticValue(0n);
|
|
173
|
+
const oneFraction = new ArithmeticValue(1n);
|
|
174
|
+
var Distribution = class {
|
|
175
|
+
currentInput = null;
|
|
176
|
+
currentOutput = null;
|
|
177
|
+
currentArrIdx = 0;
|
|
178
|
+
encode(data) {
|
|
179
|
+
try {
|
|
180
|
+
const output = new ArithmeticRange();
|
|
181
|
+
this.currentInput = data;
|
|
182
|
+
this.currentOutput = output;
|
|
183
|
+
this.currentArrIdx = 0;
|
|
184
|
+
this.distribution();
|
|
185
|
+
const expectedLength = this.currentArrIdx;
|
|
186
|
+
const actualLength = data.length;
|
|
187
|
+
if (expectedLength !== actualLength) throw RangeError(`Too large encoding input (the expected input length was ${expectedLength}, received ${actualLength})`);
|
|
188
|
+
return output;
|
|
189
|
+
} finally {
|
|
190
|
+
this.currentInput = null;
|
|
191
|
+
this.currentOutput = null;
|
|
192
|
+
this.currentArrIdx = 0;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
decode(value) {
|
|
196
|
+
try {
|
|
197
|
+
const output = [];
|
|
198
|
+
this.currentInput = new ArithmeticValue(value);
|
|
199
|
+
this.currentOutput = output;
|
|
200
|
+
this.distribution();
|
|
201
|
+
return output;
|
|
202
|
+
} finally {
|
|
203
|
+
this.currentInput = null;
|
|
204
|
+
this.currentOutput = null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
encodeRange(i, fractionRange) {
|
|
208
|
+
const range = this.currentOutput;
|
|
209
|
+
const low = fractionRange[i - 1] ?? zeroFraction;
|
|
210
|
+
const high = fractionRange[i] ?? oneFraction;
|
|
211
|
+
const { den: reducedDen, a: reducedLow, b: reducedHigh } = Fraction.commonDenominator(low, high);
|
|
212
|
+
if (reducedLow === reducedHigh) throw RangeError(`Invalid encoding input data at index ${this.currentArrIdx}: the distribution range of ${i} has zero size`);
|
|
213
|
+
const distancePer1 = range.high - range.low;
|
|
214
|
+
range.den *= reducedDen;
|
|
215
|
+
range.low *= reducedDen;
|
|
216
|
+
range.high *= reducedDen;
|
|
217
|
+
range.high = range.low + distancePer1 * reducedHigh;
|
|
218
|
+
range.low = range.low + distancePer1 * reducedLow;
|
|
219
|
+
}
|
|
220
|
+
decodeRange(value, fractionRange) {
|
|
221
|
+
const output = this.currentOutput;
|
|
222
|
+
let matchedIdx = fractionRange.length;
|
|
223
|
+
let matchedPrev = fractionRange[matchedIdx - 1] ?? zeroFraction;
|
|
224
|
+
let matchedCurrent = oneFraction;
|
|
225
|
+
let prev = zeroFraction;
|
|
226
|
+
for (let i = 0; i < fractionRange.length; ++i) {
|
|
227
|
+
const current = fractionRange[i];
|
|
228
|
+
try {
|
|
229
|
+
if (value.isGreaterThanOrEqualTo(current) || prev.isGreaterThan(value)) continue;
|
|
230
|
+
matchedIdx = i;
|
|
231
|
+
matchedPrev = prev;
|
|
232
|
+
matchedCurrent = current;
|
|
233
|
+
break;
|
|
234
|
+
} finally {
|
|
235
|
+
prev = current;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const { den, a, b } = Fraction.commonDenominator(matchedPrev, matchedCurrent);
|
|
239
|
+
value.num = value.num * den - a * value.den;
|
|
240
|
+
value.den *= b - a;
|
|
241
|
+
output.push(matchedIdx);
|
|
242
|
+
return matchedIdx;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* @example
|
|
246
|
+
* class MyDistribution extends Distribution {
|
|
247
|
+
* protected override distribution() {
|
|
248
|
+
* const kind = this.range([
|
|
249
|
+
* // Vegetables
|
|
250
|
+
* frac(1, 5),
|
|
251
|
+
* // Fast foods
|
|
252
|
+
* frac(2, 3),
|
|
253
|
+
* // Fruits
|
|
254
|
+
* ])
|
|
255
|
+
* if (kind === 2) {
|
|
256
|
+
* const fruit = this.range([
|
|
257
|
+
* // Apple
|
|
258
|
+
* 0.25,
|
|
259
|
+
* // Banana
|
|
260
|
+
* 0.625,
|
|
261
|
+
* // Orange
|
|
262
|
+
* 0.75,
|
|
263
|
+
* // Strawberry
|
|
264
|
+
* ])
|
|
265
|
+
* // ...
|
|
266
|
+
* }
|
|
267
|
+
* }
|
|
268
|
+
* }
|
|
269
|
+
*/
|
|
270
|
+
range(range) {
|
|
271
|
+
if (this.currentInput === null) throw TypeError("Cannot call Distribution.range() when idle state");
|
|
272
|
+
const fractionRange = range.map((v) => new ArithmeticValue(v));
|
|
273
|
+
fractionRange.forEach((v, i) => {
|
|
274
|
+
if (!v.isValid) throw RangeError(`Invalid distribution edge at index ${i}: ${v} is out of range 0 ~ 1 (provided ${range.join(", ")})`);
|
|
275
|
+
if (i === 0) return;
|
|
276
|
+
const prev = fractionRange[i - 1];
|
|
277
|
+
if (prev.isGreaterThan(v)) throw TypeError(`Invalid distribution edges at index ${i - 1} / ${i}: ${prev} > ${v} (provided ${range.join(", ")})`);
|
|
278
|
+
});
|
|
279
|
+
if (Array.isArray(this.currentInput)) {
|
|
280
|
+
const i = this.currentInput[this.currentArrIdx];
|
|
281
|
+
if (i === void 0) throw RangeError(`Too small encoding input (received (${this.currentInput.length}) [${this.currentInput.join(", ")}])`);
|
|
282
|
+
if (!Number.isInteger(i) || i < 0 || fractionRange.length < i) throw RangeError(`Invalid encoding input data at index ${this.currentArrIdx}: expected integer between 0 ~ ${fractionRange.length}, received ${i}`);
|
|
283
|
+
this.encodeRange(i, fractionRange);
|
|
284
|
+
++this.currentArrIdx;
|
|
285
|
+
return i;
|
|
286
|
+
} else {
|
|
287
|
+
const value = this.currentInput;
|
|
288
|
+
if (!value.isValid) throw RangeError(`Decoding input is out of range 0 ~ 1 (received ${value})`);
|
|
289
|
+
return this.decodeRange(value, fractionRange);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
// src/serializer.ts
|
|
294
|
+
var Serializer = class {
|
|
295
|
+
distribution;
|
|
296
|
+
constructor(distribution) {
|
|
297
|
+
this.distribution = distribution;
|
|
298
|
+
}
|
|
299
|
+
encode(input) {
|
|
300
|
+
return this.distribution.encode(this.serialize(input));
|
|
301
|
+
}
|
|
302
|
+
decode(value) {
|
|
303
|
+
return this.deserialize(this.distribution.decode(value));
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
// src/string-coder.ts
|
|
307
|
+
var StringCoder = class {};
|
|
308
|
+
var DigitCoder = class extends StringCoder {
|
|
309
|
+
encode(range) {
|
|
310
|
+
if (!range.isValid) throw RangeError(`Invalid range input: ${range}`);
|
|
311
|
+
let { lowAbs: low, highAbs: high } = range;
|
|
312
|
+
const { denAbs: den } = range;
|
|
313
|
+
if (low * 2n <= den && den < high * 2n) return "";
|
|
314
|
+
const charMap = this.charMap;
|
|
315
|
+
const digits = BigInt(charMap.length);
|
|
316
|
+
let code = "";
|
|
317
|
+
const den2 = den * 2n;
|
|
318
|
+
const den3 = den * 3n;
|
|
319
|
+
for (;;) {
|
|
320
|
+
const lowNum = low * digits;
|
|
321
|
+
const highNum = high * digits;
|
|
322
|
+
const lowCeil = ceilFraction(lowNum * 2n - den, den2);
|
|
323
|
+
const highCeil = ceilFraction(highNum * 2n - den3, den2);
|
|
324
|
+
if (lowCeil < 0n || digits < lowCeil || highCeil < -1n || digits <= highCeil) throw RangeError(`Low or high value is out of range (${lowCeil} ~ ${highCeil} / ${digits}). This is an internal bug in DigitCoder.encode() method.`);
|
|
325
|
+
if (lowCeil <= highCeil) {
|
|
326
|
+
code += charMap[Number(highCeil)];
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
const divNum = den * lowCeil;
|
|
330
|
+
const preferredCeil = divNum - lowNum <= highNum - divNum ? lowCeil : highCeil;
|
|
331
|
+
if (preferredCeil < 0n || digits <= preferredCeil) throw RangeError(`preferredCeil is out of range (${preferredCeil} / ${digits}). This is an internal bug in DigitCoder.encode() method.`);
|
|
332
|
+
code += charMap[Number(preferredCeil)];
|
|
333
|
+
low = lowNum - preferredCeil * den;
|
|
334
|
+
high = highNum - preferredCeil * den;
|
|
335
|
+
if (low < 0n) low = 0n;
|
|
336
|
+
if (high > den) high = den;
|
|
337
|
+
}
|
|
338
|
+
return code;
|
|
339
|
+
}
|
|
340
|
+
decode(code) {
|
|
341
|
+
const charMap = this.charMap;
|
|
342
|
+
const digits = BigInt(charMap.length);
|
|
343
|
+
let num = 1n;
|
|
344
|
+
let den = 2n;
|
|
345
|
+
for (let i = 0; i < code.length; ++i) {
|
|
346
|
+
const char = code[i];
|
|
347
|
+
const charIdx = charMap.indexOf(char);
|
|
348
|
+
if (charIdx === -1) throw TypeError("Invalid char received: " + char);
|
|
349
|
+
num = (num - 1n) * digits + BigInt(charIdx * 2 + 1);
|
|
350
|
+
den *= digits;
|
|
351
|
+
}
|
|
352
|
+
return new ArithmeticValue(num, den);
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
var Base64Coder = class extends DigitCoder {
|
|
356
|
+
charMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
357
|
+
};
|
|
358
|
+
const BASE64_CODER = new Base64Coder();
|
|
359
|
+
var Base64UrlCoder = class extends DigitCoder {
|
|
360
|
+
charMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
361
|
+
};
|
|
362
|
+
const BASE64URL_CODER = new Base64UrlCoder();
|
|
363
|
+
var Z85Coder = class extends DigitCoder {
|
|
364
|
+
charMap = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#";
|
|
365
|
+
};
|
|
366
|
+
const Z85_CODER = new Z85Coder();
|
|
367
|
+
var Base94Coder = class extends DigitCoder {
|
|
368
|
+
charMap = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
|
|
369
|
+
};
|
|
370
|
+
const BASE94_CODER = new Base94Coder();
|
|
371
|
+
// src/protocol.ts
|
|
372
|
+
var Protocol = class {
|
|
373
|
+
distribution;
|
|
374
|
+
coder;
|
|
375
|
+
constructor(distribution, coder) {
|
|
376
|
+
this.distribution = distribution;
|
|
377
|
+
this.coder = coder;
|
|
378
|
+
}
|
|
379
|
+
encode(data) {
|
|
380
|
+
return this.coder.encode(this.distribution.encode(data));
|
|
381
|
+
}
|
|
382
|
+
decode(code) {
|
|
383
|
+
return this.distribution.decode(this.coder.decode(code));
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
export { ArithmeticRange, ArithmeticValue, BASE64URL_CODER, BASE64_CODER, BASE94_CODER, Base64Coder, Base64UrlCoder, Base94Coder, DigitCoder, Distribution, Fraction, Protocol, Serializer, StringCoder, Z85Coder, Z85_CODER, frac };
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arithcode",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"description": "Arithmetic coding + String coder",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": ["dist"],
|
|
8
|
+
"exports": "./dist/index.mjs",
|
|
9
|
+
"types": "./dist/index.d.mts",
|
|
10
|
+
"sideEffects": false,
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/idiotf/arithcode/issues"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/idiotf/arithcode.git"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=20"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@eslint/js": "^10",
|
|
23
|
+
"@types/node": "^20",
|
|
24
|
+
"@types/seedrandom": "^3.0.8",
|
|
25
|
+
"eslint": "^10",
|
|
26
|
+
"globals": "^17.6.0",
|
|
27
|
+
"jiti": "^2.7.0",
|
|
28
|
+
"prettier": "^3.8.3",
|
|
29
|
+
"publint": "^0.3.25",
|
|
30
|
+
"seedrandom": "^3.0.5",
|
|
31
|
+
"tsdown": "^0.23.4",
|
|
32
|
+
"typescript": "^6",
|
|
33
|
+
"typescript-eslint": "^8.72.1"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"lint": "eslint",
|
|
37
|
+
"format": "prettier -w '**/*.ts'",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"build": "tsdown --publint",
|
|
40
|
+
"prepack": "bun run build"
|
|
41
|
+
}
|
|
42
|
+
}
|