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

Started by
13 comments, last by Servant of the Lord 11 years, 1 month ago

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?

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

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.

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.

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.
Thanks, that answered my question.

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.

I'm working on a game! It's called "Spellbook Tactics". I'd love it if you checked it out, offered some feedback, etc. I am very excited about my progress thus far and confident about future progress as well!

http://infinityelephant.wordpress.com

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

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.

[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]
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!

This topic is closed to new replies.

Advertisement