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

This topic is 1909 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

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 on other sites
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 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.

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

Share on other sites
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 on other sites

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

Share on other sites

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 on other sites
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))
{
}
}

Or, you can do it with one line of code:
return File.ReadAllText(path);

Edited by Nypyren

Share on other sites

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

• 19
• 10
• 19
• 14
• 20