Multiplying numbers when they're strings
Tags: javascript, multiplication, tutorial
Published: 2022-07-20 09:33:14

---
title: Multiplying numbers when they're strings
published: true
description: An implementation of how to multiply two base 10 numbers together when they're stored as strings, without fully converting to numbers.
tags: javascript, multiplication, tutorial
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/22t0genmt4tdk68polj9.png
# Use a ratio of 100:42 for best results.
---
We all know it: javascript's numbers are a long standing meme in the programming community.

There are work arounds, such as using a delta when comparing two floating point numbers

```javascript
if(Math.abs(a-d) < 0.0001)
{
  //these are close enough to be called the same
}
```

Or fixes, such as the [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) class:

``` javascript
let toobig1 = 9007199254740992n;
let toobig2 = 9007199254740993n;
console.log(toobig1 == toobig2) //false! unlike for regular numbers
```

So we can work with the limitations that IEEE floating point numbers impose upon us. 

But just for fun, I want to show how to multiply two arbitrary floating point numbers when they are strings. 

The method I will use is basically what I was taught in school, e.g.:

```
   123
  ×456
------
   738
+ 6150
+49200
------
 56088
```

There is a limit to this, because the ECMAScript specification has a maximum string length of 2\*\*53 - 1 (i.e. 9007199254740991) and some browsers implement an even stricter limit. Firefox, for example, limits string length to 2\*\*30 - 2 (i.e. 1073741822), but in theory this methods can be used with any two numbers with any number of digits each.

Now, I know *you* would never put in invalid input, and *I* certainly would not, but just in case some imbecile uses the function I'm defining a number as anything that matches this regular expression: `/^(-?)(\d+)(([.])(\d+))?$/` Which means that there always has to be a number before the decimal place, so this function would reject `.2`, which might annoy some people, but I'm doing for the sake of simplicity. Also, no thousands separators or similar allowed, and I'm ignoring the fact that some localities use `,` as the decimal place, and assuming everything is written left to right. I leave all those non-mathematical parts as an exercise to the reader.

All the grouping is so I can use the separate bits.

So, up top, the function looks like:

``` javascript
let am = a.match(/^(-?)(\d+)(([.])(\d+))?$/)
if(am === null)
{
  throw `Format Error: ${a} is not a valid number`
}
    
let bm = b.match(/^(-?)(\d+)(([.])(\d+))?$/)
if(bm === null)
{
  throw `Format Error: ${b} is not a valid number`
}
```
Next I need to detect if the result will be negative.
``` javascript
let aneg = a[0] === '-';
let bneg = b[0] === '-';
    
let negative = (aneg ^ bneg) === 1;
```
`^` is the XOR operator, and `true` gets treated as `1` and `false` as `0`.

I'm actually going to do integer multiplication and put the decimal place in afterwards. So the next thing I want to know is how many digits there will be after the decimal place. This is the sum of the number of digits after the decimal place in each number.

``` javascript
let adecCount = 0;
let anum = am[2];
    
if(am[5])
{
  adecCount = am[5].length
  anum += am[5];
}
   
let bdecCount = 0;
let bnum = bm[2];
    
if(bm[5])
{
  bdecCount = bm[5].length
  bnum += bm[5];
}
    
let finalDecCount = adecCount + bdecCount;
```

You can see I'm also mashing together the integer and fractional parts of each number.

Now I need to do each partial calculation, just in case I'm asked to show my working. Don't forget those carries!

``` javascript
let partresults = [];

let adigits = anum.split('').reverse().map(s => parseInt(s, 10));
let bdigits = bnum.split('').reverse().map(s => parseInt(s, 10));
    
for(let ai = 0; ai < adigits.length; ai++)
{
  let part = (Array(ai)).fill(0);
  let carry = 0
  let da = adigits[ai];
  for(let db of bdigits)
  {
    let mul = (db*da) + carry;
    carry = Math.floor(mul/10);
    mul = mul%10;
    part.unshift(mul);
  }
  if(carry > 0)
  {
    part.unshift(carry);
  }
  partresults.push(part);
}
```
The first thing I do it turn the string of digits into an array of single digit numbers. I reverse the order because I want to work from right to left.

Personally I prefer `for` loops over calling `.forEach`, but that's just habit rather than any other reason.

The calculation has an outer loop and an inner loop.

The first thing I'm doing in the outer loop (`let part = (Array(ai)).fill(0);`) is making sure that each partial calculation lines up the units, tens, hundreds, etc., correctly, with the units on the right.


Next I need to add each array in the array of arrays together, to end with one array which is the result. Sounds like a reduce operation if ever there was one.
``` javascript
let resultDigits = [];
    
if(partresults.length === 1)
{
  resultDigits = partresults[0];
}
else
{
  resultDigits = partresults.reduce((agg, arr) => 
  {
    while(agg.length < arr.length)
    {
      agg.unshift(0);
    }
    let carry = 0;
    for(let arri = arr.length-1; arri >= 0; arri--)
    {
      let agd = agg[arri];
      let ard = arr[arri];
      let value = agd + ard + carry;
      if(value > 9)
      {
        carry = Math.floor(value/10);
        value = value % 10;
      }
      else
      {
        carry = 0;
      }
      agg[arri] = value;
    }
    
    if(carry > 0)
    {
      agg.unshift(carry);
    }
    
    return agg;
  }, []);
}
```
Of course I need to deal with the simple case where there's only one nested array.

Now I need to figure out where the decimal place might go.

```javascript
if(finalDecCount > 0)
{
  resultDigits.splice(resultDigits.length - finalDecCount, 0, '.');
}
```
Ohh splice! How splicy.

And finally I add in a `-` if the result if negative, join it all together and return.
``` javascript
if(negative)
{
  resultDigits.unshift('-');
}

return resultDigits.join('');
```

You can see the full code in [this gist](https://gist.github.com/Mellen/e43508d0b8a27b9b0e1dd875cf8737ee).

Feel free to riff on it, and let me know optimisations or different approaches you'd take!