From a12274940ec8cdff09392017bd7f2ce4a4c5e843 Mon Sep 17 00:00:00 2001 From: BorysLevytskyi Date: Mon, 8 May 2023 16:49:53 +0200 Subject: [PATCH] Correct support rshift for 64-bit numbers --- src/core/calc.test.ts | 94 +++++++++++++++++++++++----------- src/core/calc.ts | 54 ++++++++++++++----- src/core/formatter.test.ts | 8 +-- src/core/formatter.ts | 11 +--- src/core/utils.tsx | 8 ++- src/expression/engine.ts | 3 +- src/expression/numberParser.ts | 2 +- 7 files changed, 122 insertions(+), 58 deletions(-) diff --git a/src/core/calc.test.ts b/src/core/calc.test.ts index 092db6b..dbf1dfe 100644 --- a/src/core/calc.test.ts +++ b/src/core/calc.test.ts @@ -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", () => { diff --git a/src/core/calc.ts b/src/core/calc.ts index bf5b395..16f3903 100644 --- a/src/core/calc.ts +++ b/src/core/calc.ts @@ -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 diff --git a/src/core/formatter.test.ts b/src/core/formatter.test.ts index 522333f..fadcd5c 100644 --- a/src/core/formatter.test.ts +++ b/src/core/formatter.test.ts @@ -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"); }); diff --git a/src/core/formatter.ts b/src/core/formatter.ts index 309739a..8c138f5 100644 --- a/src/core/formatter.ts +++ b/src/core/formatter.ts @@ -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: diff --git a/src/core/utils.tsx b/src/core/utils.tsx index a2c3b1a..cdf69ca 100644 --- a/src/core/utils.tsx +++ b/src/core/utils.tsx @@ -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}; \ No newline at end of file +function asIntN(num: NumberType) : number { + return typeof num == "bigint" ? parseInt(num.toString()): num as number; +} + +export {chunkifyString, asIntN}; \ No newline at end of file diff --git a/src/expression/engine.ts b/src/expression/engine.ts index e7d6ae5..2a2c718 100644 --- a/src/expression/engine.ts +++ b/src/expression/engine.ts @@ -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); diff --git a/src/expression/numberParser.ts b/src/expression/numberParser.ts index ed13bf7..d0b6d41 100644 --- a/src/expression/numberParser.ts +++ b/src/expression/numberParser.ts @@ -13,7 +13,7 @@ interface ParserConfig { } export interface ParsedNumber { - value: number|bigint; + value: NumberType; base: NumberBase; input: string; }