问题
In javascipt I have av floating point "a" like this:
var a = 5.;
Now I want a new number "b" that is barely larger than "a". I could do this:
var b = a + 1.e-10;
But what if "a" is a really small number?
var a = 5.e-20;
var b = a + 1.e-10;
Now "b" is many orders of magnitude larger than "a".
Also if I make the difference between "a" and "b" too small, a large "a" could cause the difference to be rounded off.
How do I make the number "b" larger than any number "a", but closer to "a" than any other number that is larger than "a", or how do I make a number "b" that is smaller than "a" but closer to "a" than any other number smaller than "a".
Edit:
Too be more specific: I'm looking for a function "makeLarger(a)" That takes a number "a" and return a number "b" where "b>a" will always evaluate to true and "c>a && c<b" will always evaluate to false for any number "c". And also a similar function "makeSmaller(a)". I want "a" to be any number, positive, negative or zero.
回答1:
Assuming a
is positive, real and sufficiently far away from being a subnormal (in this case, greater than 1.0020841800044864e-292
), then the following should work:
var u = Number.EPSILON/2 + Number.EPSILON*Number.EPSILON;
var b = a + a*u;
Note that b = a * (1+u)
won't work. (e.g. if a = 0.9999999999999998
).
The basic idea is that the gap between floating point numbers is roughly proportional, but only increases in steps (it is the same for all numbers in the same binade). So the challenge is to choose u
small enough so that it works for the extremes in each binade.
So without loss of generality, it is sufficient to consider the numbers a
in the interval [1.0,2.0). We need to ensure that
Machine.EPSILON/2 < a*u < Machine.EPSILON*3/2
so that the final addition will round in the correct direction (instead of back to a
or 2 increments). It is fairly straightforward to show that the u
defined above satisfies these properties.
To go downwards you can do
var c = a - a*u;
P.S.: Another option, though trickier to prove, is
var v = 1 - Machine.EPSILON/2;
var b = a / v; # upwards
var c = a * v; # downwards
This has the advantage of working for a greater range (any positive, non-subnormal real number).
For subnormals, you can just add/subtract Number.MIN_VALUE
, so combining this all together you get:
function nextup(a) {
var v = 1 - Number.EPSILON/2;
if (a >= Number.MIN_VALUE / Number.EPSILON) {
// positive normal
return (a/v);
} else if (a > -Number.MIN_VALUE / Number.EPSILON) {
// subnormal or zero
return (a+Number.MIN_VALUE);
} else {
// negative normal or NaN
return (a*v);
}
}
function nextdown(a) {
var v = 1 - Number.EPSILON/2;
if (a >= Number.MIN_VALUE / Number.EPSILON) {
// positive normal
return (a*v);
} else if (a > -Number.MIN_VALUE / Number.EPSILON) {
// subnormal or zero
return (a-Number.MIN_VALUE);
} else {
// negative normal or NaN
return (a/v);
}
}
回答2:
this may be unorthodox, but you can do the following:
- cast the float to a string, you need not be concerned with the size of the number then (within reason).
- find the index of the decimal point.
- retrieve a significant group of numbers from the end (tweaking required here - something like 'up to the first couple that aren't 0 led by a 9 or whatnot')
- cast them to an int and increase by 1
- replace the numbers in the first string with those in the second into var b
- place the decimal (at the previous index), cast to float.
there are some fine tuning aspects to work out but that will accomplish it.
I dont feel like writing the code for the example, but that is trivial.
回答3:
You might use a Big Number approach -- to store your number as a sequence of its digits:
e. g. 123... many digits here ...45
as [1, 2, 3, .., 4, 5]
So you can handle reeeeeally loooooong numbers and have really great precision of your floats.
There are several js modules for this, e.g. http://jsfromhell.com/classes/bignumber
来源:https://stackoverflow.com/questions/48330463/javascript-increase-or-decrease-float-as-little-as-possible