diff --git a/README.md b/README.md index d19c534..447b414 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,7 @@ These changes improve throughput and reduce memory pressure when working with la - [formatList](#formatlist) - Formats an array of strings into a human-readable list with proper commas and "and". - [formatToOctal](#formattotoctal) - Converts a decimal number to octal, optional "0o" prefix. - [formatTemperature](#formattemperature) - Converts temperatures between Celsius, Fahrenheit, and Kelvin. +- [formatToBinary](#formattobinary) - Converts a decimal integer to a binary string with optional bit grouping. - [formatToHexadecimal](#formattohexadecimal) - Converts temperatures between Celsius, Fahrenheit, and Kelvin. - [formatToDecimal](#formattodecimal) - Converts base-2/8/16 strings to decimal. @@ -1532,6 +1533,36 @@ Notes: - Kelvin values are rendered without the degree symbol (e.g., "298.15K"). - An error is thrown for invalid conversions or non-numeric input values. +#### `formatToBinary(num, options)` + +Converts a decimal integer to its binary (base-2) string representation with optional grouping from the least significant bit for readability. Supports negative numbers. + +```javascript +import { formatToBinary } from 'stringzy'; + +// Basic conversions +formatToBinary(5); // "101" +formatToBinary(10); // "1010" +formatToBinary(255); // "11111111" +formatToBinary(0); // "0" +formatToBinary(-5); // "-101" + +// Grouping from right to left (no left-padding) +formatToBinary(255, { group: 4 }); // "1111 1111" +formatToBinary(10, { group: 2 }); // "10 10" +formatToBinary(-255, { group: 4 }); // "-1111 1111" + +// Invalid cases +formatToBinary(3.14); // TypeError (must be an integer) +formatToBinary('5'); // TypeError (input must be a number) +formatToBinary(10, { group: 0 }); // TypeError (group must be positive integer) +``` + +| Parameter | Type | Default | Description | +| --------- | ------ | ------- | ------------------------------------------------ | +| num | number | required| The decimal integer to convert to binary. | +| options | object | `{}` | Optional configuration. | +| - group | number | — | Positive integer; bits per group (right-to-left) | #### formatToHexadecimal(num, options) Converts a decimal number into its hexadecimal (base-16) string representation.
Supports optional prefix "0x" and lowercase formatting.
diff --git a/src/formatting/binary.ts b/src/formatting/binary.ts new file mode 100644 index 0000000..64b8f43 --- /dev/null +++ b/src/formatting/binary.ts @@ -0,0 +1,57 @@ +/** + * Converts a decimal integer to its binary (base-2) string representation. + * + * - Supports negative numbers (prefixed with '-') + * - Optional grouping from the least significant bit for readability + * + * Examples: + * 5 → "101" + * 10 → "1010" + * 255 → "11111111" + * 0 → "0" + * + * Grouping examples (from right to left): + * formatToBinary(255, { group: 4 }) → "1111 1111" + * formatToBinary(10, { group: 2 }) → "10 10" + * + * @param {number} num - The decimal integer to convert. + * @param {{ group?: number }} [options] - Optional grouping configuration. + * @returns {string} The binary string representation. + * @throws {TypeError} If input is not a number, is NaN, or not an integer. + */ +export function formatToBinary(num: number, options?: { group?: number }): string { + if (typeof num !== 'number' || Number.isNaN(num)) { + throw new TypeError('Input must be a number'); + } + + if (!Number.isInteger(num)) { + throw new TypeError('Input must be an integer'); + } + + const isNegative = num < 0; + const absoluteValue = Math.abs(num); + + // Handle zero explicitly to avoid "-0" or empty strings + const core = absoluteValue.toString(2); + + const groupSize = options?.group; + if (groupSize !== undefined) { + if ( + typeof groupSize !== 'number' || + Number.isNaN(groupSize) || + !Number.isInteger(groupSize) || + groupSize <= 0 + ) { + throw new TypeError('Group size must be a positive integer'); + } + } + + const grouped = + groupSize && core.length > groupSize + ? core.replace(new RegExp(`\\B(?=(\\d{${groupSize}})+(?!\\d))`, 'g'), ' ') + : core; + + return isNegative ? `-${grouped}` : grouped; +} + + diff --git a/src/formatting/index.ts b/src/formatting/index.ts index 86be34e..cfc69de 100644 --- a/src/formatting/index.ts +++ b/src/formatting/index.ts @@ -10,6 +10,7 @@ export { formatOrdinal } from './ordinal'; export { formatList } from './listToString'; export { formatToOctal } from './octal'; export { formatTemperature } from './temperature'; +export { formatToBinary } from './binary'; export { formatToHexadecimal } from './hexadecimal'; export { formatToDecimal } from './decimal'; @@ -25,6 +26,7 @@ import { formatOrdinal } from './ordinal'; import { formatList } from './listToString'; import { formatToOctal } from './octal'; import { formatTemperature } from './temperature'; +import { formatToBinary } from './binary'; import { formatToHexadecimal } from './hexadecimal'; import { formatToDecimal } from './decimal'; @@ -40,7 +42,8 @@ export const formatting = { formatOrdinal, formatList, formatTemperature, + formatToBinary, formatToHexadecimal, formatToOctal, - formatToDecimal, + formatToDecimal }; diff --git a/src/tests/formatting/binary.test.ts b/src/tests/formatting/binary.test.ts new file mode 100644 index 0000000..d49255a --- /dev/null +++ b/src/tests/formatting/binary.test.ts @@ -0,0 +1,59 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import { formatToBinary } from '../../formatting/binary'; + +describe('formatToBinary', () => { + it('converts standard positive integers', () => { + assert.strictEqual(formatToBinary(5), '101'); + assert.strictEqual(formatToBinary(10), '1010'); + assert.strictEqual(formatToBinary(255), '11111111'); + assert.strictEqual(formatToBinary(1), '1'); + assert.strictEqual(formatToBinary(2), '10'); + }); + + it('handles zero', () => { + assert.strictEqual(formatToBinary(0), '0'); + }); + + it('prefixes negative numbers with a minus sign', () => { + assert.strictEqual(formatToBinary(-5), '-101'); + assert.strictEqual(formatToBinary(-10), '-1010'); + }); + + it('supports optional grouping from right to left (LSB first)', () => { + assert.strictEqual(formatToBinary(255, { group: 4 }), '1111 1111'); + assert.strictEqual(formatToBinary(10, { group: 2 }), '10 10'); + assert.strictEqual(formatToBinary(5, { group: 4 }), '101'); // no padding on the left + assert.strictEqual(formatToBinary(1023, { group: 4 }), '11 1111 1111'); + }); + + it('applies grouping with negative numbers', () => { + assert.strictEqual(formatToBinary(-255, { group: 4 }), '-1111 1111'); + }); + + it('handles large integers (MAX_SAFE_INTEGER)', () => { + const expected = Number.MAX_SAFE_INTEGER.toString(2); + assert.strictEqual(formatToBinary(Number.MAX_SAFE_INTEGER), expected); + }); + + it('throws TypeError for invalid inputs (type/NaN)', () => { + assert.throws(() => formatToBinary('5' as any), /Input must be a number/); + assert.throws(() => formatToBinary(null as any), /Input must be a number/); + assert.throws(() => formatToBinary(undefined as any), /Input must be a number/); + assert.throws(() => formatToBinary(NaN as any), /Input must be a number/); + }); + + it('throws TypeError for non-integer numbers', () => { + assert.throws(() => formatToBinary(3.14 as any), /Input must be an integer/); + assert.throws(() => formatToBinary(-2.5 as any), /Input must be an integer/); + }); + + it('throws TypeError for invalid group size', () => { + assert.throws(() => formatToBinary(10, { group: 0 }), /Group size must be a positive integer/); + assert.throws(() => formatToBinary(10, { group: -1 }), /Group size must be a positive integer/); + assert.throws(() => formatToBinary(10, { group: 2.5 as any }), /Group size must be a positive integer/); + assert.throws(() => formatToBinary(10, { group: '4' as any }), /Group size must be a positive integer/); + }); +}); + +