3

I'm working with numbers like 0.3333333333333333, 0.1111111111111111, 0.6666666666666666 and I'd like to round them to the nearest hundredth while keeping numbers like 0.14285714285714285 intact.

Sorry if this question's been asked. It's gotta be a common question, but I can't seem to come up with the right keywords.

  • you mean 0.11... to 0.1? – Amini Aug 07 '21 at 10:19
  • yes but I have to check if the digits repeat in order to figure out whether it should be rounded – citywatcher Aug 07 '21 at 10:20
  • Do you want to check how many times the specific number is repeated in a rounded number or not rounded? – Amini Aug 07 '21 at 10:23
  • the number isn't rounded (although since it's a javascript float, it does only repeat for about 16 decimal points) – citywatcher Aug 07 '21 at 10:25
  • Do you want `0.1444444444444444` or `0.14` if the input is `0.1444444444444444`? Your examples you said you want to shorten all repeat every digit after the `.`, but you alsos aid you want to truncate them to hundreths. – T.J. Crowder Aug 07 '21 at 10:33
  • @citywatcher is this what you are looking for? https://stackoverflow.com/questions/11832914/how-to-round-to-at-most-2-decimal-places-if-necessary – Chanckjh Aug 07 '21 at 10:37
  • You can use the simplest way. **Math.round()**. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round – Amini Aug 07 '21 at 10:37
  • @AmirrezaAmini - How does that help with the detection of the ones they want to round? It helps with the rounding (though it gets tricky thanks to imprecision). – T.J. Crowder Aug 07 '21 at 10:41
  • @T.J.Crowder I showed the simplest way to round the number. – Amini Aug 07 '21 at 10:43

4 Answers4

2

There may be a mathematical way to detect those, but you can detect those on the string version of the number using a regular expression like this: /^0\.(\d)\1+$/. That says

  • ^0. Must start with 0.
  • (\d) Followed by a digit (we capture this digit)
  • \1+ Followed by the same digit (\1 refers back to the capture group) one or more times (+

Then grab just the first four characters of the string if you want to truncate (0.6666666666666666 becomes 0.66) or the first five and convert back to number and round (0.6666666666666666 becomes 0.67).

Live Example:

const numbers = [
    0.3333333333333333,
    0.1111111111111111,
    0.6666666666666666,
    0.14285714285714285,
    0.1444444444444444,
];

for (const number of numbers) {
    const str = String(number);
    if (/^0\.(\d)\1+$/.test(str)) {
        const truncated = Number(str.substring(0, 4));
        const rounded = Math.round(Number(str.substring(0, 5) * 100)) / 100;
        console.log(`Truncated: ${truncated}, rounded: ${rounded}`);
    } else {
        console.log(`Unchanged; ${number}`);
    }
}

(Convert back to number if you need the number value.)

In modern environments, you can make that expression a bit clearer by using a named capture group rather than the traditional anonymous version above: /^0\.(?<digit>\d)\k<digit>+$/ In that, (?<digit>\d) is the capture group named "digit" and \d<digit> is a backreference to that capture group.

Live Example:

const numbers = [
    0.3333333333333333,
    0.1111111111111111,
    0.6666666666666666,
    0.14285714285714285,
    0.1444444444444444
];

for (const number of numbers) {
    const str = String(number);
    if (/^0\.(?<digit>\d)\k<digit>+$/.test(str)) {
        const truncated = Number(str.substring(0, 4));
        const rounded = Math.round(Number(str.substring(0, 5) * 100)) / 100;
        console.log(`Truncated: ${truncated}, rounded: ${rounded}`);
    } else {
        console.log(str);
    }
}

There's question what you want done with 0.1444444444444444 (do you want 0.1444444444444444 or 0.14)? All your examples repeat starting at the ., but you're rounding to hundreths. Now, those are two separate things, but people are interpreting your question to say that 0.1444444444444444 should become 0.14. If so, it's a trivial change to allow any digit in the tens place: Just add \d after . in the expression: /^0\.\d(\d)\1+$/

Live Example:

const numbers = [
    0.3333333333333333,
    0.1111111111111111,
    0.6666666666666666,
    0.14285714285714285,
    0.1444444444444444,
    0.1555555555555555,
];

for (const number of numbers) {
    const str = String(number);
    if (/^0\.\d(\d)\1+$/.test(str)) {
        const truncated = Number(str.substring(0, 4));
        const rounded = Math.round(Number(str.substring(0, 5) * 100)) / 100;
        console.log(`Truncated: ${truncated}, rounded: ${rounded}`);
    } else {
        console.log(str);
    }
}
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • thanks, I was hoping there was a way of doing it by using math but for now I'll use a method like this. also, I didn't think to only check after the hundredth place -- that's a good idea. – citywatcher Aug 07 '21 at 10:57
  • @citywatcher - Good to know I interpreted the question correctly, but also good that the different way Keshav interpreted it was useful in the end. I'm sure there's a math-orientated way to do it (by repeatedly checking against `Math.pow(10, -1)`, `Math.pow(10, -2)`, but I started down that path and got more and more nervous about the inherent imprecision of JavaScript's numbers (IEEE-754 double-precision floating point). So I stuck with a string-based solution instead. – T.J. Crowder Aug 07 '21 at 11:05
  • 2
    Just passing through, noticing there's been a lot of energy expended here. Did a comment get deleted about rounding vs truncating? Doesn't `0.666666...` round to `0.67`? – danh Aug 07 '21 at 18:08
  • @danh - That's a **really** good point, the OP does say "round". I don't know where I got truncate. I've updated to handle it, and to convert back to number. Thank you for pointing that out! – T.J. Crowder Aug 07 '21 at 18:25
1

Since your question is not so clear, I took it as follows:

If there is a repeating chunk of digits, keep the pattern twice and truncate the rest. If there is not repeating digits, rounding to hundreths.

For example, the following is what you get by running the proposed code. The suffix ... is to indicate the repeated digits.

0.14285714285714285 => 0.142857142857...     (repeating pattern = 142857)
0.1444444444444444  => 0.144...              (repeating pattern = 4)
0.3333333333333333  => 0.33...               (repeating pattern = 3)
0.1428824114288241  => 0.1428824114288241... (repeating pattern = 14288241)
0.1288241128824112  => 0.12882411288241...   (repeating pattern = 1288241)
0.12128824112882411 => 0.1212882411288241... (repeating pattern = 1288241)
0.1231231231231231  => 0.123123...           (repeating pattern = 123) 
0.101010101010101   => 0.1010...             (repeating pattern = 10)
0.12300123123123123 => 0.12300123123...      (repeating pattern = 123) 
0.4254250042542542  => 0.42542500425425...   (repeating pattern = 425)
0.1232435213443346  => 0.12                  (repeating pattern = None)

I had to create the test case to make sure the code works for various patterns. The nums array contains the input and the expected answer.

You can use the code as

const {result, pattern} = testRepeatingDigits (0.1444444)

If you want to round the answer, you can modify the code where it returns the number string with ....

If you give me your requirement I can always edit and improve the answer.

Here is the complete code that you can run and see.

/**
 * Returns the logest repeating substring from the beginning.
 */
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;
}

/**
 * Rotate the substring and find the left most matched point
 */
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();
  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
  };
}

// test cases
const nums = [{
    num: 0.14285714285714285, // rep: 142857, truncated: [14285]
    str: '0.142857142857...'
  },
  {
    num: 0.1444444444444444, // rep: 4, truncated: [4444444444444]
    str: '0.144...'
  },
  {
    num: 0.3333333333333333, // rep: 3, truncated: [33333333333333]
    str: '0.33...'
  },
  {
    num: 0.1428824114288241, // rep: 14288241, truncated: []
    str: '0.1428824114288241...'
  },
  {
    num: 0.1288241128824112, // 1288241, [12]
    str: '0.12882411288241...'
  },
  {
    num: 0.12128824112882411, // 1288241, [1]
    str: '0.1212882411288241...'
  },
  {
    num: 0.1231231231231231, // 123, [1]
    str: '0.123123...'
  },
  {
    num: 0.1010101010101010, // 10, [101010101010]
    str: '0.1010...'
  },
  {
    num: 0.12300123123123123, // 123, [123123]
    str: '0.12300123123...'
  },
  {
    num: 0.4254250042542542, // 425, [42]
    str: '0.42542500425425...'
  },
  {
    num: 0.1232435213443346, // no repeat
    str: '0.12'
  },
];

nums.forEach(({ num, str }) => {
  const { result, pattern } = testRepeatingDigits(num);
  console.log(`${num} => ${result} (repeating pattern = ${pattern}) ${result === str ? 'OK' : 'Incorrect!'} `);
});
waterloos
  • 410
  • 2
  • 7
0

Not perfectly clear but here is my take.

It feels like you would like to test the floating part of given number against a repeating pattern. So perhaps you can do like;

function truncAtRepeat(n){
  var [is,fs] = String(n).split("."),
      index   = (fs + fs).indexOf(fs,1);
  return index === fs.length ? n
                             : parseFloat(is + "." + fs.slice(0,index));
}

console.log(truncAtRepeat(1.177177177177177));
console.log(truncAtRepeat(1.17717717717717));
console.log(truncAtRepeat(3.14159265359));
Redu
  • 25,060
  • 6
  • 56
  • 76
0

use a list and iterate over each number in the string and find the repeating numbers

def find_repeating_numbers(string):
    # create a list of numbers
    numbers = [int(x) for x in string]
    # create a list of repeating numbers
    repeating_numbers = []
    # iterate through the list of numbers
    for i in range(len(numbers)):
        # if the number is already in the list of repeating numbers, continue
        if numbers[i] in repeating_numbers:
            continue
        # if the number is not in the list of repeating numbers, check if it is repeated
        else:
        # if the number is repeated, add it to the list of repeating numbers
            if numbers.count(numbers[i]) > 1:
                repeating_numbers.append(numbers[i])
    # return the list of repeating numbers
    return repeating_numbers

data=[0.14285714285714285,0.1444444444444444,0.3333333333333333  
,0.1428824114288241,0.1288241128824112,0.12128824112882411,0.1231231231231231  
,0.101010101010101,0.12300123123123123,0.4254250042542542,0.1232435213443346  
]

# print the list of repeating numbers
#print(find_repeating_numbers('14285714285714285'))
for item in data:
    item=re.sub('0.','',str(item))
    result=find_repeating_numbers(item)
    repeating_number=''.join([str(n) for n in result])
    print(item,repeating_number)

output:

14285714285714285 142857
1444444444444444 4
3333333333333333 3
1428824114288241 1428
1288241128824112 1284
12128824112882411 1284
1231231231231231 123
1 
123123123123123 123
42542542542542 425
1232435213443346 1234
Golden Lion
  • 3,840
  • 2
  • 26
  • 35