Math.ceil/floor Vs parseInt Vs plus/minus

R

RobG

I am writing a script to move an absolutely positioned element on a
page by a factor using style.top & style.left.

The amount to move by is always some fraction, so I was tossing up
between Math.ceil/floor and parseInt +/- 1 to ensure the amount to
move was at least 1 and in the right direction. I made a small test to
see which one is faster and also included simply adding/subtracting 1.

parseInt generally took 50% to 100% longer than Math.ceil/floor, but
adding/subtracting 1 is 30 to 100 times faster (depending on the
browser) than Math.ceil/floor. Naturally I'd like to use that since
it's so much quicker.

So my question is:

Is it safe to set the 'top' or 'left' of an element to some
fractional measurement of pixels?

e.g. is it safe to do:

x = 1.9863098
el.style.left = x + 'px'

or should I be making x an integer first (using parseInt or
Math.ceil/floor)? Here's my test script:


<form action="">
<input type="text" value="1000" name="num">Number of iterations<br>
<textarea name="outCell" rows="10" cols="30">
Click Run it to run it.
</textarea>
<input type="button" value="Run it" onclick="

// x will sometimes be +ve, sometimes -ve
var x = (Math.random()-0.5)*10;
var i, f, s, t0, t1;

i = this.form.num.value;
s = new Date();
while ( i-- ) {
( x>0 )? parseInt(x)+1 : parseInt(x)-1;
x += x;
}
f = new Date();
t0 = f-s;

i = this.form.num.value;
s = new Date();
while ( i-- ) {
( x>0 )? Math.ceil(x) : Math.floor(x);
x += x;
}
f = new Date();
t1 = f-s

i = this.form.num.value;
s = new Date();
while ( i-- ) {
( x>0 )? x+1 : x-1;
x += x;
}
f = new Date();

this.form.outCell.value =
'parseInt: ' + t0 + '\n'
+ 'ceil/floor: ' + t1 + '\n'
+ 'plus/minus: ' + (f-s)
;
">
</form>
 
R

Richard Cornford

RobG said:
I am writing a script to move an absolutely positioned element
on a page by a factor using style.top & style.left.

The amount to move by is always some fraction, so I was tossing
up between Math.ceil/floor and parseInt +/- 1 to ensure the
amount to move was at least 1 and in the right direction. I
made a small test to see which one is faster and also included
simply adding/subtracting 1.

parseInt generally took 50% to 100% longer than Math.ceil/floor,

It would as parseInt type-converts non-string arguments into strings
prior to parsing them.
but adding/subtracting 1 is 30 to 100 times faster (depending
on the browser)

Irrelevant if you have to do that anyway.
than Math.ceil/floor. Naturally I'd like to use that since
it's so much quicker.

Subtraction is no substitute for rounding.
So my question is:

Is it safe to set the 'top' or 'left' of an element to some
fractional measurement of pixels?

e.g. is it safe to do:

x = 1.9863098
el.style.left = x + 'px'

No. Many browsers will happily make pixel approximations of non-integer
values (they need to in order to cope with em, %, etc. units), but some
go belly up. It is much safer only to ask for pixel positions in
integers.
or should I be making x an integer first (using
parseInt or Math.ceil/floor)?
<snip>

No. If you want to turn a number with a fractional part into an integer
suitable for use in positioning in the quickest way possible use:-

x | 0

- or:-

x >> 0

- as the bitwise operations truncate their operands to 32 bit signed
integer values (except for - >>> -, which uses ToUint32 internally
instead of ToInt32). 32 bit signed integers are sufficiently big to cove
all realistic pixel positioning and dimension requirements.

Generally, if you want fast math for pixel positioning work try to use
bitwise operators, particularly at the end of sequences of calculations.
For example, halving a dimension can be done as - ( x >> 1 ) - and be
guaranteed to return an integer result (so will not then need to be
floored), so:-

((totalWidth - elementWidth) >> 1)

- gives you a guaranteed integer result that is no bigger than half of
the width not taken up by elementWidth, ideal for centring an element in
a space, and without the need for further rounding.

i = this.form.num.value;
s = new Date();
while ( i-- ) {
( x>0 )? parseInt(x)+1 : parseInt(x)-1;
<snip>

It is difficult for me to tell what you are after from this. My
inclination would be to handle distance and direction independently.
Acquiring a +1/-1 value for direction and a separate positive value for
distance. That allows the distance to be rounded in a known directions
(ie, truncated with bitwise operators, floored or ceiled) and then
direction applied by multiplication with +1/-1 (preserving the integer
nature of the result).

Many expressions can be used to give +1/-1 results based on
Math.random(), E.G.:-

Math.random()<0.5 ? +1 : -1 // fastest on average, by a
// small margin

(((Math.random()<0.5)&& +1)||-1)

((Math.random()+0.5)<<1)-1

((Math.random()<0.5)<<1)-1

So given a positive distance - d - that starts truncated/floored to an
integer, but must be made at least 1, and a direction - v -, that will
be either -1 or +1, you might have:-

(d + !d) * v

- where if - d - is non-zero - !d - is false, adding zero after
type-conversion to a number, and when - d - is zero - !d is true, adding
1 following type-conversion. Then the dimensions - d -,
truncated/floored and adjusted by one,if needed, only in one direction,
is multiplied by +1 or -1 to give an integer distance in one of two
opposite directions.

Richard.
 
R

RobG

Richard said:
RobG wrote:
[...]

Irrelevant if you have to do that anyway.

The strategy is to emulate ceil/floor, i.e. ensure that +ve numbers are
always 'rounded' up and -ve always 'rounded' down. This can be done by
adding 1 to +ve and subtracting 1 from -ve and truncating. The trick is
to do the logic for determining +ve/-ve, add/subtract and truncate with
the quickest algorithm.

I want numbers like 0.1 to become 1 (add 1 and truncate or use ceil) and
-0.1 to become -1 (subtract 1 and truncate or use floor).

It seems simply adding/subtracting and letting the browser truncate when
setting style.left/top is not reliable - small 'jitters' occur randomly
near the end of the movement, different browsers have different
behaviours. I was pretty sure I'd be told to pass integers.
Subtraction is no substitute for rounding.

In the context described above, it kinda works, but not reliably, so I
guess you're right.

[...]
No. Many browsers will happily make pixel approximations of non-integer
values (they need to in order to cope with em, %, etc. units), but some
go belly up. It is much safer only to ask for pixel positions in
integers.

Agreed, and the results are inconsistent across browsers so it's a poor
strategy - unless unpredictable jitters with imprecise movement are
intended. :)
<snip>

No. If you want to turn a number with a fractional part into an integer
suitable for use in positioning in the quickest way possible use:-

x | 0

- or:-

x >> 0

- as the bitwise operations truncate their operands to 32 bit signed
integer values (except for - >>> -, which uses ToUint32 internally
instead of ToInt32). 32 bit signed integers are sufficiently big to cove
all realistic pixel positioning and dimension requirements.

Here's my final version. moveBy is generated by some function of the
distance between two elements:

if ( moveBy ) {
setPos( obj, ((moveBy > 0)? ( moveBy+=1 | 0 ) : ( moveBy-=1 | 0 ) ));
}

It's neatly symmetrical, fast and +ve/-ve movement and truncating are
dealt with in one go.

Which is at least as fast as simply adding/subtracting one and is faster
by a factor of about 15 than the equivalent:

setPos( obj, ((moveBy > 0)? Math.ceil(moveBy) : Math.floor(moveBy)));
Generally, if you want fast math for pixel positioning work try to use
bitwise operators, particularly at the end of sequences of calculations.
For example, halving a dimension can be done as - ( x >> 1 ) - and be
guaranteed to return an integer result (so will not then need to be
floored), so:-

((totalWidth - elementWidth) >> 1)

- gives you a guaranteed integer result that is no bigger than half of
the width not taken up by elementWidth, ideal for centring an element in
a space, and without the need for further rounding.

Yes, but I need to account for direction (+ve -ve) and would like to
have that done within a single step as above.
<snip>

It is difficult for me to tell what you are after from this. My

It's just a test of part of my logic, on its own it's pretty
meaningless. ;-p

[...]
Many expressions can be used to give +1/-1 results based on
Math.random(), E.G.:-

Math.random()<0.5 ? +1 : -1 // fastest on average, by a
// small margin

(((Math.random()<0.5)&& +1)||-1)

((Math.random()+0.5)<<1)-1

((Math.random()<0.5)<<1)-1

So given a positive distance - d - that starts truncated/floored to an
integer, but must be made at least 1, and a direction - v -, that will
be either -1 or +1, you might have:-

(d + !d) * v

- where if - d - is non-zero - !d - is false, adding zero after
type-conversion to a number, and when - d - is zero - !d is true, adding
1 following type-conversion. Then the dimensions - d -,
truncated/floored and adjusted by one,if needed, only in one direction,
is multiplied by +1 or -1 to give an integer distance in one of two
opposite directions.

Actually I'm trying to avoid random movement (!) but thanks for the tip,
I'm sure it will come in handy.
 
R

Richard Cornford

RobG wrote:
Here's my final version. moveBy is generated by some
function of the distance between two elements:

if ( moveBy ) {
setPos( obj, ((moveBy > 0)? ( moveBy+=1 | 0 ) : ( moveBy-=1 | 0 )
)); }
<snip>

In looking at this I observe that the value that is determined by the
outcome of the conditional expression is the +/-1, so maybe;-

setPos( obj, ((moveBy += ((moveBy > 0)?1:-1)) | 0) )

Richard.
 
R

RobG

Richard said:
RobG wrote:


<snip>

In looking at this I observe that the value that is determined by the
outcome of the conditional expression is the +/-1, so maybe;-

setPos( obj, ((moveBy += ((moveBy > 0)?1:-1)) | 0) )

And the outer bit-wise truncates - sweet!
 
D

Dr John Stockton

JRS: In article <[email protected]>, dated Fri, 8
Jul 2005 01:13:11, seen in RobG
And the outer bit-wise truncates - sweet!

If you are repeatedly moving in the same direction by
function(distance), then the direction should be determined, for speed,
outside the move loop. Possibly, write a function to move +, another to
move -, and at the start of each sequence assign one or other to do the
job; but function calls may be slower.

If you are using new Date() to time your moves, you probably need not
care much about the speed of the move calculation code, depending on OS;
unless the OS maintains and serves the date as a day- or seconds- count,
there's more work in new Date() than there can be in move-calculation.
And, between ticks, one can calculate a lot.

Timing with setTimeout or setInterval would not have those overheads,
but different ones.
 
R

RobG

Dr said:
JRS: In article <[email protected]>, dated Fri, 8
Jul 2005 01:13:11, seen in RobG



If you are repeatedly moving in the same direction by
function(distance), then the direction should be determined, for speed,
outside the move loop. Possibly, write a function to move +, another to
move -, and at the start of each sequence assign one or other to do the
job; but function calls may be slower.

Thanks, I'll consider that.

The destination can move before it is reached, therefore position and
distance are calculated each time after a move. I am working on
creating objects that learn to move of their own accord - accelerate,
move with a set speed, then decelerate. But they also need to be
updated with where the destination has moved to as they go.

My biggest speed issue is eliminating the use of Math functions. They
are handy to use to get the logic right, but then optimisation is
required - using a function that is 30 times faster frees-up quite a
bit of CPU!
If you are using new Date() to time your moves, you probably need not
care much about the speed of the move calculation code, depending on OS;
unless the OS maintains and serves the date as a day- or seconds- count,
there's more work in new Date() than there can be in move-calculation.
And, between ticks, one can calculate a lot.

I was using a date object to time sections of code - usually 10,000 or
so loops with multiple runs are required to get meaningful results.
I've also found that when testing a series loops in a single function,
going first is a distinct disadvantage.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top