diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fb250b6c..1145e3891 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `log2` and `log` math functions in stdlib: PR [#166](https://github.com/tact-lang/tact/pull/166) + ### Changed ### Fixed diff --git a/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap b/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap index 22d73653d..eabe16777 100644 --- a/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap +++ b/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap @@ -2972,6 +2972,38 @@ return b.end_cell().begin_parse();", "name": "__tact_float_to_string", "signature": "slice __tact_float_to_string(int src, int digits)", }, + { + "code": { + "code": "asm "UBITSIZE DEC"", + "kind": "asm", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_log2", + "signature": "int __tact_log2(int num)", + }, + { + "code": { + "code": "if (num < base) { + return 0; +} +int result = 0; +while (num >= base) { + num /= base; + result += 1; +} +return result;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_log", + "signature": "int __tact_log(int num, int base)", + }, { "code": { "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; @@ -6795,6 +6827,38 @@ return b.end_cell().begin_parse();", "name": "__tact_float_to_string", "signature": "slice __tact_float_to_string(int src, int digits)", }, + { + "code": { + "code": "asm "UBITSIZE DEC"", + "kind": "asm", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_log2", + "signature": "int __tact_log2(int num)", + }, + { + "code": { + "code": "if (num < base) { + return 0; +} +int result = 0; +while (num >= base) { + num /= base; + result += 1; +} +return result;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_log", + "signature": "int __tact_log(int num, int base)", + }, { "code": { "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g) = v; @@ -10618,6 +10682,38 @@ return b.end_cell().begin_parse();", "name": "__tact_float_to_string", "signature": "slice __tact_float_to_string(int src, int digits)", }, + { + "code": { + "code": "asm "UBITSIZE DEC"", + "kind": "asm", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_log2", + "signature": "int __tact_log2(int num)", + }, + { + "code": { + "code": "if (num < base) { + return 0; +} +int result = 0; +while (num >= base) { + num /= base; + result += 1; +} +return result;", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set {}, + "name": "__tact_log", + "signature": "int __tact_log(int num, int base)", + }, { "code": { "code": "var (v'a, v'b, v'c, v'd, v'e, v'f, v'g, v'h) = v; diff --git a/src/generator/writers/writeStdlib.ts b/src/generator/writers/writeStdlib.ts index c1b2c6096..43a79d05d 100644 --- a/src/generator/writers/writeStdlib.ts +++ b/src/generator/writers/writeStdlib.ts @@ -1223,4 +1223,28 @@ export function writeStdlib(ctx: WriterContext) { `); }); }); + + ctx.fun(`__tact_log2`, () => { + ctx.signature(`int __tact_log2(int num)`); + ctx.context('stdlib'); + ctx.asm(`asm "UBITSIZE DEC"`); + }); + + ctx.fun(`__tact_log`, () => { + ctx.signature(`int __tact_log(int num, int base)`); + ctx.context('stdlib'); + ctx.body(() => { + ctx.write(` + if (num < base) { + return 0; + } + int result = 0; + while (num >= base) { + num /= base; + result += 1; + } + return result; + `); + }); + }); } \ No newline at end of file diff --git a/src/imports/stdlib.ts b/src/imports/stdlib.ts index 2431a8481..05b90b7fb 100644 --- a/src/imports/stdlib.ts +++ b/src/imports/stdlib.ts @@ -183,7 +183,9 @@ files['std/math.tact'] = 'CmlubGluZSBmdW4gcmFuZG9tSW50KCk6IEludCB7CiAgICBuYXRpdmVQcmVwYXJlUmFuZG9tKCk7CiAgICByZXR1cm4gbmF0aXZlUmFuZG9tKCk7Cn0KCmlubGluZSBm' + 'dW4gcmFuZG9tKG1pbjogSW50LCBtYXg6IEludCk6IEludCB7CiAgICBuYXRpdmVQcmVwYXJlUmFuZG9tKCk7CiAgICByZXR1cm4gbWluICsgbmF0aXZlUmFuZG9tSW50' + 'ZXJ2YWwobWF4IC0gbWluKTsKfQoKLy8gTWF0aAoKQG5hbWUobWluKQpuYXRpdmUgbWluKHg6IEludCwgeTogSW50KTogSW50OwoKQG5hbWUobWF4KQpuYXRpdmUgbWF4' + - 'KHg6IEludCwgeTogSW50KTogSW50OwoKQG5hbWUoYWJzKQpuYXRpdmUgYWJzKHg6IEludCk6IEludDsKCkBuYW1lKG5vdykKbmF0aXZlIG5vdygpOiBJbnQ7'; + 'KHg6IEludCwgeTogSW50KTogSW50OwoKQG5hbWUoYWJzKQpuYXRpdmUgYWJzKHg6IEludCk6IEludDsKCkBuYW1lKG5vdykKbmF0aXZlIG5vdygpOiBJbnQ7CgpAbmFt' + + 'ZShfX3RhY3RfbG9nMikKbmF0aXZlIGxvZzIobnVtOiBJbnQpOiBJbnQ7CgpAbmFtZShfX3RhY3RfbG9nKQpuYXRpdmUgbG9nKG51bTogSW50LCBiYXNlOiBJbnQpOiBJ' + + 'bnQ7'; files['std/primitives.tact'] = 'cHJpbWl0aXZlIEludDsKcHJpbWl0aXZlIEJvb2w7CnByaW1pdGl2ZSBCdWlsZGVyOwpwcmltaXRpdmUgU2xpY2U7CnByaW1pdGl2ZSBDZWxsOwpwcmltaXRpdmUgQWRk' + 'cmVzczsKcHJpbWl0aXZlIFN0cmluZzsKcHJpbWl0aXZlIFN0cmluZ0J1aWxkZXI7'; diff --git a/src/test/feature-math.spec.ts b/src/test/feature-math.spec.ts index 5eb3ed23a..740df83b4 100644 --- a/src/test/feature-math.spec.ts +++ b/src/test/feature-math.spec.ts @@ -7,8 +7,7 @@ describe('feature-math', () => { beforeEach(() => { __DANGER_resetNodeId(); }); - it('should perform basic math operations correctly', async () => { - + it('should perform math operations correctly', async () => { // Init const system = await ContractSystem.create(); const treasure = system.treasure('treasure'); @@ -25,11 +24,15 @@ describe('feature-math', () => { .storeBit(1) .storeRef(beginCell().storeBit(1).endCell()) .endCell(); - const stringA = "foo"; - const stringB = "bar"; + const stringA = 'foo'; + const stringB = 'bar'; const dictA = Dictionary.empty().set(0n, 0n); const dictB = Dictionary.empty().set(0n, 2n); - await contract.send(treasure, { value: toNano('10') }, { $$type: 'Deploy', queryId: 0n }); + await contract.send( + treasure, + { value: toNano('10') }, + { $$type: 'Deploy', queryId: 0n } + ); await system.run(); // Tests @@ -335,5 +338,53 @@ describe('feature-math', () => { expect(await contract.getCompare28(dictA, dictB)).toBe(true); expect(await contract.getCompare28(dictB, dictA)).toBe(true); expect(await contract.getCompare28(dictA, dictA)).toBe(false); + + // Test advanced math operations + for (let num = 1n; num <= 100n; num++) { + expect(await contract.getLog2(num)).toBe( + BigInt(Math.floor(Math.log2(Number(num)))) + ); + } + + for (let num = 1n; num <= 10n; num++) { + for (let base = 2n; base <= 10; base++) { + const logarithm = BigInt( + Math.floor(Math.log2(Number(num)) / Math.log2(Number(base))) + ); + expect(await contract.getLog(num, base)).toBe(logarithm); + } + } + + expect(await contract.getLog2(0n)).toBe(-1n); + expect(await contract.getLog(0n, 2n)).toBe(0n); + + const maxint = 2n ** 256n - 1n; + + for (let num = maxint - 100n; num <= maxint; num++) { + expect(await contract.getLog2(num)).toBe(255n); + } + + for (let num = maxint - 10n; num <= maxint; num++) { + for (let base = 2; base <= 10; base++) { + expect(await contract.getLog(num, BigInt(base))).toBe( + BigInt(num.toString(base).length - 1) + ); + } + } + + for (let num = maxint / 2n - 50n; num <= maxint / 2n; num++) { + expect(await contract.getLog2(num)).toBe(254n); + } + for (let num = maxint / 2n + 1n; num <= maxint / 2n + 50n; num++) { + expect(await contract.getLog2(num)).toBe(255n); + } + + for (let num = maxint / 2n - 5n; num <= maxint / 2n + 5n; num++) { + for (let base = 2; base <= 10; base++) { + expect(await contract.getLog(num, BigInt(base))).toBe( + BigInt(num.toString(base).length - 1) + ); + } + } }); -}); \ No newline at end of file +}); diff --git a/src/test/features/math.tact b/src/test/features/math.tact index a66d497e9..661762a48 100644 --- a/src/test/features/math.tact +++ b/src/test/features/math.tact @@ -295,4 +295,16 @@ contract MathTester with Deployable { get fun isNotNull3(cell: Cell?): Bool { return cell != null; } + + // + // Advanced Math Operations + // + + get fun log2(num: Int): Int { + return log2(num); + } + + get fun log(num: Int, base: Int): Int { + return log(num, base); + } } \ No newline at end of file diff --git a/stdlib/std/math.tact b/stdlib/std/math.tact index 89457c594..5fa9ae833 100644 --- a/stdlib/std/math.tact +++ b/stdlib/std/math.tact @@ -39,4 +39,10 @@ native max(x: Int, y: Int): Int; native abs(x: Int): Int; @name(now) -native now(): Int; \ No newline at end of file +native now(): Int; + +@name(__tact_log2) +native log2(num: Int): Int; + +@name(__tact_log) +native log(num: Int, base: Int): Int; \ No newline at end of file