josdejong / mathjs

An extensive math library for JavaScript and Node.js
https://mathjs.org
Apache License 2.0
14.4k stars 1.24k forks source link

how to identify periods in decimals of bignumbers #3176

Open nycos62 opened 7 months ago

nycos62 commented 7 months ago

Hello, maybe a stupid question, but is there a natural way to get periodicity in BigNumbers decimals

math.config({
  number: 'BigNumber',      // Default type of number:
                            // 'number' (default), 'BigNumber', or 'Fraction'
  precision: 20             // Number of significant digits for BigNumbers
}) 
math.divide(math.bignumber(1),math.bignumber(6)).toFixed();
'0.16666666666666666667'

as you can see it ends with digit '7'

I wish to identify here a period of length 1 starting at decimal 2 : '0.1 ͞6'

thx

josdejong commented 7 months ago

You can use fractions for that, like:"

math.fraction(1, 6).toString() // "0.1(6)"
math.fraction(1, 7).toString() // "0.(142857)"

EDIT: Fractions cannot work with BigNumber though, you'll have to calculate with fractions from the start.

nycos62 commented 7 months ago

thank you very much !! another stupid question... sorry... if I start from a string formula, which tools are available to get decimal period ? is there really only by fraction that I could achieve this ?

math.config({
  number: 'Fraction'
})
math.simplify('cos(pi/n)'.replace(/n/g, 3),{},{exactFraction:true}).toString()
'cos(pi / 3)'
math.simplify('1/n'.replace(/n/g, 3),{},{exactFraction:true}).toString()
'1 / 3'

I wish I could from whatever str formula find the decimal period

Yaffle commented 7 months ago

@nycos62 , if you know limits on the denominator and numerator , you can use method from https://stackoverflow.com/questions/14002113/how-to-simplify-a-decimal-into-the-smallest-possible-fraction/14011299#14011299 to convert decimal to fraction, then use fraction to string as @josdejong says

p.s. perhaps, can you just work with fractions all the time?

josdejong commented 7 months ago

You can use math.fraction(value).toString().

You should be aware though that the representation may not be exact, for example you may have a value like 0.3333333333, then the question is is this the exact number, or is it a rounded representation of 1/3 and should it have an infinite number or digits? So, be careful with the precision you need to work with.

nycos62 commented 7 months ago

I might have found some sort of middle solution...

math.config({
  number: 'BigNumber',      // Default type of number:
                            // 'number' (default), 'BigNumber', or 'Fraction'
  precision: 509             // Number of significant digits for BigNumbers
}) 

const a = math.bignumber("205.16666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667");
const b = math.bignumber("185.85714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714285714286");
const c = math.bignumber("19.129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258064516129032258065");
const d = math.bignumber("26.708333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333");
const e = math.bignumber("53.071428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571");

function findLongestSubstring (str) {
  let candidate = "";
  for (let i = 1; i <= str.length - i; i++) {
    if (str.indexOf(str.substring(0, i), i) === i)
      candidate = str.substring(0, i);
  }
  return candidate;
}

function rotateAndMoveLeft (str, substr, fromIndex) {
  const rotate = (str) => `${str[str.length-1]}${str.slice(0, str.length-1)}`;

  const lastIndex = substr.length - 1;
  let rotatedStr = substr;
  let pos;
  // console.log(`str=${str}, substr=${substr}, fromIndex=${fromIndex}`);
  for (pos = fromIndex - 1; pos >= 0; pos--) {
    if (rotatedStr[lastIndex] === str[pos]) {
      rotatedStr = rotate(rotatedStr);
    } else {
      pos++;
      break;
    }
  }
  const from = pos !== -1 ? pos : 0;
  return {
    subStr: rotatedStr,
    from,
    numMoved: fromIndex - from
  };
}

function shrinkPattern (pattern) {
  const _shrink = (head, tail) => {
    if (tail.length === 0)
      return head;
    return tail.split(head).every(item => item.length === 0) ?
      head : _shrink(`${head}${tail[0]}`, tail.slice(1));
  }
  return _shrink(pattern[0], pattern.slice(1));
}

function testRepeatingDigits (num) {

  const str = num.toString().substring(0,num.toString().length-3);
  const idx = str.indexOf('.');
  if (idx < 0)
    return false;
  const digitStr = str.substring(idx + 1);
  const [...digits] = digitStr;

  // the first guess of repeating pattern from the right-most digit
  const init = [...findLongestSubstring(digits.slice(0).reverse().join(''))].reverse().join('');

  // no repeating patterns found
  if (init.length === 0)
    return {
      result: (Math.round(num * 100) / 100).toString(),
      pattern: "None"
    };

  // rotate the first guessed pattern to the left to find the beginning of the repeats
  const searchFrom = digitStr.length - (init.length * 2);
  const { subStr, from, numMoved } = searchFrom > 0 ?
    rotateAndMoveLeft(digitStr, init, searchFrom) : { subStr: init, from: 0, numMoved: 0 };

  // shrink the pattern to minimum
  const pattern = shrinkPattern(subStr);

  // truncate the digits overflows the two repeatings of the pattern
  return {
    result: `${str.substring(0, idx+1)}${digitStr.substring(0, from + pattern.length * 2)}...`,
    pattern
  };
}

function toStringVinculum(n) {
  let test = testRepeatingDigits(n);
  let overlineChar = '\u0305';//not compatible on android :/ , so usage of span
  let nStr = n.toString();
  if (test == false || test.pattern == 'None')
    return n.toString();
  else
  {
    return test.result.substring(0,test.result.length-(3+(2*test.pattern.length))) + '<span style="text-decoration:overline">' +test.pattern.split('').join('</span><span style="text-decoration:overline">') + '</span>';
  }  
}

console.log(toStringVinculum(a));
console.log(toStringVinculum(b));
console.log(toStringVinculum(c));
console.log(toStringVinculum(d));
console.log(toStringVinculum(e));

'205.1‾6'
'185.‾857142'
'19.‾129032258064516'
'26.708‾3'
'53.0‾714285'