Showing posts with label mathematics. Show all posts
Showing posts with label mathematics. Show all posts

Monday, 22 January 2018

C++ Simple Limits & Server Recase

Health & Myself
What a whirlwind few weeks I've had, first of all, I've had some minor medical work done so not really been in the mood to do anything very interesting except recuperate in my spare time.  I've also got on-going dental treatment going on, so I'm doubly aggravated.

Next I have been so busy with work, I've had a change of job title, I've been effectively promoted into the lead developer role for the platform/product range I work on; this is sort of a defacto position, as I'm the ONLY developer working in this area at present, however I'm now officially the man on point and am about to reform a new development team around it.  I will be working in Scrum methodology with this new team, we'll be pushing product to both Windows and Linux, Git will be our source control system (no matter what anyone else says) and I'm going to leverage C++ as the core product with tools all being produced in Python, very exciting stuff.

Server Issues
At home however, you may have seen this late night  problem....


I have not, as yet, tackled this problem in any satisfactory manner; there's just been too much going on.

However.... I have a plan, a plan so cunning you could put a tail on it and call it a fox.

I had a look around in the case, and removed the bad drive, here it is....


This is an old drive first of all, and it was only 3.0gb/s SATA, but it was useful as part of my mirror, and without it I'm now naked.  In my room/workspace tidy rambling video you will see I have a stack of WD blue drives on my desk, however I'm at pains to put them into this server, as the more space I give myself then the more data I have to move later, and the less places I have to stash that data.

What do I mean?... Well, that machine already has about 4TB of data in the two ZFS Pools and the one scratch drive, I do not have ANYWHERE else to put 4TB of data, and I can't afford a new drive, hence why I've carefully and strategically informed the wife I'm buying the off 1TB drive once in a while over the last year... Dropping a 4TB drive in the mix, she'll flip.

Recasing my Server & Workstation
So, my plan?


Well, first of all I have my main work station in a trusty, but now rather old, Coolmaster Cosmos 1000 case (you can see this case in some of the very early pages of this very blog)... Now, this is a very nice case, and has 10 internal 3.5" drive bays... So would be pretty nice for my server to move into, add all the new WD drives to it and voila a working server with expansion capabilities.... A recasing, that's a plan... Plus I get to perhaps look at that Xeon mod a year on and evaluate how it got along.... And of course more ZFS fun... Maybe a video about Git & setting up my own ticket handling system.


Right, that's the server... What about the now case-less workstation?

Well, I'm considering a new case, of course.  Front runners are the Corsair Obsidian 750D as it has modular upgrade ability, and if I like it, I can get another and expand the server into it next year.

Infiniband
A long-term project is to consider an Infiniband link between my server and my workstation, we'll see how this goes along, not only would I need to purchase some adapters and cables, but I would need to switch my workstation into a new dual-boot as only Linux would be on Infiniband (and as you may have noticed Windows was on the list of platforms for my new role specification).

C++
Simple limits in C++?.. What am I talking about, hmmm, well I just ran into some code:

auto value (0);
if ( requisitValue < defaultValue )
{
    value = requisitValue;
}
else
{
     value = defaultValue;
}

Nothing wrong with this you may argue, however the developer hadn't looked at the values they were working with, the defaultValue for this system locale was ALWAYS the highest value on a sliding scale, the configuration insisted the default be the cap and so any requisite value would be smaller.

I asked the chap to have a think about this code, having explained the requisit would always be lower than the default, and asked him to come up with any improvements.  He did....

auto value(
  (requisitValue < defaultValue) ? requisitValue : defaultValue);

I asked him to rationalise this change, why had he done it?


  • To meet the coding standard define of not assigning, but to initialise with a value
  • To make the code less lines of code
I have no disagreement with the first point, the second... Not so much....

I do not subscribe to needing code to be smaller (less lines of code) so long as it's readable, as for my work maintainability far out reaches using less lines of code.  Especially when the change made is exactly the the same resulting code, just the change is far harder to read.


(Don't miss-read this, I agree you should try to make functional code take less lines of code, but the above is white-space more than functional change, white-space on a modern compiler is NOT a problem, make your code readable before you start trying obfuscating functionality for fancy frills).

I stopped the developer in his tracks however, and asked him to pull up the document for the specification and describe "defaultValue"... "The maximum amount allowed in the value" he read.

So, changing the name of this value was the first step it became "MaximumValue".  Use a meaningful name.

And finally, if you really want this decision to be a single line of code, how about:

auto value(
    std::min<valueType>(
        requisiteValue,
        maximumValue));

Using the minimum function here, we make it clear we're doing a mathematical operation, it's a single line instruction (even if I do stagger it for readability over four), and we're not going to confusing the order of our parameters, so avoid introducing comparator ordering bugs later.

This is the simplest way to find a limit between two variables of the same type in C++, using std::min and std::max.  Learn about them and love them.

The reply the young man gave me... "But changing the name'll mean I have to change hundreds of different places in the code!".... "Yes, so use find & replace in files?".... "Oh but what about all the other "defaultValues?".... "Why are they not in namespaces, or classes, or using other names?"... Silence.

Wednesday, 20 November 2013

Maths: Zero Factorial in Code

Sometimes maths really confuses me... I have to be honest, I work with computers because I'm pretty good at asking them for results for things, and I know really quite well how to feed a calculation into the machine.

But, the source of these calculations is generally going to be mathematical and I find maths just to be so annoying...

Here's the information from Numberphile about Factorials...


Right, so they started with the factorial of 5 (5!) being 4 x 3 x 2 x 1 = 120.  And yes you can do this and ask your computer code to do it for you:

int factorial = 5 x 4 x 3 x 2 x 1;
cout << factorial;

Not problem, we can even write a program to do this function for us, quickly...

int factorial (const int& p_Factorial)
{
int l_result = p_Factorial;
int l_temp = p_Factorial - 1;
while ( l_temp > 0 )
{
l_result = l_result * l_temp;
l_temp--;
}
return l_result;
}

This code follows the first example they give in the video, and it presents correct results for all factorials of 1 and above.  This also matches with the graph drawn in the video.

This also goes back to what is stated in the video around the 0:45 second mark... "you can go down to 3 x 2 x 1", does Dr Grime mean you can take factorials down to one?  If so, then this code is correct, and this rule controls the while loop in there.

Except of course zero raises its ugly head, and breaks everything, now to explain zero the pattern above is not followed, a new pattern is invented, and this is where it gets sticky for computers and my brain, because the new pattern is not multiplication, its division...

If we approach the pattern above as multiplication, then my brain tells me the pattern, the parameter even, for the function starts 0, and then ends.  So to my mind zero factorial is immediately zero and the function ends:

int factorial (const int& p_Factorial)
{
int l_result = p_Factorial;
if ( p_Factorial > 0 )
{
int l_temp = p_Factorial - 1;

while ( l_temp > 0 )
{
l_result = l_result * l_temp;
l_temp--;
}
}
return l_result;
}

Or in fact I'd like to make the function throw an "invalid argument" and only let it calculate for integers of value one and above.

But this is not the pattern held to, no as you can see the pattern becomes "Take a factorial you've not calculated and use it as a parameter into the local calculation" this is a bit alien to a programming language function, take something you've not calculated as the parameter... what?

So, the function becomes something cyclic, or even two functions:

int factorialPreStep (const int& p_Factorial)
{
int l_result = p_Factorial;
if ( p_Factorial > 0 )
{
int l_temp = p_Factorial - 1;

while (l_temp > 0)
{
l_result = l_result * l_temp;
l_temp--;
}
}
return l_result;
}

int factorial (const int& p_Factorial)
{
int l_result = factorialPreStep(p_Factorial+1);
l_result = l_result / (p_Factorial + 1);
return l_result;
}

Now, the function "factorial" will always need to be passed 0 or higher, and it'll therefore always pass 1 or higher to the pre-calculation.

The full application therefore is:

#include <iostream>

using namespace std;

int factorialPreStep (const int& p_Factorial)
{
int l_result = p_Factorial;
if ( p_Factorial > 0 )
{
int l_temp = p_Factorial - 1;

do
{
l_result = l_result * l_temp;
l_temp--;
}
while ( l_temp > 0 );
}
return l_result;
}

int factorial (const int& p_Factorial)
{
int l_result = factorialPreStep(p_Factorial+1);

l_result = l_result / (p_Factorial+1);

return l_result;
}

int main (int p_argc, char** p_argv)
{
    cout << factorial(5) << endl;
    cout << factorial(4) << endl;
    cout << factorial(3) << endl;
    cout << factorial(2) << endl;
    cout << factorial(1) << endl;

    return 0;
}

And we see the output:


Right, so this code works?  With zero?...... HMmmm, lets see....


Yes, it works for zero factorial, which as per the video comes out as one, but this was by using the division pattern... 1 / 1 = 1, that's fair I get that... But it is not the same as the original multiplication pattern.

The original pattern, the original function, is a x b x c... so 5 x 4 x 3 x 2 x 1.... What is that pattern for one factorial?... Well the pattern is just 1... so what's the pattern for 0... well it's bleeding 0....

As a programmer this is an obvious avenue of research, we like to find multiple ways to get the same result, usually so we can find the most efficient solution for runtime, but what we don't like is two seemingly matching patterns, giving different results...

So for this code we could have written:

    cout << factorial(5) << endl;
    cout << factorial(4) << endl;
    cout << factorial(3) << endl;
    cout << factorial(2) << endl;
    cout << factorial(1) << endl;

as:

    cout << (5 * 4 * 3 * 2 * 1) << endl;
    cout << (4 * 3 * 2 * 1) << endl;
    cout << (3 * 2 * 1) << endl;
    cout << (2 * 1) << endl;
    cout << (1) << endl;
    
The pattern is right, the results are right, the code is right, but then we add 0!...

    cout << (0) << endl;
    
And it most certainly is not right, the pattern is broken, so was it a valid pattern?  Well it must have been because we use this pattern in our loop to calculate the prefactorial step...

Argh, see my head just exploded.... I hate maths for this.

And it only hurts my head more as I understand my solution code, and that solution code works... GRRRR.