Correct support rshift for 64-bit numbers

This commit is contained in:
BorysLevytskyi
2023-05-08 16:49:53 +02:00
parent 86adc59c59
commit a12274940e
7 changed files with 122 additions and 58 deletions

View File

@@ -3,35 +3,7 @@ import { BitwiseOperationExpression, ScalarValue, BitwiseOperator } from '../exp
import { INT32_MAX_VALUE } from './const';
import exp from 'constants';
describe("calc", () => {
it('calculates number of bits', () => {
expect(calc.numberOfBitsDisplayed(1)).toBe(1);
expect(calc.numberOfBitsDisplayed(BigInt(-1))).toBe(64);
expect(calc.numberOfBitsDisplayed(2)).toBe(2);
expect(calc.numberOfBitsDisplayed(3)).toBe(2);
expect(calc.numberOfBitsDisplayed(68719476735)).toBe(36);
expect(calc.numberOfBitsDisplayed(-INT32_MAX_VALUE)).toBe(32);
expect(calc.numberOfBitsDisplayed(-(BigInt(INT32_MAX_VALUE+1)))).toBe(64);
});
it('calculates max number of bits', () => {
expect(calc.maxNumberOfBitsDisplayed([1, 2, 3, 10])).toBe(4);
});
it('calculates expression', () => {
var result = calc.calcExpression(new BitwiseOperationExpression(
"1|2&3",
[
new ScalarValue(1),
new BitwiseOperator(new ScalarValue(2), "|"),
new BitwiseOperator(new ScalarValue(3), "&"),
]
));
expect(result).toBe(3);
});
describe('calc.flipBit', () => {
it('calculates flipped bit 32-bit number', () => {
expect(calc.flipBit(0, 31)).toBe(1);
expect(calc.flipBit(1, 31)).toBe(0);
@@ -54,7 +26,26 @@ describe("calc", () => {
expect(calc.flipBit(-1, 0)).toBe(2147483647);
expect(calc.flipBit(2147483647, 30)).toBe(2147483645);
});
});
describe('calc.numberOfBitsDisplayed', () => {
it('calculates number of bits', () => {
expect(calc.numberOfBitsDisplayed(1)).toBe(1);
expect(calc.numberOfBitsDisplayed(BigInt(-1))).toBe(64);
expect(calc.numberOfBitsDisplayed(2)).toBe(2);
expect(calc.numberOfBitsDisplayed(3)).toBe(2);
expect(calc.numberOfBitsDisplayed(68719476735)).toBe(36);
expect(calc.numberOfBitsDisplayed(-INT32_MAX_VALUE)).toBe(32);
expect(calc.numberOfBitsDisplayed(-(BigInt(INT32_MAX_VALUE+1)))).toBe(64);
});
it('maxNumberOfBitsDisplayed', () => {
expect(calc.maxNumberOfBitsDisplayed([1, 2, 3, 10])).toBe(4);
});
});
describe('calc.applyTwosComplement', () => {
it('applies twos complement', () => {
expect(calc.applyTwosComplement("010")).toBe("110");
expect(calc.applyTwosComplement("110")).toBe("010"); // reverse
@@ -63,6 +54,48 @@ describe("calc", () => {
expect(calc.applyTwosComplement("10101100")).toBe("01010100");
expect(calc.applyTwosComplement("01010100")).toBe("10101100"); // reverse
});
});
describe('calc.rshift', () => {
it('produces number when given number and vice vers', () => {
const number = calc.rshift(1, 1, 32);
const bigInt = calc.rshift(BigInt(1), 1, 32);
expect(typeof number).toBe('number');
expect(number).toBe(2);
expect(typeof bigInt).toBe('bigint');
expect(bigInt.toString()).toBe('2');
});
it("respects bit size", () => {
expect(calc.rshift(BigInt("0b0100"), 2, 4).toString()).toBe("0");
});
it('transitions number to negative', ()=> {
// 4-bit space
expect(calc.rshift(BigInt("0b0100"), 1, 4).toString()).toBe("-8");
// 5-bit space
expect(calc.rshift(BigInt("0b00100"), 1, 5).toString()).toBe("8");
expect(calc.rshift(BigInt("0b01000"), 1, 5).toString()).toBe("-16");
// 32-bit
expect(calc.rshift(BigInt("2147483647"), 1, 32).toString()).toBe("-2");
expect(calc.rshift(BigInt("2147483647"), 2, 32).toString()).toBe("-4");
// 64-bit
expect(calc.rshift(BigInt("9223372036854775807"), 1, 64).toString()).toBe("-2");
expect(calc.rshift(BigInt("9223372036854775807"), 2, 64).toString()).toBe("-4");
expect(calc.rshift(BigInt("2147483647"), 1, 64).toString()).toBe("4294967294");
expect(calc.rshift(BigInt("2147483647"), 2, 64).toString()).toBe("8589934588");
});
});
describe("calc misc", () => {
it('calcualte 31th bit in 64-bit int', () => {
expect(calc.flipBit(calc.promoteToBigInt(-1), 31).toString()).toBe("8589934591");
@@ -71,10 +104,13 @@ describe("calc", () => {
it('promotes to BigInt with the same bits', () => {
expect(calc.promoteToBigInt(-1).toString(2)).toBe("11111111111111111111111111111111");
});
});
describe("bitwise ", () => {
it("NOT same as in node", () => {

View File

@@ -1,12 +1,19 @@
import { type } from "os";
import { Expression } from "../expression/expression-interfaces";
import formatter from "./formatter";
import { NumberType } from "./types";
import { asIntN } from "./utils";
export default {
abs (num : NumberType) : NumberType {
return num >= 0 ? num : -num;
},
numberOfBitsDisplayed: function (num: number|bigint) : number {
maxBitSize(num : NumberType) : number {
return typeof num == "bigint" ? 64 : 32;
},
numberOfBitsDisplayed: function (num: NumberType) : number {
if(num < 0) {
return typeof num == 'bigint' ? 64 : 32
@@ -26,31 +33,21 @@ export default {
return Math.max.apply(null, counts);
},
calcExpression: function (expr: Expression) {
return eval(expr.expressionString);
},
flipBit: function(num: number|bigint, index: number): number|bigint {
flipBit: function(num: NumberType, index: number): NumberType {
const is64bit = typeof num == 'bigint';
const size = typeof num == "bigint" ? 64 : 32;
const bin = formatter.bin(num).padStart(size, '0');
const staysNegative = (bin[0] == "1" && index > 0);
const becomesNegative = (bin[0] == "0" && index == 0);
//console.log(bin);
let m = 1;
let flipped = bin.substring(0, index) + flip(bin[index]) + bin.substring(index+1);
//console.log(flipped);
if(staysNegative || becomesNegative) {
flipped = this.applyTwosComplement(flipped);
m=-1;
}
//console.log(flipped);
return is64bit ? BigInt("0b"+ flipped)*BigInt(m) : parseInt(flipped, 2)*m;
},
@@ -83,7 +80,38 @@ export default {
return bin.split('').map(b => b=="1"?"0":"1").join("");
},
bitwise: {
binaryRepresentation(num : NumberType, bitSize?: number) : string {
bitSize = bitSize || typeof num == "bigint" ? 64 : 32;
const bin = this.abs(num).toString(2);
if(bin.length > bitSize!)
throw new Error(`Binary represenation '${bin}' is bigger than the given bit size ${bitSize}`)
return num < 0
? this.applyTwosComplement(bin.padStart(bitSize, '0'))
: bin;
},
rshift (num: NumberType, numBytes : NumberType, bitSize: number) : NumberType {
const bytes = asIntN(numBytes);
let bin = this.binaryRepresentation(num, bitSize).padStart(bitSize, '0');
bin = bin.substring(bytes) + "0".repeat(bytes);
let m = BigInt(1);
if(bin['0'] == '1') {
bin = this.applyTwosComplement(bin);
m = BigInt(-1);
}
const result = BigInt("0b" + bin) * m;
return typeof num == "bigint" ? result : asIntN(result);
},
bitwise: {
not: (bin: string) : string => {
var padded = bin

View File

@@ -13,16 +13,16 @@ describe("formatter", () => {
});
it('formats large binary number correctly', () => {
var decimal = 68719476735;
var binary = formatter.bin(68719476735);
var decimal = BigInt("68719476735");
var binary = formatter.bin(decimal);
var hex = formatter.numberToString(decimal, 'hex');
expect(binary).toBe('111111111111111111111111111111111111');
expect(hex).toBe('0xfffffffff');
});
it('formats negative binary numbers', () => {
//expect(formatter.numberToString(-1, 'bin')).toBe("11111111111111111111111111111111");
//expect(formatter.numberToString(-0, 'bin')).toBe("0");
expect(formatter.numberToString(-1, 'bin')).toBe("11111111111111111111111111111111");
expect(formatter.numberToString(-0, 'bin')).toBe("0");
expect(formatter.numberToString(-2147483647, 'bin')).toBe("10000000000000000000000000000001");
});

View File

@@ -3,21 +3,14 @@ import { NumberType } from "./types";
export type NumberBase = 'dec' | 'hex' | 'bin';
const formatter = {
numberToString: function(num: number|bigint, base: NumberBase) : string {
numberToString: function(num: NumberType, base: NumberBase) : string {
switch(base) {
case 'hex':
var hexVal = calc.abs(num).toString(16);
return num >= 0 ? '0x' + hexVal : '-0x' + hexVal;
case 'bin':
if(num < 0) {
const size = calc.numberOfBitsDisplayed(num);
const absBin = calc.abs(num).toString(2).padStart(size, '0');
return calc.applyTwosComplement(absBin);
}
return num.toString(2);
return calc.binaryRepresentation(num);
case 'dec':
return num.toString(10);
default:

View File

@@ -1,3 +1,5 @@
import { NumberType } from "./types";
function chunkifyString(input: string, chunkSize: number) : string[] {
const result : string[] = [];
@@ -9,4 +11,8 @@ function chunkifyString(input: string, chunkSize: number) : string[] {
return result;
}
export {chunkifyString};
function asIntN(num: NumberType) : number {
return typeof num == "bigint" ? parseInt(num.toString()): num as number;
}
export {chunkifyString, asIntN};

View File

@@ -1,3 +1,4 @@
import calc from "../core/calc";
import { NumberType } from "../core/types";
import ScalarValue from "./ScalarValue";
@@ -20,7 +21,7 @@ function evalute(op1 : NumberType, operator: string, op2 : NumberType) : NumberT
switch(operator) {
case ">>": return (a >> b) as (NumberType);
case ">>>": return (a >>> b) as (NumberType);
case "<<": return (a << b) as (NumberType);
case "<<": return calc.rshift(a, b, calc.maxBitSize(a));
case "&": return (b & a) as (NumberType);
case "|": return (b | a) as (NumberType);
case "^": return (b ^ a) as (NumberType);

View File

@@ -13,7 +13,7 @@ interface ParserConfig {
}
export interface ParsedNumber {
value: number|bigint;
value: NumberType;
base: NumberBase;
input: string;
}