💾 Archived View for tilde.pink › ~irek › log › 20231220.gmi captured on 2024-05-10 at 12:22:32. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2024-02-05)

-=-=-=-=-=-=-

2023.12.20 Wed 06:28 Re: From C to C++ to Rust

My response to video "From C -> C++ -> Rust" [1] by code_report [2].

Video takes a function written in C and refactors it according to author standards, opinions and liking. They always like to pick on the old languages. I suspect deficiency of vitamin C, ... ANSI C.

It's easy to argue about simple and trivial stuff like semantics, formatting and text editors. Regardless, this time I would like to join the discussion. Continue reading at your own risk of getting triggered.

That's the original function declaration and definition.

int calculate (int bottom, int top)
{
	if (top > bottom)
	{
		int sum = 0;

		for (int number = bottom; number <= top; number++)
		{
			if (number % 2 == 0)
			{
				sum += number;
			}
		}

		return sum;
	}
	else
	{
		return 0;
	}
}

The main point of the video is about rewriting this function in different languages climbing the latter of abstractions. Almost as if this would make it better just by switching the language. This OFC can be true. For example if our goal would be to make code faster on specific hardware then rewriting code from higher level scripting language to lower level compiled system language would (without any changes to the logic) help us achieve our goal. In case of the video author achieve higher level of abstraction by switching the languages. So his goal was to have more abstract code. Usually done by programmers who like to feel better about themselves. I see no wisdom in such doings.

I think it's a mistake to make programming more appealing to programmer in cost of end user experience (case of reactive front-end frameworks). In this scenario by switching to higher level language we sacrifice memory and performance in favor of made up believes. I will stay in land of C as my goal is to improve on code size, logic simplicity and runtime performance.

Making your life easier in expense of end user is not professional.

Now, let my adjust the code structure. I will change formatting and apply early return pattern to avoid nesting the happy path. I have to handle all edge cases and errors to earn my right to return desired value.

I usually code in c89 standard. This means that I have to put all variable declarations at the top in the function body. Let's do that, and I will also rename the `number` variable to `i` as this is commonly used name for iterator variable. More than that, I will use the `sum` variable in early return.

int calculate(int bottom, int top)
{
	int i, sum = 0;
	if (top <= bottom) {
		return sum;
	}
	for (i = bottom; i <= top; i++) {
		if (i % 2 == 0) {
			sum += i;
		}
	}
	return sum;
}

Now it's easy to see that the early return is actually redundant as the for loop will be ignored if the condition from early return is true. And because we have default value for `sum` we can safely remove first if statement.

But that's not the only thing that can be removed. If you look closely you will see that the `bottom` argument is not used at all. It's only purpose is to be cloned to different variable. So I can remove the `i` variable declaration and use `bottom` variable instead, but I like to keep the `i` variable name so it's going to stay.

We removed redundant parts but we also made it less descriptive. I'm not even going to defend against the argument of long descriptive names, not this time. Instead I will add a documentation in form of comment and rename the `top` variable to something else as I'm going more abstract with this code anyway.

/* Starting from `i` add up all even numbers up to the value of `max`. */
int calc(int i, int max)
{
	int sum = 0;
	for (; i <= max; i++) {
		if (i % 2 == 0) {
			sum += i;
		}
	}
	return sum;
}

If I would only to refactor the structure of the code then this is where I would end. But we can do so much more.

Look at the logic now. We are iterating over N numbers but every second number we do nothing, and we know that upfront. So why not jump to every second number and remove the if statement? It's because the initial value of `i` can be odd or even. We can easily adjust that on start. Also, with that there is so little code that we can inline for loop.

/* Starting from `i` add up all even numbers up to the value of `max`. */
int calc(int i, int max)
{
	int sum = 0;
	for (i += i % 2; i <= max; i += 2) sum += i;
	return sum;
}

We reduced the entire logic to SINGLE FOR LOOP and reduced number of iterations by half.

That's not the end of the story tho. Function solves the problem as expected but there are different solutions to the same problem. The main flaw of current implementation is that it will take more CPU cycles for bigger range of numbers. We can use math to get around this having constant time and speed for any range of numbers.

int calc(int beg, int end)
{
	end /= 2;
	beg = (beg - 1) / 2;
	return (end*end + end) - (beg*beg + beg);
}

One last thing. Divisions by 2 can be replaced with a bit shifting to the right. Let's do that for the sake of performance.

int calc(int beg, int end)
{
	end >>= 1;
	beg = (beg - 1) >> 1;
	return (end*end + end) - (beg*beg + beg);
}

I CHALLENGE YOU 🫵 TO WRITE SIMPLER VERSION!

Regardless of your opinion on this matter I just wanted to show how we can take the original problem and walk a different path of the refactor journey by having a different goal. And maybe along the way we will achieve enlightenment.

[1] From C -> C++ -> Rust

[2] code_report YouTube channel