forked from jeffpar/pcjs.v1
-
Notifications
You must be signed in to change notification settings - Fork 0
/
int36.js
1382 lines (1291 loc) · 54.1 KB
/
int36.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* @fileoverview Support for 36-bit integers (using unsigned JavaScript numbers)
* @author <a href="mailto:[email protected]">Jeff Parsons</a> (@jeffpar)
* @copyright © 2012-2020 Jeff Parsons
*
* This file is part of PCjs, a computer emulation software project at <https://www.pcjs.org>.
*
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <https://www.pcjs.org/modules/shared/lib/defines.js>.
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of PCjs
* for purposes of the GNU General Public License, and the author does not claim any copyright
* as to their contents.
*/
"use strict";
/*
From the "PDP-10 System Reference Manual", May 1968, p. 1-4:
1.1 NUMBER SYSTEM
The program can interpret a data word as a 36-digit, unsigned binary number, or the left and right
halves of a word can be taken as separate 18-bit numbers. The PDP-10 repertoire includes instructions
that effectively add or subtract one from both halves of a word, so the right half can be used for
address modification when the word is addressed as an index register, while the left half is used to
keep a control count.
The standard arithmetic instructions in the PDP-10 use twos complement, fixed point conventions to do
binary arithmetic. In a word used as a number, bit 0 (the leftmost bit) represents the sign, 0 for positive,
1 for negative. In a positive number the remaining 35 bits are the magnitude in ordinary binary notation.
The negative of a number is obtained by taking its twos complement. If x is an n-digit binary number, its
twos complement is 2^n - x, and its ones complement is (2^n - 1) - x, or equivalently (2^n - x) - 1.
Subtracting a number from 2^n - 1 (ie, from all 1s) is equivalent to performing the logical complement,
ie changing all 0s to 1s and all 1s to 0s. Therefore, to form the twos complement one takes the logical
complement (usually referred to merely as the complement) of the entire word including the sign, and adds
1 to the result. In a negative number the sign bit is 1, and the remaining bits are the twos complement
of the magnitude.
Zero is represented by a word containing all 0s. Complementing this number produces all 1s, and adding
1 to that produces all 0s again. Hence there is only one zero representation and its sign is positive.
Since the numbers are symmetrical in magnitude about a single zero representation, all even numbers both
positive and negative end in 0, all odd numbers in 1 (a number all 1s represents -1). But since there are
the same number of positive and negative numbers and zero is positive, there is one more negative number
than there are nonzero positive numbers. This is the most negative number and it cannot be produced by
negating any positive number (its octal representation is 400000 000000 and its magnitude is one greater
than the largest positive number).
If ones complements were used for negatives one could read a negative number by attaching significance
to the as instead of the 1s. In twos complement notation each negative number is one greater than the
complement of the positive number of the same magnitude, so one can read a negative number by attaching
significance to the rightmost 1 and attaching significance to the 0s at the left of it (the negative number
of largest magnitude has a 1 in only the sign position). In a negative integer, 1s may be discarded at the
left, just as leading 0s may be dropped in a positive integer. In a negative fraction, 0s may be discarded
at the right. So long as only 0s are discarded, the number remains in twos complement form because it
still has a 1 that possesses significance; but if a portion including the rightmost 1 is discarded, the
remaining part of the fraction is now a ones complement.
The computer does not keep track of a binary point - the programmer must adopt a point convention and shift
the magnitude of the result to conform to the convention used. Two common conventions are to regard a number
as an integer (binary point at the right) or as a proper fraction (binary point at the left); in these two
cases the range of numbers represented by a single word is -2^35 to 2^35 - 1, or -1 to 1 - 2^35. Since
multiplication and division make use of double length numbers, there are special instructions for performing
these operations with integral operands.
SIDEBAR: Multiplication produces a double length product, and the programmer must remember that discarding
the low order part of a double length negative leaves the high order part in correct twos complement form
only if the low order part is null.
...
2.5 FIXED POINT ARITHMETIC
For fixed point arithmetic the PDP-10 has instructions for arithmetic shifting (which is essentially
multiplication by a power of 2) as well as for performing addition, subtraction, multiplication and division
of numbers in fixed point format [§ 1.1]. In such numbers the position of the binary point is arbitrary
(the programmer may adopt any point convention). The add and subtract instructions involve only single length
numbers, whereas multiply supplies a double length product, and divide uses a double length dividend. The high
and low order words respectively of a double length fixed point number are in accumulators A and A+1 (mod 20),
where the magnitude is the 70-bit string in bits 1-35 of the two words and the signs of the two are identical.
There are also integer multiply and divide instructions that involve only single length numbers and are
especially suited for handling smaller integers, particularly those of eighteen bits or less such as addresses
(of course they can be used for small fractions as well provided the programmer keeps track of the binary point).
For convenience in the following, all operands are assumed to be integers (binary point at the right).
The processor has four flags, Overflow, Carry 0, Carry 1 and No Divide, that indicate when the magnitude of a
number is or would be larger than can be accommodated. Carry 0 and Carry 1 actually detect carries out of bits
0 and 1 in certain instructions that employ fixed point arithmetic operations: the add and subtract instructions
treated here, the move instructions that produce the negative or magnitude of the word moved [§ 2.2], and the
arithmetic test instructions that increment or decrement the test word [§ 2.7]. In these instructions an
incorrect result is indicated - and the Overflow flag set - if the carries are different, ie if there is a carry
into the sign but not out of it, or vice versa. The Overflow flag is also set by No Divide being set, which
means the processor has failed to perform a division because the magnitude of the dividend is greater than or
equal to that of the divisor, or in integer divide, simply that the divisor is zero. In other overflow cases
only Overflow itself is set: these include too large a product in multiplication, and loss of significant bits
in left arithmetic shifting.
SIDEBAR: Overflow is determined directly from the carries, not from the carry flags, as their states may reflect
events in previous instructions.
*/
/**
* @class Int36
* @property {number} value
* @property {number|null} extended
* @property {number} magnitude
* @property {number|null} remainder
* @property {number} error
* @unrestricted
*
* The 'value' property stores the 36-bit value as an unsigned integer. When the value should be
* interpreted as a signed quantity, subtract BIT36 whenever value > INT_MASK.
*
* The 'extended' property stores an additional 36 bits of data from a multiplication, and can also
* provide an additional 36 bits of data to a division. Internally, it should be null whenever the
* value is not extended.
*
* The 'magnitude' property is normally 35 for non-extended values and 71 for extended values;
* it is set to 70 whenever 'value' and 'extended' are both converted to 35-bit values with matching
* signs; see setMagnitude().
*
* The 'remainder' property stores the remainder from the last division. You should assume it will
* be set to null by any other operation.
*
* The 'error' property records any error(s) from the last operation.
*/
class Int36 {
/**
* Int36(obj, extended)
*
* The constructor, which simply calls set(), creates an Int36 from either:
*
* 1) another Int36
* 2) a single 36-bit value, with an optional 36-bit extension
* 3) nothing (initial value will be zero)
*
* All internal Int36 values (ie, the value and any extension) are unsigned values in the range:
*
* 0 <= i <= Math.pow(2, 36) - 1
*
* The lower bound is ZERO and the upper bound is Int36.WORD_MASK. Whenever an Int36 value should be
* interpreted as a signed value, values above Int36.INT_MASK (ie, values with bit 35 set) should be
* converted to their signed counterpart by subtracting the value from Int36.WORD_LIMIT (aka WORD_MASK + 1);
* the easiest way to do that is call isNeg() to check the sign bit and then call negate() as
* appropriate.
*
* NOTE: We use modern bit numbering, where bit 0 is the right-most (least-significant) bit and
* bit 35 is the left-most bit. This is opposite of the PDP-10 convention, which defined bit 0 as the
* left-most bit and bit 35 as the right-most bit.
*
* Although the integer precision of JavaScript floating-point (IEEE 754 double-precision) numbers is:
*
* -Math.pow(2, 53) <= i <= Math.pow(2, 53)
*
* it seems unwise to ever permit our internal values to creep outside the 36-bit range, because
* floating-point operations will drop least-significant bits in favor of most-significant bits when a
* result becomes too large, which is the opposite of what integer operations traditionally do. There
* might be some optimization benefits to performing our internal 36-bit truncation "lazily", but at
* least initially, I prefer to truncate() the results of all 36-bit arithmetic operations immediately.
*
* Most of the Int36 operations come in two flavors: those that accept numbers, and those that accept
* another Int36. The latter are more efficient, because (as just explained) an Int36's internal value
* should always be in range, whereas external numbers could be out of range OR have a fractional value
* OR be something else entirely (NaN, Infinity, -Infinity, undefined, etc), so numeric inputs are
* always passed through the static validate() function.
*
* We could eliminate the two flavors and check each parameter's type, like we do in the constructor,
* but constructor calls are infrequent (if they're not, you're doing something wrong), whereas Int36-only
* operations should be as fast and unchecked as possible.
*
* @this {Int36}
* @param {Int36|number} [obj] (if omitted, the default is zero)
* @param {number|null} [extended]
*/
constructor(obj, extended)
{
this.set(obj, extended);
}
/**
* set(obj, extended)
*
* @this {Int36}
* @param {Int36|number} [obj] (if omitted, the default is zero)
* @param {number|null} [extended]
*/
set(obj = 0, extended)
{
if (obj instanceof Int36) {
this.value = obj.value;
this.extended = obj.extended;
this.magnitude = obj.magnitude;
this.remainder = obj.remainder;
}
else {
this.value = Int36.validate(obj || 0);
this.extended = null;
this.magnitude = 35;
/*
* NOTE: Surprisingly, isNaN(null) is false, whereas isNaN(undefined) is true. Go figure.
*/
if (extended != null && !isNaN(extended)) {
this.extended = Int36.validate(extended);
/*
* Since I'm not requiring there to be any relationship between these independently set values
* (ie, that their sign bits match), we have to treat this Int36 as a signed 71-bit value until
* further notice.
*/
this.magnitude = 71;
}
this.remainder = null;
}
this.error = Int36.ERROR.NONE;
}
/**
* toDecimal(fUnsigned)
*
* @this {Int36}
* @param {boolean} fUnsigned
* @return {string}
*/
toDecimal(fUnsigned)
{
let s = "", fNeg = false;
let i36Div = new Int36(10000000000);
let i36Rem = new Int36();
let i36Tmp = new Int36(this);
if (!fUnsigned && i36Tmp.isNeg()) {
i36Tmp.negate();
fNeg = true;
}
/*
* Conversion of any 72-bit value should take no more than 3 divisions by 10,000,000,000.
*/
let nMaxDivs = 3, quotient, minDigits;
do {
i36Tmp.div(i36Div);
/*
* In a perfect world, there would be no errors, because all calculations at this point
* are within known bounds. But let's make sure we don't produce garbage or spin our wheels.
*/
if (i36Tmp.error || i36Tmp.remainder >= 10000000000 || !nMaxDivs--) {
s = "error";
break;
}
quotient = i36Tmp.value || i36Tmp.extended;
let minDigits = (quotient? 10 : 1);
i36Rem.set(i36Tmp.remainder);
do {
i36Rem.divNum(10);
if (i36Rem.remainder < 0 || i36Rem.remainder > 9) {
quotient = 0;
s = "error";
break;
}
s = String.fromCharCode(0x30 + i36Rem.remainder) + s;
} while (--minDigits > 0 || i36Rem.value);
} while (quotient);
if (fNeg) s = '-' + s;
return s;
}
/**
* toString(radix, fUnsigned)
*
* @this {Int36}
* @param {number} [radix] (default is 10)
* @param {boolean} [fUnsigned] (default is signed for radix 10, unsigned for any other radix)
* @return {string}
*/
toString(radix = 10, fUnsigned = false)
{
if (radix == 10) {
return this.toDecimal(fUnsigned);
}
let value = this.value;
let extended = this.extended;
if (radix == 8) {
let s = Int36.octal(value);
if (extended != null) {
s = Int36.octal(extended) + ' ' + s;
}
if (this.remainder != null) {
s += ':' + Int36.octal(this.remainder);
}
if (DEBUG && this.error) {
if (this.error & Int36.ERROR.OVERFLOW) {
s += " overflow";
}
if (this.error & Int36.ERROR.UNDERFLOW) {
s += " underflow";
}
if (this.error & Int36.ERROR.DIVZERO) {
s += " divide-by-zero";
}
}
return s;
}
if (extended != null) {
/*
* TODO: Need a radix-independent solution for these extended (up to 72-bit) values,
* because after 52 bits, JavaScript will start dropping least-significant bits. Until
* then, you're better off sticking with octal (see above).
*/
value = extended * Int36.WORD_LIMIT + value;
}
return value.toString(radix);
}
/**
* truncate(result, operand, fSub)
*
* The range of valid results (0 - WORD_MASK) is divided into two equal sub-ranges: 0 to INT_MASK,
* where the sign bit is zero (the bottom range), and INT_MASK+1 to WORD_MASK (the top range), where
* the sign bit is one. During a single arithmetic operation, the result can "wrap around" from
* the bottom range to the top, or from the top range to the bottom, but it's an overflow/underflow
* error ONLY if the result "wraps across" the midpoint between the two ranges, producing an
* unnaturally small delta (<= INT_MASK).
*
* This can be confirmed independently by examining the sign bits (BIT35) of the original value
* (V), the operand (O), the result (R), as well as two intermediate calculations, VR = (V ^ R)
* and OR = (O ^ R), and a final calculation, E = (VR & OR). If E is set, then overflow (V == 0)
* or underflow (V == 1) occurred.
*
* In the case of subtraction (when fSub is true), consult the second table, which replaces OR
* with OV (O ^ V).
*
* V O R VR OR E
* - - - -- -- -
* 0 0 0 0 0 0
* 0 0 1 1 1 1 (adding positive to positive yielded negative: overflow)
* 0 1 0 0 1 0
* 0 1 1 1 0 0
* 1 0 0 1 0 0
* 1 0 1 0 1 0
* 1 1 0 1 1 1 (adding negative to negative yielded positive: underflow)
* 1 1 1 0 0 0
*
* V O R VR OV E
* - - - -- -- -
* 0 0 0 0 0 0
* 0 0 1 1 0 0
* 0 1 0 0 1 0
* 0 1 1 1 1 1 (subtracting negative from positive yielded negative: overflow)
* 1 0 0 1 1 1 (subtracting positive from negative yielded positive: underflow)
* 1 0 1 0 1 0
* 1 1 0 1 0 0
* 1 1 1 0 0 0
*
* NOTE: This function's job is to truncate the result of an operation to 36-bit accuracy,
* not to remove any fractional portion that might also exist. If an operation could have produced
* a non-integer result (eg, div()), it's the caller's responsibility to deal with that first.
*
* @this {Int36}
* @param {number} result
* @param {number} [operand]
* @param {boolean} [fSub] (true if operand was subtracted)
* @return {number}
*/
truncate(result, operand, fSub)
{
if (DEBUG && result !== Math.trunc(result)) {
console.log("Int36.truncate(" + result + " is not an integer)");
}
this.extended = null;
this.magnitude = 35;
this.remainder = null;
this.error = Int36.ERROR.NONE;
if (result < 0) {
result += Int36.WORD_LIMIT;
}
result %= Int36.WORD_LIMIT;
/*
* We don't actually need to know what the operand was to determine overflow or underflow
* for addition or subtraction, just the original value (this.value) the new value (result).
*
* We do, however, need to know the operand if we want to confirm our error calculation using
* the truth table above, which requires examining the sign bits of all the inputs and outputs.
*/
if (operand !== undefined) {
/*
* Calculating V, R, O, and E as described above is somewhat tedious, because bits
* above bit 31 cannot be accessed directly; we shift all the sign bits down to bit 0
* using division first. We don't need to truncate the results, because the subsequent
* bitwise operations perform truncation automatically.
*/
let e = 0, v = 0, r = 0, o = 0;
if (DEBUG) {
v = this.value / Int36.INT_LIMIT, r = result / Int36.INT_LIMIT, o = operand / Int36.INT_LIMIT;
e = ((v ^ r) & (o ^ (fSub? v : r))) & 1;
}
if ((result > Int36.INT_MASK) != (this.value > Int36.INT_MASK)) {
let delta = result - this.value;
if (Math.abs(delta) <= Int36.INT_MASK) {
this.error |= (delta > 0? Int36.ERROR.OVERFLOW : Int36.ERROR.UNDERFLOW);
if (DEBUG && (delta > 0) != !(e & v)) e = 0;
}
}
if (DEBUG && (!this.error) != (!e)) console.log("overflow inconsistency");
}
return result;
}
/**
* add(i36)
*
* @this {Int36}
* @param {Int36} i36
*/
add(i36)
{
this.value = this.truncate(this.value + i36.value, i36.value);
}
/**
* addNum(num)
*
* @this {Int36}
* @param {number} num
*/
addNum(num)
{
num = Int36.validate(num);
this.value = this.truncate(this.value + num, num);
}
/**
* sub(i36)
*
* @this {Int36}
* @param {Int36} i36
*/
sub(i36)
{
this.value = this.truncate(this.value - i36.value, i36.value, true);
}
/**
* subNum(num)
*
* @this {Int36}
* @param {number} num
*/
subNum(num)
{
num = Int36.validate(num);
this.value = this.truncate(this.value - num, num, true);
}
/**
* mul(i36)
*
* @this {Int36}
* @param {Int36} i36
*/
mul(i36)
{
this.mulExtended(i36.value);
}
/**
* mulNum(num)
*
* @this {Int36}
* @param {number} num
*/
mulNum(num)
{
this.mulExtended(Int36.validate(num));
}
/**
* mulExtended(value)
*
* To support 72-bit results, we perform the multiplication process as you would "by hand",
* treating the operands to be multiplied as two 2-digit numbers, where each "digit" is an 18-bit
* number (base 2^18). Each individual multiplication of these 18-bit "digits" will produce
* a result within 2^36, well within JavaScript integer accuracy.
*
* TODO: Review what should happen when the multiplicand is extended; currently, we simply ignore
* the extended portion.
*
* @this {Int36}
* @param {number} value
*/
mulExtended(value)
{
let fNeg = false, extended;
let n1 = this.value, n2 = value;
this.error = Int36.ERROR.NONE;
if (n1 > Int36.INT_MASK) {
n1 = Int36.WORD_LIMIT - n1;
fNeg = !fNeg;
}
if (n2 > Int36.INT_MASK) {
n2 = Int36.WORD_LIMIT - n2;
fNeg = !fNeg;
}
if (n1 < Int36.HALF_SHIFT && n2 < Int36.HALF_SHIFT) {
value = n1 * n2;
extended = 0;
}
else {
let n1d1 = (n1 % Int36.HALF_SHIFT);
let n1d2 = Math.trunc(n1 / Int36.HALF_SHIFT);
let n2d1 = (n2 % Int36.HALF_SHIFT);
let n2d2 = Math.trunc(n2 / Int36.HALF_SHIFT);
let m1d1 = n1d1 * n2d1;
let m1d2 = (n1d2 * n2d1) + Math.trunc(m1d1 / Int36.HALF_SHIFT);
extended = Math.trunc(m1d2 / Int36.HALF_SHIFT);
m1d2 = (m1d2 % Int36.HALF_SHIFT) + (n1d1 * n2d2);
value = (m1d2 * Int36.HALF_SHIFT) + (m1d1 % Int36.HALF_SHIFT);
extended += Math.trunc(m1d2 / Int36.HALF_SHIFT) + (n1d2 * n2d2);
}
this.value = this.truncate(value);
this.extended = this.truncate(extended);
this.magnitude = 71;
if (fNeg) this.negate();
this.setMagnitude(70);
}
/**
* div(i36)
*
* @this {Int36}
* @param {Int36} i36
*/
div(i36)
{
this.divExtended(i36.value);
}
/**
* divNum(num)
*
* @this {Int36}
* @param {number} num
*/
divNum(num)
{
this.divExtended(Int36.validate(num));
}
/**
* divExtended(divisor)
*
* We disallow a divisor of zero; however, we no longer disallow a divisor smaller than the
* extended portion of the dividend, even though such a divisor would produce a quotient larger
* than 36 bits. Instead, we support extended quotients, because some of our internal functions
* (eg, toDecimal()) require it.
*
* For callers that can only handle 36-bit quotients, they can either perform their own preliminary
* check of the divisor against any extended dividend, or they can simply allow all divisions to
* proceed, check for an extended quotient afterward, and report the appropriate error.
*
* @this {Int36}
* @param {number} divisor
*/
divExtended(divisor)
{
this.error = Int36.ERROR.NONE;
if (!divisor) {
this.error |= Int36.ERROR.DIVZERO;
return;
}
let fNegQ = false, fNegR = false;
if (divisor > Int36.INT_MASK) {
divisor = Int36.WORD_LIMIT - divisor;
fNegQ = !fNegQ;
}
if (this.isNeg()) {
this.negate();
fNegR = true; fNegQ = !fNegQ;
}
this.extend();
this.setMagnitude(71);
/*
* Initialize the four double-length 72-bit values we need for the division process.
*
* The process involves shifting the divisor left 1 bit (ie, doubling it) until it equals
* or exceeds the dividend, and then repeatedly subtracting the divisor from the dividend and
* shifting the divisor right 1 bit until the divisor is "exhausted" (no bits left), with an
* "early out" if the dividend gets "exhausted" first.
*
* Note that each element of these double arrays is a 36-bit value, so it's rarely a good idea
* to use bitwise operators on them, because those would operate on only the low 32 bits.
* Stick with the double worker functions I've created, and trust your JavaScript engine to
* inline/optimize the code.
*
* TODO: Profile this code to determine if individual variables (eg, dResLo and dResHi)
* instead of 2-element arrays is faster and/or less impactful on garbage collection. I prefer
* both the simplified syntax of arrays as well as their extensibility if we ever want/need
* to go beyond 72 bits.
*/
let dRes = [0, 0];
let dPow = [1, 0];
let dDiv = [divisor, 0];
let dRem = [this.value, this.extended];
while (Int36.cmpD(dRem, dDiv) > 0) {
Int36.addD(dDiv, dDiv);
Int36.addD(dPow, dPow);
}
do {
if (Int36.cmpD(dRem, dDiv) >= 0) {
Int36.subD(dRem, dDiv);
Int36.addD(dRes, dPow);
if (Int36.zeroD(dRem)) break;
}
Int36.shrD(dDiv);
Int36.shrD(dPow);
} while (!Int36.zeroD(dPow));
/*
* Since divisors are limited to 36-bit values, something's wrong if we have an extended remainder.
*/
this.assert(!dRem[1], "extended remainder");
this.value = dRes[0];
this.extended = dRes[1];
this.remainder = dRem[0];
if (fNegQ) this.negate();
this.reduce();
if (fNegR && this.remainder) {
this.remainder = Int36.WORD_LIMIT - this.remainder;
}
}
/**
* ash(i36)
*
* @this {Int36}
* @param {Int36} i36 (18-bit value representing the number of bits to arithmetically shift left (> 0) or right (< 0))
*/
ash(i36)
{
this.ashNum(i36.value);
}
/**
* ashNum(num)
*
* @this {Int36}
* @param {number} num (18-bit value representing the number of bits to arithmetically shift left (> 0) or right (< 0))
*/
ashNum(num)
{
num = Int36.validate(num);
this.error = Int36.ERROR.NONE;
/*
* Convert the unsigned 18-bit value in regEA to a signed 8-bit value (+/-255).
*/
let s = ((num << 14) >> 14) % 256;
if (this.extended == null) {
if (s) {
/*
* Simulate opASH()
*/
let w = this.value, bitsShifted;
/*
* Convert the unsigned word (w) to a signed value (i), for convenience.
*/
let i = w > Int36.INT_MASK? -(Int36.WORD_LIMIT - w) : w;
if (s > 0) {
if (s >= 35) {
i = (i < 0? Int36.INT_LIMIT : 0);
bitsShifted = Int36.INT_MASK;
} else {
i = (i * Math.pow(2, s)) % Int36.INT_LIMIT;
/*
* bitsShifted must be set to the mask of all magnitude bits shifted out of
* the original word. Using 8-bit signed words as an example, this table shows
* the bitsShifted values that would correspond to shifting 1-7 bits left:
*
* shifts bitsShifted value calculation
* ------ ----------- ------ -----------
* 1 0b01000000 128-64 128-Math.pow(2, 7-1)
* 2 0b01100000 128-32 128-Math.pow(2, 7-2)
* 3 0b01110000 128-16 128-Math.pow(2, 7-3)
* ...
* 7 0b01111111 128-1 128-Math.pow(2, 7-7)
*/
bitsShifted = Int36.INT_LIMIT - Math.pow(2, 35 - s);
}
if (w <= Int36.INT_MASK) {
/*
* Since w was positive, overflow occurs ONLY if any of the bits we shifted out were 1s.
* If all those bits in the original value (w) were 0s, then adding bitsShifted to it could NOT
* produce a value > INT_MASK.
*/
if (w + bitsShifted > Int36.INT_MASK) this.error |= Int36.ERROR.OVERFLOW;
} else {
/*
* Since w was negative, overflow occurs ONLY if any of the bits we shifted out were 0s.
* If all those bits in the original value (w) were 1s, subtracting bitsShifted from it could NOT
* produce a value <= INT_MASK.
*/
if (w - bitsShifted <= Int36.INT_MASK) this.error |= Int36.ERROR.OVERFLOW;
}
} else {
if (s <= -35) {
i = (i < 0? -1 : 0);
} else {
i = Math.trunc(i / Math.pow(2, -s));
}
}
w = (i < 0? i + Int36.WORD_LIMIT : i);
this.value = w;
}
}
else {
if (s) {
/*
* Simulate opASHC()
*
* TODO: Note that we force the result to be treated as a 70-bit result (we set magnitude to 70 at the end);
* however, what should happen if the incoming value wasn't a proper 70-bit value (ie, if the signs of value
* and extended didn't match)?
*/
let bits;
let wRight = this.value;
let wLeft = this.extended;
if (s > 0) {
/*
* Handle all the left shift cases below, which don't need to worry about sign-extension
* but DO need to worry about overflow.
*/
let wLeftOrig = wLeft;
if (s >= 36) {
/*
* Since all wLeft bits are being shifted out, any positive value other than zero OR any negative value
* other than WORD_MASK indicates an overflow.
*/
if (wLeft > 0 && wLeft < Int36.INT_LIMIT || wLeft > Int36.INT_MASK && wLeft < Int36.WORD_MASK) {
this.error |= Int36.ERROR.OVERFLOW;
}
if (s >= 71) {
/*
* Since all wRight bits are being shifted out, any positive value other than zero OR any negative value
* other than WORD_MASK indicates an overflow.
*/
if (wRight > 0 && wRight < Int36.INT_LIMIT || wRight > Int36.INT_MASK && wRight < Int36.WORD_MASK) {
this.error |= Int36.ERROR.OVERFLOW;
}
wLeft = 0;
} else {
/*
* Left shift 36-70 bits.
*/
wLeft = (wRight * Math.pow(2, s - 35)) % Int36.INT_LIMIT;
bits = Int36.INT_LIMIT - Math.pow(2, 70 - s);
if (wLeftOrig <= Int36.INT_MASK) {
/*
* Since wLeft was positive, overflow occurs ONLY if any of the bits we shifted out were 1s.
* If all those bits in the original value were 0s, then adding bits to it could NOT produce
* a value > INT_MASK.
*/
if (wRight + bits > Int36.INT_MASK) this.error |= Int36.ERROR.OVERFLOW;
} else {
/*
* Since wLeft was negative, overflow occurs ONLY if any of the bits we shifted out were 0s.
* If all those bits in the original value were 1s, subtracting bits from it could NOT produce
* a value <= INT_MASK.
*/
if (wRight - bits <= Int36.INT_MASK) this.error |= Int36.ERROR.OVERFLOW;
}
}
wRight = 0;
if (wLeftOrig > Int36.INT_MASK) {
wLeft += Int36.INT_LIMIT;
wRight += Int36.INT_LIMIT;
}
} else {
/*
* Left shift 1-35 bits.
*/
wLeft = ((wLeft * Math.pow(2, s)) % Int36.INT_LIMIT) + Math.trunc((wRight % Int36.INT_LIMIT) / Math.pow(2, 35 - s));
wRight = (wRight * Math.pow(2, s)) % Int36.INT_LIMIT;
/*
* Determine overflow and update the sign bits.
*/
bits = Int36.INT_LIMIT - Math.pow(2, 35 - s);
if (wLeftOrig <= Int36.INT_MASK) {
/*
* Since wLeft was positive, overflow occurs ONLY if any of the bits we shifted out were 1s.
* If all those bits in the original value were 0s, then adding bits to it could NOT produce
* a value > INT_MASK.
*/
if (wLeftOrig + bits > Int36.INT_MASK) this.error |= Int36.ERROR.OVERFLOW;
} else {
/*
* Since wLeft was negative, overflow occurs ONLY if any of the bits we shifted out were 0s.
* If all those bits in the original value were 1s, subtracting bits from it could NOT produce
* a value <= INT_MASK.
*/
if (wLeftOrig - bits <= Int36.INT_MASK) this.error |= Int36.ERROR.OVERFLOW;
/*
* Last but not least, update the sign bits of wLeft and wRight to indicate negative values.
*/
wLeft += Int36.INT_LIMIT;
wRight += Int36.INT_LIMIT;
}
}
} else {
/*
* Handle all the right shift cases below, which don't need to worry about overflow but DO
* need to worry about sign-extension.
*/
if (s <= -36) {
if (s <= -72) {
wRight = (wLeft > Int36.INT_MASK? Int36.WORD_MASK : 0);
} else {
wRight = Math.trunc((wLeft % Int36.INT_LIMIT) / Math.pow(2, -s - 35));
}
if (wLeft <= Int36.INT_MASK) {
wLeft = 0;
} else {
wLeft = Int36.WORD_MASK;
wRight += Int36.INT_LIMIT;
}
} else {
/*
* For this right shift of 1-35 bits, determine the value of bits shifted in from the left.
*/
bits = (wLeft > Int36.INT_MASK? Int36.WORD_LIMIT - Math.pow(2, 36 + s) : 0);
/*
* The bits that we add to wRight from wLeft must be shifted right one additional bit, because
* they must "skip over" the sign bit of wRight. This means we must zero the sign bit of wRight,
* which can be done by performing a "mod" with INT_LIMIT.
*/
wRight = Math.trunc((wRight % Int36.INT_LIMIT) / Math.pow(2, -s)) + (((wLeft % Int36.INT_LIMIT) * Math.pow(2, 35 + s)) % Int36.INT_LIMIT);
wLeft = Math.trunc(wLeft / Math.pow(2, -s)) + bits;
/*
* Last but not least, we must set the sign of wRight to the sign of wLeft.
*/
if (wLeft > Int36.INT_MASK) wRight += Int36.INT_LIMIT;
}
}
this.value = wRight;
this.extended = wLeft;
}
/*
* We allow ashNum(0) as a way of marking an extended Int36 as a 70-bit value.
*/
this.magnitude = 70;
}
}
/**
* lsh(i36)
*
* @this {Int36}
* @param {Int36} i36 (18-bit value representing the number of bits to logically shift left (> 0) or right (< 0))
*/
lsh(i36)
{
this.lshNum(i36.value);
}
/**
* lshNum(num)
*
* @this {Int36}
* @param {number} num (18-bit value representing the number of bits to logically shift left (> 0) or right (< 0))
*/
lshNum(num)
{
num = Int36.validate(num);
this.error = Int36.ERROR.NONE;
/*
* Convert the unsigned 18-bit value in regEA to a signed 8-bit value (+/-255).
*/
let s = ((num << 14) >> 14) % 256;
if (this.extended == null) {
if (s) {
let w = this.value;
if (s > 0) {
if (s >= 36) {
w = 0;
} else {
w = (w * Math.pow(2, s)) % Int36.WORD_LIMIT;
}
} else {
if (s <= -36) {
w = 0;
} else {
w = Math.trunc(w / Math.pow(2, -s));
}
}
this.value = w;
}
} else {
if (s) {
let wRight = this.value;
let wLeft = this.extended;
if (s > 0) {
if (s >= 36) {
wRight = 0;
if (s >= 72) {
wLeft = 0;
} else {
wLeft = (wRight * Math.pow(2, s - 36)) % Int36.WORD_LIMIT;
}
} else {
wLeft = ((wLeft * Math.pow(2, s)) % Int36.WORD_LIMIT) + Math.trunc(wRight / Math.pow(2, 36 - s));
wRight = (wRight * Math.pow(2, s)) % Int36.WORD_LIMIT;
}
} else {
if (s <= -36) {
wLeft = 0;
if (s <= -72) {
wRight = 0;
} else {
wRight = Math.trunc(wLeft / Math.pow(2, -s - 36));
}
} else {
wRight = Math.trunc(wRight / Math.pow(2, -s)) + ((wLeft * Math.pow(2, 36 + s)) % Int36.WORD_LIMIT);
wLeft = Math.trunc(wLeft / Math.pow(2, -s));
}
}
this.value = wRight;
this.extended = wLeft;
}
/*
* We allow lshNum(0) as a way of marking an extended Int36 as a 71-bit value.
*
* TODO: Perhaps we should support magnitude 72 as way of indicating that the value should be treated as an
* unsigned 72-bit value?
*/
this.magnitude = 71;
}
}
/**
* rot(i36)
*
* @this {Int36}
* @param {Int36} i36 (18-bit value representing the number of bits to logically rotate left (> 0) or right (< 0))
*/
rot(i36)
{
this.rotNum(i36.value);
}
/**
* rotNum(num)
*
* @this {Int36}
* @param {number} num (18-bit value representing the number of bits to logically rotate left (> 0) or right (< 0))
*/
rotNum(num)
{
let s;
num = Int36.validate(num);