Skip to content

Commit c834318

Browse files
Implement DataView operations (#87)
JSBI.DataViewGetBigInt64 JSBI.DataViewSetBigInt64 JSBI.DataViewGetBigUint64 JSBI.DataViewSetBigUint64 as replacements for the native DataView.{get,set}Big{Int,Uint}64. This addresses half of #4.
1 parent d66a968 commit c834318

File tree

3 files changed

+164
-2
lines changed

3 files changed

+164
-2
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,15 @@ Unfortunately, there are also a few things that are not supported at all:
107107

108108
It is impossible to replicate the exact behavior of the native `++` and `--` operators in a polyfill/library. Since JSBI is intended to be transpiled away eventually, it doesn’t provide a similar-but-different alternative. You can use `JSBI.add()` and `JSBI.subtract()` instead.
109109

110+
Since version 4.2.0, polyfills for `DataView` operations are included (where `dv` is a `DataView`, `i` is an index, `le` is an optional boolean indicating little endian mode, and `x` is a `BigInt` or a `JSBI` instance, respectively):
111+
112+
| native BigInts/DataViews | JSBI |
113+
|-----------------------------|-------------------------------------------|
114+
| `dv.getBigInt64(i, le)` | `JSBI.DataViewGetBigInt64(dv, i, le)` |
115+
| `dv.setBigInt64(i, x, le)` | `JSBI.DataViewSetBigInt64(dv, i, x, le)` |
116+
| `dv.getBigUint64(i, le)` | `JSBI.DataViewGetBigUint64(dv, i, le)` |
117+
| `dv.setBigUint64(i, x, le)` | `JSBI.DataViewSetBigUint64(dv, i, x, le)` |
118+
110119
## When?
111120

112121
Now! The JSBI library is ready for use today.

lib/jsbi.ts

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
class JSBI extends Array {
1515
constructor(length: number, private sign: boolean) {
1616
super(length);
17-
// Explicitly set the prototype as per
17+
// Explicitly set the prototype as per
1818
// https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
1919
Object.setPrototypeOf(this, JSBI.prototype);
2020
if (length > JSBI.__kMaxLength) {
@@ -77,7 +77,8 @@ class JSBI extends Array {
7777
}
7878

7979
override valueOf() {
80-
throw new Error('Convert JSBI instances to native numbers using `toNumber`.');
80+
throw new Error(
81+
'Convert JSBI instances to native numbers using `toNumber`.');
8182
}
8283

8384
// Equivalent of "Number(my_bigint)" in the native implementation.
@@ -538,6 +539,54 @@ class JSBI extends Array {
538539
return !JSBI.EQ(x, y);
539540
}
540541

542+
// DataView-related functionality.
543+
544+
static DataViewGetBigInt64(
545+
dataview: DataView, byteOffset: number, littleEndian: boolean = false) {
546+
return JSBI.asIntN(
547+
64, JSBI.DataViewGetBigUint64(dataview, byteOffset, littleEndian));
548+
}
549+
550+
static DataViewGetBigUint64(
551+
dataview: DataView, byteOffset: number, littleEndian: boolean = false) {
552+
const [h, l] = littleEndian ? [4, 0] : [0, 4];
553+
const high = dataview.getUint32(byteOffset + h, littleEndian);
554+
const low = dataview.getUint32(byteOffset + l, littleEndian);
555+
const result = new JSBI(3, false);
556+
result.__setDigit(0, low & 0x3FFFFFFF);
557+
result.__setDigit(1, ((high & 0xFFFFFFF) << 2) | (low >>> 30));
558+
result.__setDigit(2, high >>> 28);
559+
return result.__trim();
560+
}
561+
562+
static DataViewSetBigInt64(
563+
dataview: DataView, byteOffset: number, value: JSBI,
564+
littleEndian: boolean = false) {
565+
JSBI.DataViewSetBigUint64(dataview, byteOffset, value, littleEndian);
566+
}
567+
568+
static DataViewSetBigUint64(
569+
dataview: DataView, byteOffset: number, value: JSBI,
570+
littleEndian: boolean = false) {
571+
value = JSBI.asUintN(64, value);
572+
let high = 0;
573+
let low = 0;
574+
if (value.length > 0) {
575+
low = value.__digit(0);
576+
if (value.length > 1) {
577+
const d1 = value.__digit(1);
578+
low = low | d1 << 30;
579+
high = d1 >>> 2;
580+
if (value.length > 2) {
581+
high = high | (value.__digit(2) << 28);
582+
}
583+
}
584+
}
585+
const [h, l] = littleEndian ? [4, 0] : [0, 4];
586+
dataview.setUint32(byteOffset + h, high, littleEndian);
587+
dataview.setUint32(byteOffset + l, low, littleEndian);
588+
}
589+
541590
// Helpers.
542591

543592
static __zero(): JSBI {

tests/dataview.mjs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright 2022 Google Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the “License”);
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// <https://apache.org/licenses/LICENSE-2.0>.
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an “AS IS” BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
import JSBI from '../jsbi';
15+
16+
const ab = new ArrayBuffer(16);
17+
const dv = new DataView(ab, 0, 8);
18+
const dv2 = new DataView(ab, 8, 8);
19+
20+
function hexParse(s) {
21+
if (s.charCodeAt(0) === 0x2D) {
22+
const result = JSBI.BigInt(s.slice(1));
23+
result.sign = true;
24+
return result;
25+
}
26+
return JSBI.BigInt(s);
27+
}
28+
function hexParse2(s) {
29+
if (s.charCodeAt(0) === 0x2D) return -BigInt(s.slice(1));
30+
return BigInt(s);
31+
}
32+
33+
function dataViewEqual(a, b) {
34+
for (let i = 0; i < a.length; i++) {
35+
if (a.getUint8(i) !== b.getUint8(i)) return false;
36+
}
37+
return true;
38+
}
39+
40+
function dump(dataview) {
41+
const chunks = [];
42+
for (let i = 0; i < 8; i++) {
43+
chunks.push('0x' + dataview.getUint8(i).toString(16));
44+
}
45+
return chunks.join(', ');
46+
}
47+
48+
function error(what, value, littleEndian, expected, actual) {
49+
throw new Error(`Incorrect ${what} for ${value}, littleEndian=${
50+
littleEndian}: expected ${expected} but got ${actual}`);
51+
}
52+
53+
function test(value) {
54+
const jsbi = hexParse(value);
55+
const bigint = hexParse2(value);
56+
for (const littleEndian of [true, false]) {
57+
// Signed
58+
JSBI.DataViewSetBigInt64(dv, 0, jsbi, littleEndian);
59+
dv2.setBigInt64(0, bigint, littleEndian);
60+
if (!dataViewEqual(dv, dv2)) {
61+
error('signed DV contents', value, littleEndian, dump(dv2), dump(dv));
62+
}
63+
let out = JSBI.DataViewGetBigInt64(dv, 0, littleEndian);
64+
let out2 = JSBI.BigInt(dv2.getBigInt64(0, littleEndian).toString());
65+
if (!JSBI.equal(out, out2)) {
66+
error(
67+
'signed readout value', value, littleEndian, jsbi.toString(16),
68+
out.toString(16));
69+
}
70+
// Unsigned
71+
JSBI.DataViewSetBigUint64(dv, 0, jsbi, littleEndian);
72+
dv2.setBigUint64(0, bigint, littleEndian);
73+
if (!dataViewEqual(dv, dv2)) {
74+
error('unsigned DV contents', value, littleEndian, dump(dv2), dump(dv));
75+
}
76+
out = JSBI.DataViewGetBigUint64(dv, 0, littleEndian);
77+
out2 = JSBI.BigInt(dv2.getBigUint64(0, littleEndian).toString());
78+
if (!JSBI.equal(out, out2)) {
79+
error(
80+
'unsigned readout value', value, littleEndian, jsbi.toString(16),
81+
out.toString(16));
82+
}
83+
}
84+
}
85+
86+
const tests = [
87+
'0',
88+
'1',
89+
'-1',
90+
'2',
91+
'-2',
92+
'0xFFFFFFFF',
93+
'-0xFFFFFFFF',
94+
'0xFFFFFFFFFFFFFFFF',
95+
'0xFFFFFFFF00000000',
96+
'-0x8000000000000000',
97+
// Verify byte order.
98+
'0x1234567890abcdef',
99+
// ...00110011... bit patterns to flush out 30-bit digits issues.
100+
'0x3333333333333333',
101+
'0xcccccccccccccccc',
102+
];
103+
104+
for (const t of tests) test(t);

0 commit comments

Comments
 (0)