Math.ceil/floor Vs parseInt Vs plus/minus

Discussion in 'Javascript' started by RobG, Jul 7, 2005.

  1. RobG

    RobG Guest

    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>


    --
    Rob
     
    RobG, Jul 7, 2005
    #1
    1. Advertising

  2. RobG wrote:
    > 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.

    <snip>
    > 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.
     
    Richard Cornford, Jul 7, 2005
    #2
    1. Advertising

  3. RobG

    RobG Guest

    Richard Cornford wrote:
    > 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.

    >>than Math.ceil/floor. Naturally I'd like to use that since
    >>it's so much quicker.

    >
    >
    > 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. :)

    >>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.


    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>
    >
    >> 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


    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.


    --
    Rob
     
    RobG, Jul 8, 2005
    #3
  4. RobG wrote:
    <snip>
    > 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.
     
    Richard Cornford, Jul 8, 2005
    #4
  5. RobG

    RobG Guest

    Richard Cornford wrote:
    > RobG wrote:
    > <snip>
    >
    >>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) )
    >


    And the outer bit-wise truncates - sweet!


    --
    Rob
     
    RobG, Jul 8, 2005
    #5
  6. JRS: In article <Hqkze.2419$>, dated Fri, 8
    Jul 2005 01:13:11, seen in news:comp.lang.javascript, RobG
    <> posted :
    >Richard Cornford wrote:
    >> RobG wrote:
    >> <snip>
    >>
    >>>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) )
    >>

    >
    >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.

    --
    © John Stockton, Surrey, UK. ?@merlyn.demon.co.uk Turnpike v4.00 IE 4 ©
    <URL:http://www.jibbering.com/faq/> JL/RC: FAQ of news:comp.lang.javascript
    <URL:http://www.merlyn.demon.co.uk/js-index.htm> jscr maths, dates, sources.
    <URL:http://www.merlyn.demon.co.uk/> TP/BP/Delphi/jscr/&c, FAQ items, links.
     
    Dr John Stockton, Jul 8, 2005
    #6
  7. RobG

    RobG Guest

    Dr John Stockton wrote:
    > JRS: In article <Hqkze.2419$>, dated Fri, 8
    > Jul 2005 01:13:11, seen in news:comp.lang.javascript, RobG
    > <> posted :
    >
    >>Richard Cornford wrote:
    >>
    >>>RobG wrote:
    >>><snip>
    >>>
    >>>>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) )
    >>>

    >>
    >>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.


    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.

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



    --
    Rob
     
    RobG, Jul 9, 2005
    #7
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. SpaceCowboy
    Replies:
    6
    Views:
    1,093
    Josef Garvi
    Aug 15, 2003
  2. Replies:
    9
    Views:
    748
    Andrey Tarasevich
    Oct 23, 2006
  3. zlotawy

    ceil and floor

    zlotawy, Sep 15, 2007, in forum: VHDL
    Replies:
    4
    Views:
    697
  4. john.swilting

    use POSIX qw(ceil floor)

    john.swilting, Apr 21, 2007, in forum: Perl Misc
    Replies:
    2
    Views:
    171
    john.swilting
    Apr 26, 2007
  5. VK
    Replies:
    15
    Views:
    1,180
    Dr J R Stockton
    May 2, 2010
Loading...

Share This Page