Operations on Digits of a Number

Intro

Some concepts, notes, tips and examples on how to extract digits from a number. For example:

  • Count digits in a number.

  • Extract first and last digits.

  • Extract first n digits.

  • Extract last n digits.

  • Turn a number into an array of its digits.

  • What else?

Most of it apply only to integers, or whole numbers (positive integers).

Some solutions on the web involve converting the number to a string, slicing it to get the desired digits, and then converting the sliced parts back to a number. That is fine but there are also, sometimes, some more mathematical approaches to the problem (which would also be more performant).

Notes on rounding down to integer

Rounding a number down to an integer can be done with floor-like functions in many languages, or with a bitwise operation.

$ node --interactive

> var n = 794;

> while (n >= 10) n /= 10;
7.94

> n
7.94

> n ^ 0
7

We could also do n | 0 or ~~n. And of course, Math.trunc() and Math.floor().

I know PureScript uses n | 0 (saw it in the source code). @natefaubion on PureScript Discord server told me “it will wrap to an int32 range” and that “it was an old trick from asm.js.”

//
// .spago/prelude/v6.0.0/src/Data/Ring.js
//
export const intSub = function (x) {
  return function (y) {
    return x - y | 0;
  };
};

Bitwise operators in ECMAScript convert the number operands to int32, which makes the result is an integer value. That is why bitwise operations on ECMAScript numbers return numbers without the fractional part.

A bitwise operator treats their operands as a set of 32 bits (zeros and ones), rather than as decimal, hexadecimal, or octal numbers. Bitwise operators perform their operations on such binary representations, but they return standard JavaScript numerical values.

— MDN docs on bitewise operators

Count digits in a number

To count the digits in a number, log base 10 is helpful. For example, log10(100) is 2 and log10(1000) is 3. Note we get one less than the actual number of digits in each case.

Similarly, log10(99) is ≃ 1.9 and log10(199) is ≃ 2.9. If we round down to the integer part, we get 1 and 2, which is again one less than the number of digits for each number. Therefore, we can simply add 1 and we get the count of digits in a number.

$ node --interactive

> log10(100) + 1
3

> log10(1000) + 1
4

> floor(log10(99)) + 1
2

> log10(99)
1.99563519459755

> floor(log10(99)) + 1
2

We just floor the result of the log10 and add 1 to that and we are done. And we have to remember that logarithms with 0 yields Infinity, while logarithms with negative integers yield NaN, so, we can just handle the 0 case conditionally, and get the absolute value for the given number before performing the logarithm operation.

JavaScript

For this solution we use the OR (|) bitwise operation with 0 (instead of the floor function, which we could just as well) to “remove” the decimal part.

import { abs, log10 } from './index.js';

/**
 * Returns the number of digits in `num`.
 *
 * @param {number} num
 * @returns {number}
 */
export function countDigits(num) {
  if (num === 0) return 1;
  return (log10(abs(num)) + 1) | 0;
}

Here’s a Ramda REPL for countDigits in a more FPish way of doing it!

const log10 = Math.log10.bind(Math);
const abs = Math.abs.bind(Math);
const int = n => n | 0;
const add1 = n => n + 1;

const countDigits = compose(add1, int, log10, abs);

[
  countDigits(0),     //=> 1
  countDigits(-0),    //=> 1
  countDigits(7),     //=> 1
  countDigits(-7),    //=> 1
  countDigits(-42),   //=> 2
  countDigits(999),   //=> 3
  countDigits(-8765), //=> 4
  countDigits(1e4),   //=> 5
  countDigits(-1e5),  //=> 6
];

Turn number into array of its digits

Using the modulo operation to keep getting the last digit and adding it to the front of the array.

  • Let digits be an empty array.

  • While n >= 10:

    • Let m be the result of n module 10.

    • Let n be the result of flooring n divided by 10

    • Add m to the front of digits.

  • Add floored n to the front of digits.

  • Return digits.

For 793, this is how it goes:

793 % 10      -> 3
793 / 10 | 0  -> 79
digits is [3]
           ^

79 % 10       -> 9
79 / 10 | 0   -> 7
(note we need to add 9 *before* 3 in digits)
[9, 3]
 ^

Add remaining 7 in front of [9, 3]
[7, 9, 3]
 ^

At each iteration of the loop, n is relieved of its last digit, and digits gets that digit added to is beginning.

JavaScript

/**
 * Turns a number into an array of its digits.
 *
 * @category List
 * @signature number -> Number[]
 * @param {number} num
 * @returns {number[]}
 * @example
 * numToDigits(-1894);
 * // → [1, 9, 8, 4]
 */
function numToDigits(num) {
  var n = abs(num);
  var digits = [];

  while (n >= 10) {
    var last = n % 10;
    n = n / 10 | 0;

    digits.unshift(last);
  }

  digits.unshift(n | 0);

  return digits;
}

Take first digit from number

Keep dividing the number by 10 while the number is greater than 10 and then, if there are decimal places left, apply an operation to round it down to the nearest integer.

JavaScript

/**
 * Returns the first digit of a number.
 *
 * @category math
 * @sig Number -> Number
 * @param {number} num
 * @returns {number}
 */
function getFirstDigit(num) {
  if (num < 0) throw new RangeError('num must be >= 0');

  var x = num;
  while (x >= 10) x /= 10;
  return x | 0;
}

Take last digit from number

To get the last digit of an integer, simply do modulo division by 10.

JavaScript

$ node --interactive

> 1984 % 10
4
> -1984
-1984
> (1e3 + 7) % 10
7

No matter the length of the number, it always works. No loop or conversion to string with some split is necessary.

/**
 * Returns the last digit of a number.
 *
 * The number must not contain a decimal place. That is, 35.7 is an
 * invalid input for this function and will result an exception, while
 * 357 is valid, and will return 7.
 *
 * @category math
 * @signature Number -> Number
 * @param {number} num
 * @returns {number}
 */
function getLastDigit(num) {
  if (!Number.isInteger(num))
    throw new RangeError('num must be an integer');

  return num % 10;
}

Take first n digits from number

If we have 12345, and we keep dividing it by 10 and flooring the result, we keep “dropping” the last digit:

$ node --interactive

> var n = 12345;

> n / 10
1234.5

> n / 10 | 0
1234

> n / 10 / 10 | 0
123

> n / 10 / 10 / 10 | 0
12

> n / 10 / 10 / 10 / 10 | 0
1

If we want to get the first three digits, we have to “drop” the last two. Or, we have to divide by 10 two times, which is the same as dividing by (10 * 10), which is 10 to the second power.

We can do a loop, something like:

var n = 12345;
while (countDigits(n) > 3)
  n = n / 10 | 0
// → 12

Or

var n = 12345;
for (var i = 0; i < 5 - 2; ++i)
  n = n / 10 | 0;
// → 12

Then we can think of this logic: “to get the first n digits, we need to drop the last m digits.” If the number has five digits, and we want the first three, 5 - 3 is 2. We need to drop the last two digits. And we know that “dropping the last two digits” means dividing by 10 two times, or by 10 / pow(10, 2).

$ node --interactive

> n / pow(10, 5 - 1) | 0
1

> n / pow(10, 5 - 2) | 0
12

> n / pow(10, 5 - 3) | 0
123

> n / pow(10, 5 - 4) | 0
1234

> n / pow(10, 5 - 5) | 0
12345

JavaScript

/**
 * Take the first `len` digits from `num`.
 *
 * Negative numbers are treated as positive.
 *
 * If the number of digits in `num` is less than or equal to
 * `len`, simply return `num`.
 *
 * @example
 * takeDigits(1984, 4);
 * // → 1984
 *
 * takeDigits(-1984, 2);
 * // → 19
 *
 * @param {number} num
 * @param {number} len
 * @returns {number}
 */
function takeDigits(num, len) {
  if (typeof len !== "number" || len < 1)
    throw new RangeError("len must be a number >= 1");

  if (num === 0) return num;

  var n = abs(num);
  var numDigits = countDigits(n);

  if (numDigits <= len)
    return n | 0;

  return (n / pow(10, numDigits - len)) | 0;
}

Drop first n digits from number

For this we can make use of powers of 10 mixed with taking the last digit in a loop.

num = 7953

last = 7953 % 10             -> 3
num  = 7953 / 10 | 0         -> 795
out  = 3 * 10 ** 0            -> 3

last = 795 % 10              -> 5
num  = 795 / 10 | 0          -> 79
out  = 5 * 10 ** 1 + out     -> 53
  • n % 10 returns the last digit in n.

  • n / 10 | 0 returns n without the last digit.

  • x * 10 ** exp makes use of the knowledge that we use a positional numeric system. digit * 10 ** 0 for the one’s place, digit * 10 ** 1 for the ten’s place, digit * 10 ** 2 for the hundred’s place, etc. For example:

    • 7 * 10 ** 0 is 7.

    • 7 * 10 ** 1 is 70.

    • 7 * 10 ** 2 is 700.

JavaScript

/**
 * Drops the first `len` digits from `num`.
 *
 * @example
 * dropDigits(1234, 2);
 * // → 34
 *
 * dropDigits(123, 3);
 * // → 123
 *
 * dropDigits(123, 4);
 * // → 123
 *
 * @param {number} num The number to drop the first `len` digits from.
 * @param {number} len The number of digits to drop from the beginning
 *   of the number. It has to be less than the number of digits in the
 *   number.
 * @returns {number} The number with `len` digits dropped from its
 *   beginning or the unmodified number if `len` is less than the number
 *   of digits in the input number.
 */
function dropDigits(num, len) {
  var n = abs(num);

  if (countDigits(n) <= len) return n;

  var out = 0;
  var numLen = countDigits(n);

  for (var exp = 0; exp < numLen - len; ++exp) {
    var last = n % 10 * 10 ** exp;
    var n = n / 10 | 0;
    out = last + out;
  }

  return out;
}