From 5512dacb0a744dd95dffc8ef97b770240d18339f Mon Sep 17 00:00:00 2001 From: BorysLevytskyi Date: Wed, 10 May 2023 22:00:40 +0200 Subject: [PATCH] Fix bus when converting unsigned to signed numbers --- src/core/Integer.test.ts | 21 +++++++++++++++-- src/core/Integer.ts | 47 ++++++++++++++++++++++++++------------ src/core/calc.ts | 1 - src/core/utils.tsx | 6 ++++- src/expression/Operator.ts | 2 -- 5 files changed, 57 insertions(+), 20 deletions(-) diff --git a/src/core/Integer.test.ts b/src/core/Integer.test.ts index a59d86e..3b7bbe5 100644 --- a/src/core/Integer.test.ts +++ b/src/core/Integer.test.ts @@ -1,5 +1,15 @@ import { Integer } from "./Integer" -import { UINT32_MAX_VALUE } from "./const"; +import { INT32_MAX_VALUE, UINT32_MAX_VALUE } from "./const"; + +it('toString for unsigned', () => { + const s = Integer.unsigned(4294967295).toString(2) + expect(s).toBe("11111111111111111111111111111111"); +}); + +it('detects size correctly for signed and unsiged version', () => { + expect(Integer.unsigned(UINT32_MAX_VALUE).maxBitSize).toBe(32); + expect(Integer.signed(UINT32_MAX_VALUE).maxBitSize).toBe(64); +}) it('converts signed to unsigned and vice versa', () => { const n1 = new Integer(-1, 8); @@ -21,7 +31,14 @@ it('convers to different type', () => { const n = src.convertTo(dest); expect(n.num()).toBe(UINT32_MAX_VALUE); -}) +}); + +it('doesnt modify 64-bit number when converting from usinged to signed', () => { + + expect(Integer.unsigned(4294967295).toSigned().num()).toBe(-1); + expect(Integer.unsigned(2147483647).toSigned().num()).toBe(2147483647); +}); + it('converts to largest size', () => { const n8 = new Integer(-1, 8); diff --git a/src/core/Integer.ts b/src/core/Integer.ts index 020709a..7d78d79 100644 --- a/src/core/Integer.ts +++ b/src/core/Integer.ts @@ -1,11 +1,11 @@ import { type } from "os"; -import { INT32_MAX_VALUE, INT32_MIN_VALUE } from "./const"; -import { asIntN } from "./utils"; +import { INT32_MAX_VALUE, INT32_MIN_VALUE, UINT32_MAX_VALUE } from "./const"; +import { asIntN, logLines as ln } from "./utils"; import formatter from "./formatter"; import calc from "./calc"; export type JsNumber = number | bigint; - +export type IntegerInput = JsNumber | string; export class Integer { @@ -13,29 +13,37 @@ export class Integer { readonly maxBitSize: number; readonly signed: boolean; - constructor(value: JsNumber, maxBitSize?: number, signed? : boolean) { + constructor(value: IntegerInput, maxBitSize?: number, signed? : boolean) { + this.value = typeof value == "bigint" ? value : BigInt(value); - this.maxBitSize = maxBitSize != null ? maxBitSize : (value >= INT32_MIN_VALUE && value <= INT32_MAX_VALUE) ? 32 : 64; this.signed = signed == null ? true : signed == true; + this.maxBitSize = maxBitSize != null ? maxBitSize : detectSize(this.value, this.signed); + + if(!this.signed && this.value < 0) + throw new Error("Value " + value + " cannot be negative if the type is unsigned"); } - static unsigned(value : JsNumber, maxBitSize?: number) { + static unsigned(value : IntegerInput, maxBitSize?: number) { return new Integer(value, maxBitSize, false); } - static long(value: JsNumber) : Integer { + static signed(value : IntegerInput, maxBitSize?: number) { + return new Integer(value, maxBitSize, true); + } + + static long(value: IntegerInput) : Integer { return new Integer(value, 64); } - static int(value: JsNumber) : Integer { + static int(value: IntegerInput) : Integer { return new Integer(value, 32); } - static short(value: JsNumber) : Integer { + static short(value: IntegerInput) : Integer { return new Integer(value, 16); } - static byte(value: JsNumber) : Integer { + static byte(value: IntegerInput) : Integer { return new Integer(value, 8); } @@ -51,10 +59,13 @@ export class Integer { if(this.signed) return new Integer(this.value, this.maxBitSize, this.signed); - const bin = calc.engine.applyTwosComplement(this.toString(2)); - const n = BigInt("0b"+bin); + const orig = this.toString(2).padStart(this.maxBitSize, '0'); + + const inverted = orig[0] == '1' ? calc.engine.applyTwosComplement(orig) : orig; + const n = BigInt("0b"+inverted); + const negative = orig[0] == '1'; - return new Integer(bin[0] == '1' ? n : -n, this.maxBitSize, true) + return new Integer(negative ? -n : n, this.maxBitSize, true) } resize(newSize: number) { @@ -115,4 +126,12 @@ export function asInteger(num: JsNumber | Integer | string): Integer { export function isInteger(num: JsNumber | Integer): num is Integer { return (num).maxBitSize !== undefined; - } \ No newline at end of file +} +function detectSize(value: bigint, signed: boolean): number { + + if(!signed) + return value > UINT32_MAX_VALUE ? 64 : 32; + else + return value < INT32_MIN_VALUE || value > INT32_MAX_VALUE ? 64 : 32; +} + diff --git a/src/core/calc.ts b/src/core/calc.ts index f4a53ff..9467d77 100644 --- a/src/core/calc.ts +++ b/src/core/calc.ts @@ -247,7 +247,6 @@ function nextPowOfTwo(num: number) : number { } function equalizeSize(n1: Integer, n2: Integer) : [Integer, Integer] { - console.log('equalizeSize()', new Error().stack); if(n1.maxBitSize == n2.maxBitSize) { if(n1.signed === n2.signed) return [n1,n2]; diff --git a/src/core/utils.tsx b/src/core/utils.tsx index 2744641..3771f2f 100644 --- a/src/core/utils.tsx +++ b/src/core/utils.tsx @@ -26,4 +26,8 @@ function randomBool() { return random(1, 10000) % 2 == 0; } -export {chunkifyString, asIntN, random, randomBool}; \ No newline at end of file +function logLines(...params: any[]) { + console.log(params.join('\n')) +} + +export {chunkifyString, asIntN, random, randomBool, logLines}; \ No newline at end of file diff --git a/src/expression/Operator.ts b/src/expression/Operator.ts index b1044c5..656ed40 100644 --- a/src/expression/Operator.ts +++ b/src/expression/Operator.ts @@ -58,9 +58,7 @@ function applyOperator(op1 : Operand, operator: string, op2 : Operand) : Operand equalizeSize(op1, op2); } - console.log(op1.value, operator, op2.value); const result = calc.operation(op1.value, operator, op2.value); - console.log('=', result); return new Operand(result, op2.base); }