@bilig/core 0.1.33 → 0.1.35

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.
@@ -29,6 +29,36 @@ export type ExactVectorMatchResult = {
29
29
  position: number | undefined;
30
30
  };
31
31
  export type ApproximateVectorMatchResult = ExactVectorMatchResult;
32
+ export interface PreparedExactVectorLookup {
33
+ sheetName: string;
34
+ rowStart: number;
35
+ rowEnd: number;
36
+ col: number;
37
+ columnVersion: number;
38
+ comparableKind: "numeric" | "text" | "mixed";
39
+ uniformStart: number | undefined;
40
+ uniformStep: number | undefined;
41
+ firstPositions: Map<string, number>;
42
+ lastPositions: Map<string, number>;
43
+ firstNumericPositions: Map<number, number> | undefined;
44
+ lastNumericPositions: Map<number, number> | undefined;
45
+ firstTextPositions: Map<string, number> | undefined;
46
+ lastTextPositions: Map<string, number> | undefined;
47
+ }
48
+ export interface PreparedApproximateVectorLookup {
49
+ sheetName: string;
50
+ rowStart: number;
51
+ rowEnd: number;
52
+ col: number;
53
+ columnVersion: number;
54
+ comparableKind: "numeric" | "text" | undefined;
55
+ uniformStart: number | undefined;
56
+ uniformStep: number | undefined;
57
+ sortedAscending: boolean;
58
+ sortedDescending: boolean;
59
+ numericValues: Float64Array | undefined;
60
+ textValues: string[] | undefined;
61
+ }
32
62
  export interface EngineLookupService {
33
63
  readonly findExactVectorMatch: (request: ExactVectorMatchRequest) => ExactVectorMatchResult;
34
64
  readonly findApproximateVectorMatch: (request: ApproximateVectorMatchRequest) => ApproximateVectorMatchResult;
@@ -44,6 +74,28 @@ export interface EngineLookupService {
44
74
  rowEnd: number;
45
75
  col: number;
46
76
  }) => void;
77
+ readonly prepareExactVectorLookup: (request: {
78
+ sheetName: string;
79
+ rowStart: number;
80
+ rowEnd: number;
81
+ col: number;
82
+ }) => PreparedExactVectorLookup;
83
+ readonly findPreparedExactVectorMatch: (request: {
84
+ lookupValue: CellValue;
85
+ prepared: PreparedExactVectorLookup;
86
+ searchMode: 1 | -1;
87
+ }) => ExactVectorMatchResult;
88
+ readonly prepareApproximateVectorLookup: (request: {
89
+ sheetName: string;
90
+ rowStart: number;
91
+ rowEnd: number;
92
+ col: number;
93
+ }) => PreparedApproximateVectorLookup;
94
+ readonly findPreparedApproximateVectorMatch: (request: {
95
+ lookupValue: CellValue;
96
+ prepared: PreparedApproximateVectorLookup;
97
+ matchMode: 1 | -1;
98
+ }) => ApproximateVectorMatchResult;
47
99
  }
48
100
  export declare function createEngineLookupService(args: {
49
101
  readonly state: Pick<EngineRuntimeState, "workbook" | "strings">;
@@ -34,6 +34,11 @@ function normalizeApproximateComparableValue(value, lookupString, stringId = 0)
34
34
  return { kind: "invalid" };
35
35
  }
36
36
  }
37
+ function normalizeLookupText(value, lookupString) {
38
+ return (value.stringId !== undefined && value.stringId !== 0
39
+ ? lookupString(value.stringId)
40
+ : value.value).toUpperCase();
41
+ }
37
42
  function compareApproximateNumeric(left, right) {
38
43
  if (left === right) {
39
44
  return 0;
@@ -46,6 +51,22 @@ function compareApproximateText(left, right) {
46
51
  }
47
52
  return left < right ? -1 : 1;
48
53
  }
54
+ function detectUniformNumericStep(values) {
55
+ if (values.length < 2) {
56
+ return undefined;
57
+ }
58
+ const start = values[0];
59
+ const step = values[1] - start;
60
+ if (!Number.isFinite(step) || step === 0) {
61
+ return undefined;
62
+ }
63
+ for (let index = 2; index < values.length; index += 1) {
64
+ if (values[index] - values[index - 1] !== step) {
65
+ return undefined;
66
+ }
67
+ }
68
+ return { start, step };
69
+ }
49
70
  export function createEngineLookupService(args) {
50
71
  const exactColumnIndices = new Map();
51
72
  const approximateColumnIndices = new Map();
@@ -88,6 +109,14 @@ export function createEngineLookupService(args) {
88
109
  const buildExactColumnIndex = (sheetName, col, rowStart, rowEnd) => {
89
110
  const firstPositions = new Map();
90
111
  const lastPositions = new Map();
112
+ const firstNumericPositions = new Map();
113
+ const lastNumericPositions = new Map();
114
+ const firstTextPositions = new Map();
115
+ const lastTextPositions = new Map();
116
+ const numericSequence = [];
117
+ let sawNumeric = false;
118
+ let sawText = false;
119
+ let sawOther = false;
91
120
  for (let row = rowStart; row <= rowEnd; row += 1) {
92
121
  const key = readNormalizedKeyAt(sheetName, row, col);
93
122
  if (key === undefined) {
@@ -97,11 +126,48 @@ export function createEngineLookupService(args) {
97
126
  firstPositions.set(key, row);
98
127
  }
99
128
  lastPositions.set(key, row);
129
+ if (key.startsWith("n:")) {
130
+ const numericValue = Number(key.slice(2));
131
+ if (!firstNumericPositions.has(numericValue)) {
132
+ firstNumericPositions.set(numericValue, row);
133
+ }
134
+ lastNumericPositions.set(numericValue, row);
135
+ numericSequence.push(numericValue);
136
+ sawNumeric = true;
137
+ continue;
138
+ }
139
+ if (key.startsWith("s:")) {
140
+ const textValue = key.slice(2);
141
+ if (!firstTextPositions.has(textValue)) {
142
+ firstTextPositions.set(textValue, row);
143
+ }
144
+ lastTextPositions.set(textValue, row);
145
+ sawText = true;
146
+ continue;
147
+ }
148
+ sawOther = true;
100
149
  }
150
+ const comparableKind = sawOther || (sawNumeric && sawText)
151
+ ? "mixed"
152
+ : sawNumeric
153
+ ? "numeric"
154
+ : sawText
155
+ ? "text"
156
+ : "mixed";
157
+ const uniformNumericStep = comparableKind === "numeric"
158
+ ? detectUniformNumericStep(Float64Array.from(numericSequence))
159
+ : undefined;
101
160
  return {
102
161
  columnVersion: args.state.workbook.getSheetColumnVersion(sheetName, col),
162
+ comparableKind,
163
+ uniformStart: uniformNumericStep?.start,
164
+ uniformStep: uniformNumericStep?.step,
103
165
  firstPositions,
104
166
  lastPositions,
167
+ firstNumericPositions: comparableKind === "numeric" ? firstNumericPositions : undefined,
168
+ lastNumericPositions: comparableKind === "numeric" ? lastNumericPositions : undefined,
169
+ firstTextPositions: comparableKind === "text" ? firstTextPositions : undefined,
170
+ lastTextPositions: comparableKind === "text" ? lastTextPositions : undefined,
105
171
  };
106
172
  };
107
173
  const buildApproximateColumnIndex = (sheetName, col, rowStart, rowEnd) => {
@@ -161,8 +227,12 @@ export function createEngineLookupService(args) {
161
227
  return {
162
228
  columnVersion: args.state.workbook.getSheetColumnVersion(sheetName, col),
163
229
  comparableKind: undefined,
230
+ uniformStart: undefined,
231
+ uniformStep: undefined,
164
232
  sortedAscending: false,
165
233
  sortedDescending: false,
234
+ numericValues: undefined,
235
+ textValues: undefined,
166
236
  };
167
237
  }
168
238
  if (hasText) {
@@ -181,8 +251,11 @@ export function createEngineLookupService(args) {
181
251
  return {
182
252
  columnVersion: args.state.workbook.getSheetColumnVersion(sheetName, col),
183
253
  comparableKind: "text",
254
+ uniformStart: undefined,
255
+ uniformStep: undefined,
184
256
  sortedAscending,
185
257
  sortedDescending,
258
+ numericValues: undefined,
186
259
  textValues,
187
260
  };
188
261
  }
@@ -198,12 +271,16 @@ export function createEngineLookupService(args) {
198
271
  sortedDescending = false;
199
272
  }
200
273
  }
274
+ const uniformNumericStep = detectUniformNumericStep(numericValues);
201
275
  return {
202
276
  columnVersion: args.state.workbook.getSheetColumnVersion(sheetName, col),
203
277
  comparableKind: "numeric",
278
+ uniformStart: uniformNumericStep?.start,
279
+ uniformStep: uniformNumericStep?.step,
204
280
  sortedAscending,
205
281
  sortedDescending,
206
282
  numericValues,
283
+ textValues: undefined,
207
284
  };
208
285
  };
209
286
  const resolveExactColumnBounds = (request) => {
@@ -250,6 +327,76 @@ export function createEngineLookupService(args) {
250
327
  }
251
328
  return entry;
252
329
  };
330
+ const prepareExactVectorLookup = (request) => {
331
+ const entry = ensureExactColumnIndex(request.sheetName, request.col, request.rowStart, request.rowEnd);
332
+ return {
333
+ sheetName: request.sheetName,
334
+ rowStart: request.rowStart,
335
+ rowEnd: request.rowEnd,
336
+ col: request.col,
337
+ columnVersion: entry.columnVersion,
338
+ comparableKind: entry.comparableKind,
339
+ uniformStart: entry.uniformStart,
340
+ uniformStep: entry.uniformStep,
341
+ firstPositions: entry.firstPositions,
342
+ lastPositions: entry.lastPositions,
343
+ firstNumericPositions: entry.firstNumericPositions,
344
+ lastNumericPositions: entry.lastNumericPositions,
345
+ firstTextPositions: entry.firstTextPositions,
346
+ lastTextPositions: entry.lastTextPositions,
347
+ };
348
+ };
349
+ const prepareApproximateVectorLookup = (request) => {
350
+ const entry = ensureApproximateColumnIndex(request.sheetName, request.col, request.rowStart, request.rowEnd);
351
+ return {
352
+ sheetName: request.sheetName,
353
+ rowStart: request.rowStart,
354
+ rowEnd: request.rowEnd,
355
+ col: request.col,
356
+ columnVersion: entry.columnVersion,
357
+ comparableKind: entry.comparableKind,
358
+ uniformStart: entry.uniformStart,
359
+ uniformStep: entry.uniformStep,
360
+ sortedAscending: entry.sortedAscending,
361
+ sortedDescending: entry.sortedDescending,
362
+ numericValues: entry.numericValues,
363
+ textValues: entry.textValues,
364
+ };
365
+ };
366
+ const refreshPreparedExactVectorLookup = (prepared) => {
367
+ const columnVersion = args.state.workbook.getSheetColumnVersion(prepared.sheetName, prepared.col);
368
+ if (columnVersion === prepared.columnVersion) {
369
+ return prepared;
370
+ }
371
+ const refreshed = prepareExactVectorLookup(prepared);
372
+ prepared.columnVersion = refreshed.columnVersion;
373
+ prepared.comparableKind = refreshed.comparableKind;
374
+ prepared.uniformStart = refreshed.uniformStart;
375
+ prepared.uniformStep = refreshed.uniformStep;
376
+ prepared.firstPositions = refreshed.firstPositions;
377
+ prepared.lastPositions = refreshed.lastPositions;
378
+ prepared.firstNumericPositions = refreshed.firstNumericPositions;
379
+ prepared.lastNumericPositions = refreshed.lastNumericPositions;
380
+ prepared.firstTextPositions = refreshed.firstTextPositions;
381
+ prepared.lastTextPositions = refreshed.lastTextPositions;
382
+ return prepared;
383
+ };
384
+ const refreshPreparedApproximateVectorLookup = (prepared) => {
385
+ const columnVersion = args.state.workbook.getSheetColumnVersion(prepared.sheetName, prepared.col);
386
+ if (columnVersion === prepared.columnVersion) {
387
+ return prepared;
388
+ }
389
+ const refreshed = prepareApproximateVectorLookup(prepared);
390
+ prepared.columnVersion = refreshed.columnVersion;
391
+ prepared.comparableKind = refreshed.comparableKind;
392
+ prepared.uniformStart = refreshed.uniformStart;
393
+ prepared.uniformStep = refreshed.uniformStep;
394
+ prepared.sortedAscending = refreshed.sortedAscending;
395
+ prepared.sortedDescending = refreshed.sortedDescending;
396
+ prepared.numericValues = refreshed.numericValues;
397
+ prepared.textValues = refreshed.textValues;
398
+ return prepared;
399
+ };
253
400
  return {
254
401
  primeExactColumnIndex(request) {
255
402
  ensureExactColumnIndex(request.sheetName, request.col, request.rowStart, request.rowEnd);
@@ -257,6 +404,202 @@ export function createEngineLookupService(args) {
257
404
  primeApproximateColumnIndex(request) {
258
405
  ensureApproximateColumnIndex(request.sheetName, request.col, request.rowStart, request.rowEnd);
259
406
  },
407
+ prepareExactVectorLookup(request) {
408
+ return prepareExactVectorLookup(request);
409
+ },
410
+ findPreparedExactVectorMatch(request) {
411
+ const prepared = refreshPreparedExactVectorLookup(request.prepared);
412
+ if (prepared.comparableKind === "numeric") {
413
+ if (request.lookupValue.tag === ValueTag.Error) {
414
+ return { handled: false };
415
+ }
416
+ if (request.lookupValue.tag !== ValueTag.Number) {
417
+ return { handled: true, position: undefined };
418
+ }
419
+ const numericValue = Object.is(request.lookupValue.value, -0)
420
+ ? 0
421
+ : request.lookupValue.value;
422
+ if (prepared.uniformStart !== undefined && prepared.uniformStep !== undefined) {
423
+ const relative = (numericValue - prepared.uniformStart) / prepared.uniformStep;
424
+ const position = Number.isInteger(relative) ? relative + 1 : undefined;
425
+ return {
426
+ handled: true,
427
+ position: position !== undefined &&
428
+ position >= 1 &&
429
+ position <= prepared.rowEnd - prepared.rowStart + 1
430
+ ? position
431
+ : undefined,
432
+ };
433
+ }
434
+ const numericMap = request.searchMode === -1
435
+ ? prepared.lastNumericPositions
436
+ : prepared.firstNumericPositions;
437
+ const row = numericMap?.get(numericValue);
438
+ return {
439
+ handled: true,
440
+ position: row === undefined ? undefined : row - prepared.rowStart + 1,
441
+ };
442
+ }
443
+ if (prepared.comparableKind === "text") {
444
+ if (request.lookupValue.tag === ValueTag.Error) {
445
+ return { handled: false };
446
+ }
447
+ if (request.lookupValue.tag !== ValueTag.String) {
448
+ return { handled: true, position: undefined };
449
+ }
450
+ const textValue = normalizeLookupText(request.lookupValue, (id) => args.state.strings.get(id));
451
+ const textMap = request.searchMode === -1 ? prepared.lastTextPositions : prepared.firstTextPositions;
452
+ const row = textMap?.get(textValue);
453
+ return {
454
+ handled: true,
455
+ position: row === undefined ? undefined : row - prepared.rowStart + 1,
456
+ };
457
+ }
458
+ const normalizedLookupKey = normalizeExactLookupKey(request.lookupValue, (id) => args.state.strings.get(id));
459
+ if (normalizedLookupKey === undefined) {
460
+ return { handled: false };
461
+ }
462
+ const row = request.searchMode === -1
463
+ ? prepared.lastPositions.get(normalizedLookupKey)
464
+ : prepared.firstPositions.get(normalizedLookupKey);
465
+ return {
466
+ handled: true,
467
+ position: row === undefined ? undefined : row - prepared.rowStart + 1,
468
+ };
469
+ },
470
+ prepareApproximateVectorLookup(request) {
471
+ return prepareApproximateVectorLookup(request);
472
+ },
473
+ findPreparedApproximateVectorMatch(request) {
474
+ const prepared = refreshPreparedApproximateVectorLookup(request.prepared);
475
+ if (prepared.comparableKind === undefined) {
476
+ return { handled: false };
477
+ }
478
+ if (request.matchMode === 1 && !prepared.sortedAscending) {
479
+ return { handled: false };
480
+ }
481
+ if (request.matchMode === -1 && !prepared.sortedDescending) {
482
+ return { handled: false };
483
+ }
484
+ if (prepared.comparableKind === "numeric") {
485
+ let lookupValue;
486
+ switch (request.lookupValue.tag) {
487
+ case ValueTag.Empty:
488
+ lookupValue = 0;
489
+ break;
490
+ case ValueTag.Number:
491
+ lookupValue = Object.is(request.lookupValue.value, -0) ? 0 : request.lookupValue.value;
492
+ break;
493
+ case ValueTag.Boolean:
494
+ lookupValue = request.lookupValue.value ? 1 : 0;
495
+ break;
496
+ case ValueTag.Error:
497
+ return { handled: false };
498
+ case ValueTag.String:
499
+ return { handled: false };
500
+ }
501
+ const values = prepared.numericValues;
502
+ if (!values) {
503
+ return { handled: false };
504
+ }
505
+ if (prepared.uniformStart !== undefined && prepared.uniformStep !== undefined) {
506
+ const { uniformStart, uniformStep } = prepared;
507
+ const lastValue = uniformStart + uniformStep * (values.length - 1);
508
+ if (request.matchMode === 1 && uniformStep > 0) {
509
+ if (lookupValue < uniformStart) {
510
+ return { handled: true, position: undefined };
511
+ }
512
+ if (lookupValue >= lastValue) {
513
+ return { handled: true, position: values.length };
514
+ }
515
+ const position = Math.floor((lookupValue - uniformStart) / uniformStep) + 1;
516
+ return { handled: true, position: Math.min(values.length, Math.max(1, position)) };
517
+ }
518
+ if (request.matchMode === -1 && uniformStep < 0) {
519
+ if (lookupValue > uniformStart) {
520
+ return { handled: true, position: undefined };
521
+ }
522
+ if (lookupValue <= lastValue) {
523
+ return { handled: true, position: values.length };
524
+ }
525
+ const position = Math.floor((uniformStart - lookupValue) / -uniformStep) + 1;
526
+ return { handled: true, position: Math.min(values.length, Math.max(1, position)) };
527
+ }
528
+ }
529
+ let low = 0;
530
+ let high = values.length - 1;
531
+ let best = -1;
532
+ while (low <= high) {
533
+ const mid = (low + high) >> 1;
534
+ const comparison = compareApproximateNumeric(values[mid], lookupValue);
535
+ if (request.matchMode === 1) {
536
+ if (comparison <= 0) {
537
+ best = mid;
538
+ low = mid + 1;
539
+ }
540
+ else {
541
+ high = mid - 1;
542
+ }
543
+ }
544
+ else if (comparison >= 0) {
545
+ best = mid;
546
+ low = mid + 1;
547
+ }
548
+ else {
549
+ high = mid - 1;
550
+ }
551
+ }
552
+ return {
553
+ handled: true,
554
+ position: best === -1 ? undefined : best + 1,
555
+ };
556
+ }
557
+ let lookupValue;
558
+ switch (request.lookupValue.tag) {
559
+ case ValueTag.Empty:
560
+ lookupValue = "";
561
+ break;
562
+ case ValueTag.String:
563
+ lookupValue = normalizeLookupText(request.lookupValue, (id) => args.state.strings.get(id));
564
+ break;
565
+ case ValueTag.Error:
566
+ return { handled: false };
567
+ case ValueTag.Number:
568
+ case ValueTag.Boolean:
569
+ return { handled: false };
570
+ }
571
+ const values = prepared.textValues;
572
+ if (!values) {
573
+ return { handled: false };
574
+ }
575
+ let low = 0;
576
+ let high = values.length - 1;
577
+ let best = -1;
578
+ while (low <= high) {
579
+ const mid = (low + high) >> 1;
580
+ const comparison = compareApproximateText(values[mid], lookupValue);
581
+ if (request.matchMode === 1) {
582
+ if (comparison <= 0) {
583
+ best = mid;
584
+ low = mid + 1;
585
+ }
586
+ else {
587
+ high = mid - 1;
588
+ }
589
+ }
590
+ else if (comparison >= 0) {
591
+ best = mid;
592
+ low = mid + 1;
593
+ }
594
+ else {
595
+ high = mid - 1;
596
+ }
597
+ }
598
+ return {
599
+ handled: true,
600
+ position: best === -1 ? undefined : best + 1,
601
+ };
602
+ },
260
603
  findExactVectorMatch(request) {
261
604
  const normalizedLookupKey = normalizeExactLookupKey(request.lookupValue, (id) => args.state.strings.get(id));
262
605
  if (normalizedLookupKey === undefined) {
@@ -267,6 +610,50 @@ export function createEngineLookupService(args) {
267
610
  return { handled: false };
268
611
  }
269
612
  const entry = ensureExactColumnIndex(request.sheetName, bounds.col, bounds.rowStart, bounds.rowEnd);
613
+ if (entry.comparableKind === "numeric") {
614
+ if (request.lookupValue.tag === ValueTag.Error) {
615
+ return { handled: false };
616
+ }
617
+ if (request.lookupValue.tag !== ValueTag.Number) {
618
+ return { handled: true, position: undefined };
619
+ }
620
+ const numericValue = Object.is(request.lookupValue.value, -0)
621
+ ? 0
622
+ : request.lookupValue.value;
623
+ if (entry.uniformStart !== undefined && entry.uniformStep !== undefined) {
624
+ const relative = (numericValue - entry.uniformStart) / entry.uniformStep;
625
+ const position = Number.isInteger(relative) ? relative + 1 : undefined;
626
+ return {
627
+ handled: true,
628
+ position: position !== undefined &&
629
+ position >= 1 &&
630
+ position <= bounds.rowEnd - bounds.rowStart + 1
631
+ ? position
632
+ : undefined,
633
+ };
634
+ }
635
+ const numericMap = request.searchMode === -1 ? entry.lastNumericPositions : entry.firstNumericPositions;
636
+ const row = numericMap?.get(numericValue);
637
+ return {
638
+ handled: true,
639
+ position: row === undefined ? undefined : row - bounds.rowStart + 1,
640
+ };
641
+ }
642
+ if (entry.comparableKind === "text") {
643
+ if (request.lookupValue.tag === ValueTag.Error) {
644
+ return { handled: false };
645
+ }
646
+ if (request.lookupValue.tag !== ValueTag.String) {
647
+ return { handled: true, position: undefined };
648
+ }
649
+ const textValue = normalizeLookupText(request.lookupValue, (id) => args.state.strings.get(id));
650
+ const textMap = request.searchMode === -1 ? entry.lastTextPositions : entry.firstTextPositions;
651
+ const row = textMap?.get(textValue);
652
+ return {
653
+ handled: true,
654
+ position: row === undefined ? undefined : row - bounds.rowStart + 1,
655
+ };
656
+ }
270
657
  const row = request.searchMode === -1
271
658
  ? entry.lastPositions.get(normalizedLookupKey)
272
659
  : entry.firstPositions.get(normalizedLookupKey);
@@ -291,15 +678,50 @@ export function createEngineLookupService(args) {
291
678
  return { handled: false };
292
679
  }
293
680
  if (entry.comparableKind === "numeric") {
294
- const lookup = normalizeApproximateComparableValue(request.lookupValue, (id) => args.state.strings.get(id));
295
- if (lookup.kind !== "numeric" && lookup.kind !== "empty") {
296
- return { handled: false };
681
+ let lookupValue;
682
+ switch (request.lookupValue.tag) {
683
+ case ValueTag.Empty:
684
+ lookupValue = 0;
685
+ break;
686
+ case ValueTag.Number:
687
+ lookupValue = Object.is(request.lookupValue.value, -0) ? 0 : request.lookupValue.value;
688
+ break;
689
+ case ValueTag.Boolean:
690
+ lookupValue = request.lookupValue.value ? 1 : 0;
691
+ break;
692
+ case ValueTag.Error:
693
+ return { handled: false };
694
+ case ValueTag.String:
695
+ return { handled: false };
297
696
  }
298
697
  const values = entry.numericValues;
299
698
  if (!values) {
300
699
  return { handled: false };
301
700
  }
302
- const lookupValue = lookup.kind === "numeric" ? lookup.value : 0;
701
+ if (entry.uniformStart !== undefined && entry.uniformStep !== undefined) {
702
+ const { uniformStart, uniformStep } = entry;
703
+ const lastValue = uniformStart + uniformStep * (values.length - 1);
704
+ if (request.matchMode === 1 && uniformStep > 0) {
705
+ if (lookupValue < uniformStart) {
706
+ return { handled: true, position: undefined };
707
+ }
708
+ if (lookupValue >= lastValue) {
709
+ return { handled: true, position: values.length };
710
+ }
711
+ const position = Math.floor((lookupValue - uniformStart) / uniformStep) + 1;
712
+ return { handled: true, position: Math.min(values.length, Math.max(1, position)) };
713
+ }
714
+ if (request.matchMode === -1 && uniformStep < 0) {
715
+ if (lookupValue > uniformStart) {
716
+ return { handled: true, position: undefined };
717
+ }
718
+ if (lookupValue <= lastValue) {
719
+ return { handled: true, position: values.length };
720
+ }
721
+ const position = Math.floor((uniformStart - lookupValue) / -uniformStep) + 1;
722
+ return { handled: true, position: Math.min(values.length, Math.max(1, position)) };
723
+ }
724
+ }
303
725
  let low = 0;
304
726
  let high = values.length - 1;
305
727
  let best = -1;