Skip to content

Instantly share code, notes, and snippets.

@jiggzson
Last active August 22, 2024 05:03
Show Gist options
  • Save jiggzson/b5f489af9ad931e3d186 to your computer and use it in GitHub Desktop.
Save jiggzson/b5f489af9ad931e3d186 to your computer and use it in GitHub Desktop.
Converts a javascript number from scientific notation to a decimal string
/**
* Removes the minus sign from the beginning of the string
*
* @param str
* @returns an array with the first item as true if a minus
* was found and the string minus the minus sign.
*/
function stripSign(str) {
// Check if it has a minus sign
let hasMinus = str.charAt(0) === '-';
// Remove it if it does
if (hasMinus || str.charAt(0) === '+') {
str = str.substring(1);
}
return [hasMinus, str];
}
/**
* Converts a string from scientific notation form to decimal form
*
* @param str
* @returns
*/
function scientificToDecimal(str) {
if (/\d+\.?\d*e[\+\-]*\d+/i.test(str)) {
let isNegative, isSmall;
// Remove the sign by slicing the string
[isNegative, str] = stripSign(str);
// Split it into coefficient and exponent
let [c, e] = str.toLowerCase().split('e');
// Split the coefficient into the whole and decimal portion
let [w, d] = c.split('.');
// Provide and empty sting for safety if in the form n(e)n
d = d || '';
// The total length of the string
let length = w.length + d.length;
// The total string minus the dot
let numString = w + d;
// If it's small then we need to calculate the leading zeros
// The shift of the decimal place to the left
const dotLocation = w.length + Number(e);
// Is the dot needed or not
const dot = dotLocation === length ? '' : '.';
let value;
if (dotLocation <= 0) {
// Join the value but pad after the dot with zeroes
value = `0${dot}${'0'.repeat(Math.abs(dotLocation))}${numString}`;
}
else if (dotLocation > length) {
value = `${numString}${'0'.repeat(Math.abs(dotLocation - length))}`;
}
else {
value = `${numString.substring(0, dotLocation)}${dot}${numString.substring(dotLocation)}`;
}
return isNegative ? '-' + value : value;
}
return str;
}
var expect = function(value) {
return {
toEqual: function(otherValue) {
if(value !== otherValue) {
console.error(value+' did not yield the correct result!');
}
}
};
};
expect(scientificToDecimal(2.5e25)).toEqual('25000000000000000000000000');
expect(scientificToDecimal(-1.123e-10)).toEqual('-0.0000000001123');
expect(scientificToDecimal(-1e-3)).toEqual('-0.001');
expect(scientificToDecimal(-1.2e-2)).toEqual('-0.012');
expect(scientificToDecimal(12.12)).toEqual(12.12);
expect(scientificToDecimal(141120000000000000)).toEqual(141120000000000000);
expect(scientificToDecimal('0')).toEqual(0);
expect(scientificToDecimal(1.23423534e-12)).toEqual(0.00000000000123423534);
@shrpne
Copy link

shrpne commented Aug 7, 2018

This gist fails on negative numbers:

scientificToDecimal(-1.123e-10)
// "0.000000000-1123"

You can try from-exponential instead

@jiggzson
Copy link
Author

jiggzson commented Aug 9, 2018

@shrpne, fixed.

@epcliff
Copy link

epcliff commented Aug 17, 2018

Hi,

Thank you for this but I modified it a bit:

    function scientificToDecimal(num) {
        const sign = Math.sign(num);
        //if the number is in scientific notation remove it
        if(/\d+\.?\d*e[\+\-]*\d+/i.test(num)) {
            const zero = '0';
            const parts = String(num).toLowerCase().split('e'); //split into coeff and exponent
            const e = parts.pop(); //store the exponential part
            let l = Math.abs(e); //get the number of zeros
            const direction = e/l; // use to determine the zeroes on the left or right
            const coeff_array = parts[0].split('.');
            
            if (direction === -1) {
                coeff_array[0] = Math.abs(coeff_array[0]);
                num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
            }
            else {
                const dec = coeff_array[1];
                if (dec) l = l - dec.length;
                num = coeff_array.join('') + new Array(l+1).join(zero);
            }
        }
        
        if (sign < 0) {
            num = -num;
        }

        return num;
    }

@jiggzson
Copy link
Author

@epcliff. Thanks

@dbocharnikov
Copy link

@epcliff, new version does not work for (-1e-3) and returns a positive number. If I have time, i'm going to try to debug and fix it.

@iva198
Copy link

iva198 commented Mar 17, 2019

It works for me using @epcliff code by changing

if (sign < 0) {
   num = -num;
}

to

if (sign < 0) {
   num = '-'+ num;
}

@afkhalid
Copy link

I have fixed the sign thing on the function. Here is the final code

module.exports.scientificToDecimal = function(number) {
  let numberHasSign = number.startsWith("-") || number.startsWith("+");
  let sign = numberHasSign ? number[0] : "";
  number = numberHasSign ? number.replace(sign, "") : number;

  //if the number is in scientific notation remove it
  if (SCIENTIFIC_NUMBER_REGEX.test(number)) {
    let zero = '0';
    let parts = String(number).toLowerCase().split('e'); //split into coeff and exponent
    let e = parts.pop();//store the exponential part
    let l = Math.abs(e); //get the number of zeros
    let sign = e / l;
    let coeff_array = parts[0].split('.');

    if (sign === -1) {
      coeff_array[0] = Math.abs(coeff_array[0]);
      number = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
    } else {
      let dec = coeff_array[1];
      if (dec) l = l - dec.length;
      number = coeff_array.join('') + new Array(l + 1).join(zero);
    }
  }

  return `${sign}${number}`;
};

Here are the tests

it("Tests scientificToDecimal function", function(done) {
      expect(CommonMath.scientificToDecimal("-1.2e2")).toBe("-120");
      expect(CommonMath.scientificToDecimal("1.2e2")).toBe("120");
      expect(CommonMath.scientificToDecimal("1.2e+2")).toBe("120");
      expect(CommonMath.scientificToDecimal("1.2e-2")).toBe("0.012");
      expect(CommonMath.scientificToDecimal("-1.2e-2")).toBe("-0.012");
      expect(CommonMath.scientificToDecimal("-1e-3")).toBe("-0.001");

      done();
    });

@elhoucine
Copy link

@afkhalid It does not support bigger numbers:
scientificToDecimal('12345.6e-1') // "0.123456" wrong!!

@PaulRBerg
Copy link

Yes, it doesn't work for large values. This number:

const foo = scientificToDecimal("3.4028236692093846346e+38");

Is incorrectly parsed as 340282366920938500000000000000000000000.

@PaulRBerg
Copy link

TypeScript version, if anyone needs it:

function parseScientific(num: string): string {
  // If the number is not in scientific notation return it as it is.
  if (!/\d+\.?\d*e[+-]*\d+/i.test(num)) {
    return num;
  }

  // Remove the sign.
  const numberSign = Math.sign(Number(num));
  num = Math.abs(Number(num)).toString();

  // Parse into coefficient and exponent.
  const [coefficient, exponent] = num.toLowerCase().split("e");
  let zeros = Math.abs(Number(exponent));
  const exponentSign = Math.sign(Number(exponent));
  const [integer, decimals] = (coefficient.indexOf(".") != -1 ? coefficient : `${coefficient}.`).split(".");

  if (exponentSign === -1) {
    zeros -= integer.length;
    num =
      zeros < 0
        ? integer.slice(0, zeros) + "." + integer.slice(zeros) + decimals
        : "0." + "0".repeat(zeros) + integer + decimals;
  } else {
    if (decimals) zeros -= decimals.length;
    num =
      zeros < 0
        ? integer + decimals.slice(0, zeros) + "." + decimals.slice(zeros)
        : integer + decimals + "0".repeat(zeros);
  }

  return numberSign < 0 ? "-" + num : num;
}

Taken from balancer-core-v2.

@elhoucine
Copy link

elhoucine commented May 20, 2021

@PaulRBerg your code excludes some valid scientific notations such as -5e5, and floating such as .5e5.

Also you may want to start by casting Number(num) then toString, it will help avoid many edge cases.

@PaulRBerg
Copy link

Thanks for the heads-ups, @elhoucine!

@livingrock7
Copy link

@PaulRBerg

Yes, it doesn't work for large values. This number:

const foo = scientificToDecimal("3.4028236692093846346e+38");

Is incorrectly parsed as 340282366920938500000000000000000000000.

it doesnt work for me if number is "115792089237316195423570985008687907853269984665640564039457584007913129639935"
it returns "115792089237316200000000000000000000000000000000000000000000000000000000000000"

did you find any solution for this?

@PaulRBerg
Copy link

PaulRBerg commented Aug 10, 2021

did you find any solution for this?

Yes, check out from-exponential. Also in case you're doing Ethereum development, check out my package evm-bn, which you can use like this:

import { toBn } from "evm-bn";

// 3141500000000000000
const foo: BigNumber = toBn("3.1415");

@zohaibtahir
Copy link

@jiggzson
Copy link
Author

jiggzson commented Aug 6, 2024

Updated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment