From 437f79ed368fbeee274237e4bb82db72a984ea2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adem=C3=ADlson=20Tonato?= Date: Mon, 27 Oct 2025 14:57:43 -0300 Subject: [PATCH] feat: add formatToBinary function for decimal to binary conversion Closes #125 --- README.md | 32 ++++++++++++++++ src/formatting/binary.ts | 57 ++++++++++++++++++++++++++++ src/formatting/index.ts | 5 ++- src/tests/formatting/binary.test.ts | 59 +++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 src/formatting/binary.ts create mode 100644 src/tests/formatting/binary.test.ts diff --git a/README.md b/README.md index baba17f..50b88ec 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,7 @@ These changes improve throughput and reduce memory pressure when working with la - [formatOrdinal](#formatordinal) - Converts a number into its ordinal string representation (e.g., 1 → "1st", 2 → "2nd"). - [formatList](#formatlist) - Formats an array of strings into a human-readable list with proper commas and "and". - [formatTemperature](#formattemperature) - Converts temperatures between Celsius, Fahrenheit, and Kelvin. +- [formatToBinary](#formattobinary) - Converts a decimal integer to a binary string with optional bit grouping. ## 📋 API Reference @@ -1484,6 +1485,37 @@ 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) | + ## 🔧 Usage Patterns ### Individual Function Imports 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 457dcfa..07ab344 100644 --- a/src/formatting/index.ts +++ b/src/formatting/index.ts @@ -9,6 +9,7 @@ export { formatFileSize } from './fileSize'; export { formatOrdinal } from './ordinal'; export { formatList } from './listToString'; export { formatTemperature } from './temperature'; +export { formatToBinary } from './binary'; import { capitalize } from './capitalize'; import { formatNumber } from './number'; @@ -21,6 +22,7 @@ import { formatFileSize } from './fileSize'; import { formatOrdinal } from './ordinal'; import { formatList } from './listToString'; import { formatTemperature } from './temperature'; +import { formatToBinary } from './binary'; export const formatting = { capitalize, @@ -33,5 +35,6 @@ export const formatting = { formatFileSize, formatOrdinal, formatList, - formatTemperature + formatTemperature, + formatToBinary }; 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/); + }); +}); + +