Skip to content

Latest commit

 

History

History
440 lines (323 loc) · 11.6 KB

README.md

File metadata and controls

440 lines (323 loc) · 11.6 KB
Abacus as symbol of representing numbers in different bases

Radix

Radix is JavaScript library for radix transformations and manipulations.

Features:

  • Extendable;
  • Immutable;
  • No dependencies;
  • Simple API;
  • Types included;
  • Works in a browser and Node.js;

Motivation

JavaScript already has the utility function to parse and transform numbers between different radix. But it has the limitations:

  • radix value should be in range [ 2, 36 ];
  • JS integers has safe treshold as 2^53 - 1.

Of course, it is possible to use BigInt instead, but coercion between numbers and BigInts are not that great.

This library use ranks array to represent a number: [ 1, 2, 3 ] // same as 123 which makes it easier to work with such limitation. BigInt used for internal calculations so values above safe integer threshold won't be a problem.

Getting started

The package is available via npm:

npm i @ericrovell/radix
import { radix } from "@ericrovell/radix";

radix([ 1, 0, 1, 0], 2).decimal // -> 10

API

Constructor

radix(input = [ 0 ], radix = 10, options?)

Constructs a number from given ranks and specified radix. The input is validated, more about the validation rules in .valid property description.

In case of invalid input the fallback is number 0 in binary system.

radix().decimal                            // -> 0
radix([ 1, 0, 0 ]).decimal                 // -> 4
radix([ 1, 0, 0, 1, 1, 0, 1 ], 2).decimal  // -> 77
radix([ 5, 0 ], 2).decimal                 // -> 0, invalid input

Input options

The valid types of input are:

  • number: Any positive integer number, float values are considered invalid;
  • bigint;
  • string;
  • number[]: Homogeneous array of integer numbers;
  • string[]: Homogeneous array of string symbols;

number and bigint are considered to have the radix = 10, and this radix value is default.

number[] and string[] input decoded first (if such option is passed, more about soon), and after parsed as number ranks.

// integer
radix(132).getRanks(); // -> [ 1, 3, 2 ]

// bigint
radix(456n).getRanks(); // -> [ 4, 5, 6 ]

// string
radix("1234").getRanks(); // -> [ 1, 2, 3, 4 ]

// number[]
radix([ 5, 6, 7, 8 ]).getRanks(); // -> [ 5, 6, 7, 8 ]

// string[]
radix([ "1", "2", "3" ]).getRanks(); // -> [ 1, 2, 3 ]

Options

All constructor options are optional.

`decoding`

To define custom ranks decoding, provide a decodings object:

import type { Decodings } from "@ericrovell/radix";

const decodings: Decodings = {
  "A": 0,
  "B": 1,
};

radix([ "A", "B" ], 2, { decode: decodings }).getRanks(); // -> [ 1, 0 ]

Also, the decoder function can be provided instead:

import type { Decoder } from "@ericrovell/radix";

const decoder: Decoder = rank => rank ? "A" : rank;

radix([ "A", 1 ], 2, { decoder }).getRanks(); // -> [ 0, 1 ]
`minRanks`

Sets the minimal number of ranks.

radix([ 1 ], 2, { minRanks: 5 }).getRanks();     // -> [ 0, 0, 0, 0, 1 ]
radix([ 1 ], 2, { minRanks: -5 }).getRanks();    // -> [ 1 ]
radix([ 1, 2, 3, 4 ], 10, { minRanks: 3 }); // -> [ 1, 2, 3, 4 ]

Methods and properties

.decimal

Returns the numeric decimal representation.

radix([ 1, 0, 1, 0 ], 2).decimal // -> 10
radix([ 2, 4, 5 ], 8).decimal    // -> 165

Do not use if the decimal value may exceed the safe integer value as it returns Number instance which is not safe. Use .valueOf() instead.

.getRanks(encode?: Encode)

Returns ranks the number consists of.

radix([ 1, 0, 1 ], 2).getRanks() // -> [ 1, 0, 1 ]

The output may be encoded using the encode argument.

Encoding using the the encodings object:

import type { Encodings } from "@ericrovell/radix";

const binary = {
  0: "A",
  1: "B"
};

radix([ 1, 0, 1, 0 ], 2).toString(binary)  // -> [ "B", "A, "B", "A ]

Encoding using the the encoder function:

import type { Encoder } from "@ericrovell/radix";

const binaryEncoder: Encoder = rank => {
  return rank === 0 ? "A" : "B"
};

radix([ 1, 0, 1, 0 ], 2).toString(binaryEncoder)  // -> [ "B", "A, "B", "A ]
.getRank(index = 0)

Returns the rank value at specified index.

Index is tied to the rank's power:

$$ 1234 = 1 * 10^3 + 2 * 10^2 + 3 * 10^1 + 4 * 10^0$$

Here, the last rank value 4 has a power of 0, that's how index is calculated.

const number = radix([ 1, 2, 3, 4 ], 10)

number.rank(0); // -> 4
number.rank(3); // -> 1
.radix

Returns number's radix value.

radix([ 1, 0, 1 ], 2).radix // -> 2
.setRadix(radix)

Changes the number's radix and returns a new Radix instance.

radix([ 1, 0, 1, 0 ], 2).setRadix(10).getRanks();   // [ 1, 0 ]
radix([ 1, 0, 1, 0 ], 2).setRadix(8).getRanks();    // [ 1, 2 ]
radix([ 1, 0, 1, 0 ], 2).setRadix(2).getRanks();    // [ 1, 0, 1, 0 ]
.setRank(value = 0, rank = 0)

Changes the value of specific rank and returns the number as new Radix instance.

Note: The index is tied to the power, read more at .getRank() method.

radix([ 1, 0, 1 ], 2).setRank(0).getRanks()                       // -> [ 1, 0, 0 ]);
radix([ 1, 0, 1 ], 2).setRank(1, 1).getRanks()                    // -> [ 1, 1, 1 ]);
radix([ 4, 0, 5, 7 ], 8).setRank(7, 3).getRanks()                 // -> [ 7, 0, 5, 7 ]);
radix([ 1, 0, 1, 0, 1, 1, 1, 0, 1 ], 2).setRank(1, 5).getRanks()  // -> [ 1, 0, 1, 1, 1, 1, 1, 0, 1 ]);
.toString(encode?: Encode, sep = "")

Constructs a number's string representation.

radix([ 2, 3, 4 ], 10).toString()       // -> "234"

The custom encoding can be specified using the encodings object or encoder function, same as .getRanks() method.

import type { Encodings, Encoder } from "@ericrovell/radix";

const binary = {
  0: "A",
  1: "B"
};

const binaryEncoder: Encoder = rank => {
  return rank === 0 ? "A" : "B"
};

radix([ 1, 0, 1, 0 ], 2).toString(binary)  // -> "BABA"
radix([ 1, 0, 1, 0 ], 2).toString(binaryEncoder)  // -> "BABA"

To define a separator, provide a second argument:

radix([ 1, 0, 1, 0 ], 2).toString(undefined, "+")  // -> "1+0+1+0"
.valid

Returns the boolean indicating whether or not the input was valid.

Radix should be positive integer equal or larger than 2. Unary base system's are not supported. It complicated the code too much and too primitive to be practical.

Each rank should be non-negative integer and have a value less than radix. Valid input options are covered here.

radix([ 1, 1, 0 ], 2).valid        // -> true
radix([ 0, 1, 2, 8 ], 8).valid     // -> false, rank can't be 8 for the base 8
radix([ 1, 1, 0 ], 2).valid        // -> true
radix([ 1, 1, 0 ], 1.5).valid      // -> false, radix should be an integer
radix([ 0, 1, 2, 8 ], 0).valid     // -> false, radix should be a positive integer
radix(2.5, 10).valid               // -> false, unsupported input
radix([ 0, "1", 2, 8 ], 10).valid  // -> false, array should be homogeneous
.valueOf()

Returns the primitive value as decimal radix BigInt value.

radix([ 2, 3 ], 10).valueOf() // -> 23n

Method may be useful for coercion:

radix([ 1, 2 ], 10) + radix([ 2, 3 ], 10) // -> 35n

Other features

Coercion

The Radix instance supports coercion via toString() and valueOf() methods. The latter returns a bigint decimal representation.

radix([ 1, 2, 3 ], 10) + radix([ 7, 7 ], 10) // 200n

Extedibility

To extend functionality for your needs, extend the Radix class available at the root path:

import { Radix } from "@ericrovell/radix";

class RadixExtended extends Radix {
  constructor(ranks, radix) {
    super();
    // ...
  }

  getRanksSum() {
    return this.digits.reduce(( acc, digit ) => acc + digit, 0);
  }
}

const extended = new RadixExtended([ 1, 0, 1, 0 ], 2);
extended.getRanksSum() // -> 2

Iterability

The Radix instance can be iterated via for ... of loop to loop through the ranks in powers order:

import { radix } from "@ericrovell/radix";

for (const [ rank, power ] of radix([ 1, 2, 3 ])) {
  console.log(rank, power)
  // -> [ 3, 0 ], [ 2, 1, ], [ 1, 2 ]
}

for (const [ rank, power ] of radix([ 5, 4 ], 10).setRadix(2)) {
  console.log(rank, power)
  // -> [ 0, 0 ], [ 1, 1 ], [ 1, 2 ], [ 0, 3 ], [ 1, 4 ], [ 1, 5 ]
}

The same way the spread operator can be used, Array.from(), and all other methods and functions that operates on iterables.