39

I'm building a fairly calculation-heavy cart for a fabric store and have found myself needing to do a calculation on user inputted length * the baseprice per metre, but then checking the result to see if it is a multiple of the pattern length. If it is not a multiple, I need to find the closest multiple of the pattern length and change the result to that.

I need to also be able to do exactly the same calculation in PHP, but if anyone can help me out with the maths I can port anything that needs to be translated myself.

I am using jQuery 1.6.2 and already have the first part of the calculation done, I just need to check the result of (metres*price) against the pattern length.

Any help greatly appreciated

EDIT: These calculations all involve 2 decimal places for both the price and the pattern length. User inputted length may also contain decimals.

totallyNotLizards
  • 8,489
  • 9
  • 51
  • 85

6 Answers6

45

Use the % (modulus) operator in Javascript and PHP, which returns the remainder when a is divided by b in a % b. The remainder will be zero when a is a multiple of b.

Ex.

//Javascript
var result = userLength * basePrice;     //Get result
if(result % patternLength){              //Check if there is a remainder
  var remainder = result % patternLength; //Get remainder
  if(remainder >= patternLength / 2)      //If the remainder is larger than half of patternLength, then go up to the next mulitple
    result += patternLength - remainder;
  else                                    //Else - subtract the remainder to go down
    result -= remainder;
}
result = Math.round(result * 100) / 100;  //Round to 2 decimal places
Digital Plane
  • 37,354
  • 7
  • 57
  • 59
  • 1
    facepalm :) i had though of modulus but didnt think through what i could do with the remainder. this works for me, thanks muchly. – totallyNotLizards Aug 12 '11 at 09:26
  • Hey, I don't want to write a duplicate, you mean you would write a quick `bool` for `if ( a % b ) {}` to return true if `b` is multiplier of `a` ?? EDIT: the below answer explains better for my case. – thednp Jul 13 '15 at 17:36
  • 1
    JS number precision issues make this no good for a wide variety of cases. – Adam Leggett Oct 17 '19 at 20:22
35

You can use the modulus to find the remainder after a division and then if the remainder is equal to zero then it's a multiple.

//x and y are both integers
var remainder = x % y;
if (remainder == 0){
//x is a multiple of y
} else {
//x is not a multiple of y
}

If the numbers your using could be to 2dp, the modulus should still work, if not, multiply both by 100 first then carry out the above check.

Arunprasanth K V
  • 20,733
  • 8
  • 41
  • 71
steven mcdowell
  • 573
  • 4
  • 13
  • 8
    Just remember to round the number so you don't get any floating point problems like http://stackoverflow.com/questions/3966484/floating-point-numbers-and-javascript-modulus-operator – Teodor Aug 12 '11 at 09:19
8

This avoids JavaScript precision issues (if your factor y has less than 5 decimal places).

function isMultiple(x, y) {
    return Math.round(x / y) / (1 / y) === x;
}

The above algorithm will fail for very small factors <= 1e-5. The following is a solution that will work for all values that would be <= Number.MAX_SAFE_INTEGER with the decimal point removed, i.e. most values that would have up to 16 digits and all values that would have up to 15 digits, i.e. most non-scientific values.

If you are working with scientific values, use big.js.

function decimalPlaces(x) {
    const str = ''+ +x;
    const dindex = str.indexOf('.');
    const eindex = str.indexOf('e');
    return (
        (dindex < 0 ? 0 : (eindex < 0 ? str.length : eindex) - dindex - 1) +
        (eindex < 0 ? 0 : Math.max(0, -parseInt(str.slice(eindex + 1)))));
}

function isMultiple(x, y) {
    const xplaces = decimalPlaces(x);
    const yplaces = decimalPlaces(y);
    if (xplaces > yplaces) {
        return false;
    }
    const pfactor = Math.pow(10, yplaces);
    return Math.round(pfactor * x) % Math.round(pfactor * y) === 0;
}

[
    [2.03, 0.01],
    [2.03, 0.0123],
    [2.029999999999, 0.01],
    [2.030000000001, 0.01],
    [0.03, 0.01],
    [240, 20],
    [240, 21],
    [1, 1],
    [4, 2],
    [6, 3],
    [6, 4],
    [1.123456, 0.000001]
].forEach(([number, multiple]) => {
    const result = isMultiple(number, multiple);
    console.log(`isMultiple (${number}, ${multiple}) =`, result);
});
Adam Leggett
  • 3,714
  • 30
  • 24
5

In javascript there is the remainder operator (similar to most languages with a c-like syntax).

Let x = length and y = price and z = product of x*y

var remainder = (z % x) / 100;

if (remainder === 0) {
   // z is a multiple of x
}

To get the closest x multiple to your result z you could round the result up (or down) using ceil or floor functions in the Math library.

if (r >= x / 2) {
    a = Math.ceil(z/x)*x;
}
else {
    a = Math.floor(z/x)*x;
}

Then round to two decimal places

Math.round(a / 100) * 100;
Peter Kelly
  • 14,253
  • 6
  • 54
  • 63
1

Not sure if I really understood the task as it seems quite simple to me, but have a look at this PHP code:

// --- input ---
$pattern = 12.34;
$input = 24.68;
$precision = 2; // number of decimals

// --- calculation ---

// switch to "fixed point":
$base = pow(10, $precision);
$pattern = round($pattern * $base);
$input = round($input * $base);

if ($input % $pattern) {
  // not an exact multiple
  $input = ceil($input / $pattern) * $pattern;
} else {
  // it is an exact multiple
}

// back to normal precision:
$pattern /= $base;
$input /= $base;

This can be easily translated to JavaScript.

$input will be the next closest multiple of the pattern. If you just need that and don't need to know if it actually was a multiple you could also simply do something like this:

$input = ceil($input * 100 / $pattern) * $pattern / 100;
Udo G
  • 12,572
  • 13
  • 56
  • 89
0
//roundDown to 2 decimal places function
function r2(n) {
   return Math.floor(n*100)/100; 
}

neededLength = 4.55;
price = 4.63;
patternLength = 1.6;

// price despite the length of the pattern
priceSum = r2(neededLength * price);

// calculate how many pattern lengths there must be to fit into needed length
patternLengths = Math.floor((neededLength+patternLength)/patternLength);
// calculate price for pattern lengths
priceByPatternSum = r2((patternLengths * patternLength) * price );
vellotis
  • 829
  • 1
  • 12
  • 26