Reputation: 11384
I need to determine if a floating point number is a multiple of another floating point number using JavaScript.
I've read some other questions about floating point numbers and I've learned that they don't work correctly with the modulo operator (%). I've also read that you can convert the floating point numbers to integers by multiplying by 10/100/1000, etc, but this doesn't work correctly in all cases.
Example:
var A = 25.13;
var B = .0001;
var Value = A*1e5;
var Step = B*1e5;
// Is Value a multiple of Step?
if(0 === (Value % Step)) {
// Do something
}
In this case, Value is a multiple of Step and it works correctly. But what about:
var A = 2.2;
var B = .0001;
It should be a valid multiple, but instead we get:
220000.00000000003 % 10 = 2.9103830456733704e-11
There's an erroneous 3 in the 11th decimal position. I thought I could correct the problem with rounding with toFixed()
, by doing:
var Value = (A*1e5).toFixed(10);
var Step = (B*1e5).toFixed(10);
But if you do:
var A = 45436212356482;
var B = .0001;
You get:
4543621235648200192.0000000000 % 10.0000000000=2
It's a valid multiple, but it thinks it isn't.
With:
var A = 45436212546522156.45621565421;
var B = .0001;
This is not a valid multiple, but it thinks it is:
4.543621254652216e+21 % 10.0000000000=0
Is there a clever trick for determining if one floating point is a multiple of another? Or is this impossible?
UPDATE:
The goal is to restrict a number entered by a user (either integer or decimal) to certain increments.
From a logic perspective, a given value is or is not a valid multiple of a given increment.
Upvotes: 4
Views: 5725
Reputation: 1297
Here is one way you could do it:
function floatingPointAMultipleOfB(a, b) {
const precision_a = getNumbersAfterDecimal(a)
const precision_b = getNumbersAfterDecimal(b)
if (precision_a > precision_b) return false;
const int_a = Math.round(multBy10(a, precision_b))
const int_b = Math.round(multBy10(b, precision_b))
return int_a % int_b === 0
}
function getNumbersAfterDecimal(n) {
const exponential = n.toString().split('e');
if (exponential.length === 2) n = n.toFixed(Math.abs(exponential[1]))
return (n.toString().split('.')[1] || []).length;
}
function multBy10(val, n) {
if (n === 0) return val
return multBy10(val, n-1) * 10
}
Upvotes: 0
Reputation: 1
Below will work for all decimals.
var result = Math.round( Math.round(number1 * 100000) % Math.round(number2 * 100000) ) / 100000;
Upvotes: 0
Reputation: 183
Correct me if I'm wrong but solutions such as:
function IsMultipleOf(a, b) { var result = a % b; return (result < 1e-3); }
only answer for half of the problem. Suppose the following (using python code):
dt = 2.4
>>> dt = 2.2999999999999998
t = 13 * dt
>>> t = 29.899999999999999
t % dt
>>> 8.8817841970012523e-16
In such case it would work fine. Now suppose that:
dt = 1.4
>>> dt = 1.3999999999999999
t = 3 * dt
>>> t = 4.1999999999999993
t % dt
>>> 1.3999999999999995
As the round-off error is acting such that the t is lower than the next multiple of dt, the value of the modulo is closer to dt rather than 0.
One possible way to solve this is to check both scenarios:
modt = t % dt
(abs(modt) <= tolerance) or (abs(dt - modt) <= tolerance)
where I would expect tolerance = machine epsilon * abs(dt) / 2
,
based on this answer,
but the modulo operation somehow introduces more error than that.
It looks like:
tolerance = machine epsilon * max(abs(t), abs(dt)) / 2
handles the job, but that's only a guess. Alternatively,
tolerance = 2 * machine epsilon * abs(dt)
seems to work fine also.
Upvotes: 0
Reputation: 6605
The basic problem is that 32-bit floating point numbers cannot correctly represent all real numbers. For example, if the user enters 0.1, the float will have a value of 0.099999994.
So if you have an increment of 0.1, you cannot tell if he entered 0.1 (which would be valid), or if he entered 0.09999.. (which would be invalid).
My suggestion would be to use an integral datatype instead and treat it like a fixed point number. That way you don't lose precision and can easily check for multiplicity.
Upvotes: 2
Reputation: 162299
I need to determine if a floating point number is a multiple of another floating point number using JavaScript.
If we assume, that floating point numbers are used to approximate real numbers, then by definition every floating point number is a multiple of another number. Of course, since floating point numbers are actually a subset of the rational numbers, there are of course a lot of pairs of floating point numbers without a common floating point divisor. For example any two floating point numbers with different prime coefficients in the mantissa have no common divisor. The range of the exponent further limits pairs in a exact multiple.
But maybe you're not looking for for numbers with an arbitrary multiplication factor, but for those with a integer divisor and test if the result is below a chosen threshold (usually called epsilon).
e.g. functional style pseudocode!
fmod :: float -> float -> float
fmod a b =
b - a * floor( b / a )
EPSILON = 1e-5;
divides_to_integer :: float -> float -> boolean
divides_to_integer a b =
fmod(a, b) < EPSILON
The fmod function should be taken from a JavaScript math library.
Upvotes: -1
Reputation: 223454
Given your last two examples, with 4543621235648200192 and such, it seems you want to accept numbers that are integer multiples of .0001 and reject those that are not and you want to do this not using .0001 but using a variable that contains the floating-point value nearest .0001.
In general, that is impossible. There is no way for an algorithm to “know” that .0001 was intended when you pass it something else.
If you restrict the problem more, a solution might be possible. For example, it is possible (perhaps not easy) to answer this question: Is the floating-point value X the floating-point value nearest to an integer multiple of .0001? (In other words, is there an integer k such that multiplying .0001 by k and rounding to the nearest floating-point value produces exactly X?)
So, to get a solution, you need to describe your goal further. Do you want solutions for arbitrary values of Step or just certain values? Since binary floating-point cannot represent Step precisely enough, do you have some other way to describe it? E.g., will it always be a multiple of .0001? Is the Value you want to accept as a multiple always the binary floating-point number nearest an exact mathematical multiple, or might it have additional errors?
Upvotes: 4
Reputation: 10515
Since you're dealing with floating points, your best bet is to determine how "close" close is, and use that:
function IsMultipleOf(a, b) {
var result = a % b;
return (result < 1e-3);
}
Upvotes: -2