From 67a17e55a5fdb2e532436a9c5cfa6bbfe2f300fc Mon Sep 17 00:00:00 2001 From: Hein Date: Tue, 29 Apr 2025 16:50:13 +0200 Subject: [PATCH] docs(changeset): Added toBaseN and fromBaseN with test cases --- .changeset/eighty-chicken-know.md | 5 + src/llm/index.test.ts | 6 + src/strings/baseNumber.test.ts | 184 ++++++++++++++++++++++++++++++ src/strings/baseNumber.ts | 85 ++++++++++++++ src/strings/index.ts | 1 + 5 files changed, 281 insertions(+) create mode 100644 .changeset/eighty-chicken-know.md create mode 100644 src/strings/baseNumber.test.ts create mode 100644 src/strings/baseNumber.ts diff --git a/.changeset/eighty-chicken-know.md b/.changeset/eighty-chicken-know.md new file mode 100644 index 0000000..d44e710 --- /dev/null +++ b/.changeset/eighty-chicken-know.md @@ -0,0 +1,5 @@ +--- +"@warkypublic/artemis-kit": patch +--- + +Added toBaseN and fromBaseN with test cases diff --git a/src/llm/index.test.ts b/src/llm/index.test.ts index e69de29..cd68c06 100644 --- a/src/llm/index.test.ts +++ b/src/llm/index.test.ts @@ -0,0 +1,6 @@ +import { describe, it } from 'vitest'; + +describe('LLM Tests', () => { + it('test', () => { + }) +}) \ No newline at end of file diff --git a/src/strings/baseNumber.test.ts b/src/strings/baseNumber.test.ts new file mode 100644 index 0000000..0193a59 --- /dev/null +++ b/src/strings/baseNumber.test.ts @@ -0,0 +1,184 @@ +import { describe, it, expect } from 'vitest'; +import { fromBaseN, toBaseN } from './baseNumber'; // Update with actual path + +// This test suite targets ES6+ environments + +describe('Base-N Converters', () => { + describe('fromBaseN', () => { + // Basic conversion tests + it('should convert base 10 strings correctly', () => { + expect(fromBaseN('0', 10)).toBe(BigInt(0)); + expect(fromBaseN('123', 10)).toBe(BigInt(123)); + expect(fromBaseN('9999', 10)).toBe(BigInt(9999)); + }); + + it('should convert base 2 (binary) strings correctly', () => { + expect(fromBaseN('0', 2)).toBe(BigInt(0)); + expect(fromBaseN('1', 2)).toBe(BigInt(1)); + expect(fromBaseN('10', 2)).toBe(BigInt(2)); + expect(fromBaseN('1010', 2)).toBe(BigInt(10)); + expect(fromBaseN('11111111', 2)).toBe(BigInt(255)); + }); + + it('should convert base 16 (hex) strings correctly', () => { + expect(fromBaseN('0', 16)).toBe(BigInt(0)); + expect(fromBaseN('A', 16)).toBe(BigInt(10)); + expect(fromBaseN('F', 16)).toBe(BigInt(15)); + expect(fromBaseN('10', 16)).toBe(BigInt(16)); + expect(fromBaseN('FF', 16)).toBe(BigInt(255)); + expect(fromBaseN('ABC', 16)).toBe(BigInt(2748)); + expect(fromBaseN('DEADBEEF', 16)).toBe(BigInt(3735928559)); + }); + + it('should convert base 36 strings correctly', () => { + expect(fromBaseN('0', 36)).toBe(BigInt(0)); + expect(fromBaseN('Z', 36)).toBe(BigInt(35)); + expect(fromBaseN('10', 36)).toBe(BigInt(36)); + expect(fromBaseN('CLAUDE', 36)).toBe(BigInt(761371970)); + }); + + // Case insensitivity + it('should be case insensitive', () => { + expect(fromBaseN('abc', 16)).toBe(BigInt(2748)); + expect(fromBaseN('ABC', 16)).toBe(BigInt(2748)); + expect(fromBaseN('AbC', 16)).toBe(BigInt(2748)); + expect(fromBaseN('claude', 36)).toBe(BigInt(761371970)); + expect(fromBaseN('CLAUDE', 36)).toBe(BigInt(761371970)); + }); + + // Default base + it('should use base 36 by default', () => { + expect(fromBaseN('Z')).toBe(BigInt(35)); + expect(fromBaseN('10')).toBe(BigInt(36)); + }); + + // Edge cases + it('should handle empty string or null', () => { + expect(fromBaseN('')).toBe(BigInt(0)); + expect(fromBaseN('', 10)).toBe(BigInt(0)); + }); + + it('should handle very large numbers', () => { + expect(fromBaseN('ZZZZZZZZZZZZ', 36)).toBe(BigInt('4738381338321616895')); + }); + + // Error cases + it('should throw error for invalid base', () => { + expect(() => fromBaseN('123', 1)).toThrow('Base must be between 2 and 36'); + expect(() => fromBaseN('123', 37)).toThrow('Base must be between 2 and 36'); + }); + + it('should throw error for invalid characters', () => { + expect(() => fromBaseN('12$3', 10)).toThrow('Invalid character in input string: $'); + expect(() => fromBaseN('XYZ', 10)).toThrow('Digit Z is invalid for base 10'); + }); + + it('should throw error for digit out of range for base', () => { + expect(() => fromBaseN('129', 9)).toThrow('Digit 9 is invalid for base 9'); + expect(() => fromBaseN('12A', 10)).toThrow('Digit A is invalid for base 10'); + }); + }); + + describe('toBaseN', () => { + // Basic conversion tests + it('should convert to base 10 strings correctly', () => { + expect(toBaseN(0, 10)).toBe('0'); + expect(toBaseN(123, 10)).toBe('123'); + expect(toBaseN(9999, 10)).toBe('9999'); + }); + + it('should convert to base 2 (binary) strings correctly', () => { + expect(toBaseN(0, 2)).toBe('0'); + expect(toBaseN(1, 2)).toBe('1'); + expect(toBaseN(2, 2)).toBe('10'); + expect(toBaseN(10, 2)).toBe('1010'); + expect(toBaseN(255, 2)).toBe('11111111'); + }); + + it('should convert to base 16 (hex) strings correctly', () => { + expect(toBaseN(0, 16)).toBe('0'); + expect(toBaseN(10, 16)).toBe('A'); + expect(toBaseN(15, 16)).toBe('F'); + expect(toBaseN(16, 16)).toBe('10'); + expect(toBaseN(255, 16)).toBe('FF'); + expect(toBaseN(2748, 16)).toBe('ABC'); + expect(toBaseN(3735928559, 16)).toBe('DEADBEEF'); + }); + + it('should convert to base 36 strings correctly', () => { + expect(toBaseN(0, 36)).toBe('0'); + expect(toBaseN(35, 36)).toBe('Z'); + expect(toBaseN(36, 36)).toBe('10'); + expect(toBaseN(761371970, 36)).toBe('CLAUDE'); + }); + + // Default base + it('should use base 36 by default', () => { + expect(toBaseN(35)).toBe('Z'); + expect(toBaseN(36)).toBe('10'); + }); + + // Edge cases + it('should handle zero', () => { + expect(toBaseN(0)).toBe('0'); + expect(toBaseN(BigInt(0))).toBe('0'); + }); + + it('should handle negative numbers', () => { + expect(toBaseN(-123, 10)).toBe('-123'); + expect(toBaseN(-15, 16)).toBe('-F'); + expect(toBaseN(-35, 36)).toBe('-Z'); + }); + + it('should handle BigInt inputs', () => { + expect(toBaseN(BigInt(123), 10)).toBe('123'); + expect(toBaseN(BigInt('9007199254740991'), 16)).toBe('1FFFFFFFFFFFFF'); + expect(toBaseN(BigInt('4738381338321616895'), 36)).toBe('ZZZZZZZZZZZZ'); + }); + + // Error cases + it('should throw error for invalid base', () => { + expect(() => toBaseN(123, 1)).toThrow('Base must be between 2 and 36'); + expect(() => toBaseN(123, 37)).toThrow('Base must be between 2 and 36'); + }); + }); + + // Round-trip tests + describe('round-trip conversions', () => { + it('should preserve value when converting back and forth', () => { + const testValues = [ + 0, 1, 42, 123, 255, 1000, 9999, 65535, 16777215, 2147483647, + // Large values as BigInt + BigInt('4738381338321616895'), + BigInt('761371970') // Value for 'CLAUDE' in base 36 + ]; + + const bases = [2, 8, 10, 16, 36]; + + for (const value of testValues) { + for (const base of bases) { + // Convert to string in base-n, then back to number + const baseStr = toBaseN(value, base); + const backToNumber = fromBaseN(baseStr, base); + + // Convert BigInt to string for comparison since BigInt !== number + const originalValue = typeof value === 'bigint' ? value : BigInt(value); + expect(backToNumber).toBe(originalValue); + } + } + }); + + it('should handle random values in round-trip conversions', () => { + // Test with 10 random values + for (let i = 0; i < 10; i++) { + const randomValue = Math.floor(Math.random() * 1000000); + const randomBase = Math.floor(Math.random() * 35) + 2; // Random base between 2-36 + + const baseStr = toBaseN(randomValue, randomBase); + const backToNumber = fromBaseN(baseStr, randomBase); + + expect(backToNumber).toBe(BigInt(randomValue)); + } + }); + }); +}); \ No newline at end of file diff --git a/src/strings/baseNumber.ts b/src/strings/baseNumber.ts new file mode 100644 index 0000000..4b91d9b --- /dev/null +++ b/src/strings/baseNumber.ts @@ -0,0 +1,85 @@ +/** + * Convert a string in a given base to a decimal number + * This implementation targets ES6+ and uses BigInt for large number support + * + * @param str The string to convert + * @param base The base to convert from (2-36) + * @returns The decimal number as BigInt + */ +export function fromBaseN(str: string, base: number = 36): bigint { + // Input validation + if (base < 2 || base > 36) { + throw new Error('Base must be between 2 and 36'); + } + + // Convert empty string or null to 0 + if (!str) { + return BigInt(0); + } + + // Use standard alphanumeric characters for conversion: 0-9, A-Z + const lookupString = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + let result = BigInt(0); + let power = BigInt(1); + + // Process string right to left + for (let i = str.length - 1; i >= 0; i--) { + const char = str[i].toUpperCase(); + const digit = lookupString.indexOf(char); + + // Check if character is valid + if (digit === -1) { + throw new Error(`Invalid character in input string: ${char}`); + } + + // Validate digit is within base range + if (digit >= base) { + throw new Error(`Digit ${char} is invalid for base ${base}`); + } + + result += BigInt(digit) * power; + power *= BigInt(base); + } + + return result; +} + +/** + * Convert a decimal number to a string in a given base + * This implementation targets ES6+ and uses BigInt for large number support + * + * @param num The decimal number to convert + * @param base The base to convert to (2-36) + * @returns The string representation in the specified base + */ +export function toBaseN(num: number | bigint, base: number = 36): string { + // Input validation + if (base < 2 || base > 36) { + throw new Error('Base must be between 2 and 36'); + } + + // Handle 0 separately + if (num === 0 || num === BigInt(0)) { + return '0'; + } + + // Convert to BigInt to handle large numbers + const bigNum = typeof num === 'number' ? BigInt(num) : num; + + // Use standard alphanumeric characters for conversion: 0-9, A-Z + const lookupString = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + let result = ''; + let remaining = bigNum > BigInt(0) ? bigNum : -bigNum; // Handle negative numbers + const bigBase = BigInt(base); + + while (remaining > BigInt(0)) { + const digitValue = Number(remaining % bigBase); + const digitChar = lookupString[digitValue]; + + result = digitChar + result; + remaining = remaining / bigBase; + } + + // Add negative sign if necessary + return bigNum < BigInt(0) ? '-' + result : result; +} \ No newline at end of file diff --git a/src/strings/index.ts b/src/strings/index.ts index 8cd54ee..4536da6 100644 --- a/src/strings/index.ts +++ b/src/strings/index.ts @@ -7,3 +7,4 @@ export * from "./legacy"; export * from "./uuid"; export * from "./time"; export * from "./blankValue"; +export * from "./baseNumber";