Sign in to follow this  
Servant of the Lord

Why is this code like this? (Order of function calls in an equation)

Recommended Posts

In one of the Robert Penner ease equations (bounceEase), one of the lines (in Javascript) are:

return (7.5625*(position-=(1.5/2.75))*position + .75);

 

However, when someone else was ported them to C++, the person porting did this:

float postFix = position-=(1.5f / 2.75f);
return (7.5625f * (postFix) * position + 0.75f);

Presumably because the order that "position -= n" is called is undefined behavior? It could be resolved before or after the second use of 'position' is resolved, right?

 

But the change seems rather silly to me, because isn't the altered code identical to:

 

position -= (1.5f / 2.75f);
return (7.5625f * position * position + 0.75f);

 

Why bother having a identical copy of 'position' be called 'postFix'. Wouldn't they hold the same value?

Share this post


Link to post
Share on other sites
Nypyren    12062
Your confusion is the #1 reason why I would NEVER modify a variable in an expression that contains the same variable more than once. I would explicitly split the expression into multiple lines like the C++ version has.

I have no idea what Javascript's convention for expression evaluation order or sequence points is, but your second C++ code is the better interpretation of the function. Edited by Nypyren

Share this post


Link to post
Share on other sites

You mean, this:

position -= (1.5f / 2.75f);
return (7.5625f * position * position + 0.75f);

 

Is in-fact a proper interpretation of the original Javascript version? Am I correct in thinking I can just cut out the whole 'postfix' variable?

 

I'm fully in agreement about not modifying a variable inside of expressions. smile.png

Aside from potential undesired behavior, it also makes the code look messier, IMO.

Share this post


Link to post
Share on other sites
King Mir    2490
It looks like the creation of the postFix variable was the minimal change to make the code valid c++ (because of undefined behavior, like you mentioned). It's the stock way of breaking up an expression by replacing a part of the expression with a intermediate variable. Your alternative code is much cleaner. Edited by King Mir

Share this post


Link to post
Share on other sites
jwezorek    2663

It looks like the creation of the postFix variable was the minimal change to make the code valid c++ (because of undefined behavior, like you mentioned). It's the stock way of breaking up an expression by replacing a part of the expression with a intermediate variable. Your alternative code is much cleaner.

 
Yes, it looks to me like whoever wrote the original C++ port i.e. the one containing
float postFix = position-=(1.5f / 2.75f);
 
was just going really fast and porting without thinking too much about the source code. If you've ever ported from one language to another you've probably gotten into this mind set: let me just plow through this, get it working, and I can clean it up later. Of course, you never end up cleaning it up.
 
But yeah, your version 
position -= (1.5f / 2.75f);
return (7.5625f * position * position + 0.75f);
is equivalent (and clearer).  I think the original Javascript version was just being pointlessly clever, actually not even clever just kind of baroque. Edited by jwezorek

Share this post


Link to post
Share on other sites
Plethora    687

I think the original Javascript version was just being pointlessly clever, actually not even clever just kind of baroque.

 

May I piggyback on this thread (seeing as its been resolved) to ask a question that jumped to the fore of my mind when I read this?  :)

 

Many moons ago, I was just out of college and looking around to get a job in programming with my shiny new CS degree.  One place I applied gave me two interviews... Well long story short, they had their own proprietary language which they wrote their software with.  There was a test I had to take where it would give you some code snippet in c++ and then something equivilent in their language, then ask you to use those concepts to solve some common coding problems with their language.

 

The last task on the test was just to codeup a pretty simple sorting algorithm from scratch.  I was invited back for a second interview during which my test was reviewed.  The only issues the interviewer had were on this sorting algorithm, where the guy kept pointing out where I could have written it with fewer lines of code, as if fewer lines was the be all end all of coding.  This being an interview I mostly just nodded and kept my mouth shut even though I thought my version was just about identical to what he wanted with the exception of mine being significantly more readable and less confusing.

 

So this post reminded me very much of that experience.  Is there some historical basis for the belief that fewer lines of code are better if possible?  Is that sort of belief more common in certain areas of CS?  I remember being slightly confused but the emphasis being placed on the actual number of lines used. 

Share this post


Link to post
Share on other sites
Nypyren    12062
Lines of code doesn't matter as much as readability, but sometimes there are other ways to do things which are both shorter and more readable.
 
Here's an example.  In C#, you can open a file and read its contents:
 
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
  using (var sr = new StreamReader(fs))
  {
    return sr.ReadToEnd();
  }
}
Or, you can do it with one line of code:
return File.ReadAllText(path);
Edited by Nypyren

Share this post


Link to post
Share on other sites
Cornstalks    7030

So this post reminded me very much of that experience.  Is there some historical basis for the belief that fewer lines of code are better if possible?  Is that sort of belief more common in certain areas of CS?  I remember being slightly confused but the emphasis being placed on the actual number of lines used.

(100% speculation on my part):

I suspect this mentality has evolved from two sources.

The first (and probably biggest) is that back in the day, your screen was 80 characters wide and 24 lines tall. That's not a lot of room. These days we've got luxurious huge screens, multiple monitors, etc. Back then, the shorter and more concise your code was, the more you could fit on your screen and actually see your program's flow. These "old timers" who grew up with screens like this sometimes still have the habit of coding for 80x24 sized screens.

The second reason is that optimizers have gotten better, but back in the day, you pretty much had to optimize your own code. Shorter, sometimes cryptic code could mean using a few less cycles. These days, optimizers can kick the crap out of a lot of programmers and it's more worthwhile to focus on algorithmic optimizations than line-by-line optimizations. However, again, some people still have the mentality that their cryptic code is needed in order to squeeze out a few instructions, when that's not the common case today (if you're using a proper compiler).

These two things may have created habits in people that together can lead to some unnecessarily condensed and cryptic code.

Share this post


Link to post
Share on other sites
I personally like copious whitespace and good variable names.
 
Here's the code I got online: (Copied and pasted here, I didn't make it look worse)
float Bounce::easeOut(float t,float b , float c, float d) {
    if ((t/=d) < (1/2.75f)) {
        return c*(7.5625f*t*t) + b;
    } else if (t < (2/2.75f)) {
        float postFix = t-=(1.5f/2.75f);
        return c*(7.5625f*(postFix)*t + .75f) + b;
    } else if (t < (2.5/2.75)) {
        float postFix = t-=(2.25f/2.75f);
        return c*(7.5625f*(postFix)*t + .9375f) + b;
    } else {
        float postFix = t-=(2.625f/2.75f);
        return c*(7.5625f*(postFix)*t + .984375f) + b;
    }
}
(It's a static member function in an otherwise empty class. I don't know why they didn't just use a namespace)
 
Here's the same code, but after being added to my library:
float BounceEase(float position)
{
    //Optimization for end points, which are the most common spots.
    if(position == 0.0f || position == 1.0f)
    {
        return position;
    }
    
    if(position < (1.0f / 2.75f))
    {
        return (7.5625f * position * position);
    }
    else if(position < (2.0f / 2.75f))
    {
        position -= (1.5f / 2.75f);
        return (7.5625f * position * position + 0.75f);
    }
    else if(position < (2.5f / 2.75f))
    {
        position -= (2.25f / 2.75f);
        return (7.5625f * position * position + 0.9375f);
    }
    else
    {
        position -= (2.625f / 2.75f);
        return (7.5625f * position * position + 0.984375f);
    }
}
(There's minor differences because their version takes more parameters, but those parameters are constant in my code so I cut them out. The point is the whitespacing and the variable names)
 
@Nypyren: I like code like that. Sometimes few lines (hiding the rest of the lines in well-named functions) are a great benefit for legibility.
I have alot of C++ code that looks similar. LoadFileAsString(), LoadFileAsStringList(), LoadCSVFile(), LoadFileAsStringMap(), etc... 
 
This code:
StringList stringList = String::CommentedLinesRemoved(LoadFileAsStringList(filepath), '/');
if(stringList.empty())
    return false;

for(const auto &str : stringList)
{
    auto keyValue = StringToPair<std::string, std::string>(str, '=', ConvertFromString<std::string>, String::RemoveFlankingQuotes);
    this->SetVariable(keyValue.first, keyValue.second); //Add each key-value pair to the PathResolver's map.
}
 
Parses this file:
//--------------------------------------------------------------------------------
//Config file paths. These paths tell the game engine where to find the different types of config files.
//--------------------------------------------------------------------------------
 
%ENGINE-CONFIG%    = "%CONFIG%/Engine/"
%GAME-CONFIG%        = "%CONFIG%/Game/"
 
%USER-DEFAULTS%    = "%GAME-CONFIG%/User Defaults/"
%USER-SETTINGS%    = "%USER%/Settings/"
%DISPLAY-SETTINGS%      = "%USER-SETTINGS%/Display Settings/"
Helper functions for the win! Edited by Servant of the Lord

Share this post


Link to post
Share on other sites
Trienco    2555
Maybe they are desperately trying to save money and use tools like Coverity. Where the absurd criteria for the price is "lines of code". But then, I pretty much doubt those tools would support their proprietary language.
 
In terms of code, whenever something looks very repetitive or has a pattern, I feel the strange urge to change the code (depending on how much performance might be an issue). Things like repeating magic numbers or formulas just irk me somehow.
 
float BounceEase(float position)
{
    //Optimization for end points, which are the most common spots.
    if(position == 0.0f || position == 1.0f)
    {
        return position;
    }
 
    const float bias[] = {.0f, 1.5f/2.75f, 2.25f/2.75f, 2.625f/2.75f};
    const float offset[] = {.0f, .75f, .9375.f, .984375f};
    unsigned range = 3;
    
    if (position < (1.0f / 2.75f))
        range = 0;
   
    else if (position < (2.0f / 2.75f))
        range = 1;
  
    else if (position < (2.5f / 2.75f))
        range = 2;
 
    position -= bias[range];
    return (7.5625f * position * position + offset[range]);
}
Edited by Trienco

Share this post


Link to post
Share on other sites

I heavily use constants and variables in the code I write, but I'm still trying to understand the equation (which I didn't create) so I haven't messed with it too much. I think the '2.75f' and '7.5625f' are supposed to be variables passed in as parameters, and I think the rest are calculated based off of the '2.75f'. That's my goal for today - to figure out how it actually works, and to make it configurable through parameters like other peoples' implementations.

Share this post


Link to post
Share on other sites
jwezorek    2663
Servant, when you're done with this easing stuff you should write a journal about it and post the code. I think I could use easing functions for something I am working on. (I have a new version of my bezier curve paths code that supports bezier splines and re-parametrizing for arc length and could apply easing functions to the t-parameter for more natural looking behavior, I think)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this