This commit is contained in:
Miloslav Ciz 2023-01-15 15:55:54 +01:00
parent 3361c59555
commit b5ab038925
4 changed files with 18 additions and 16 deletions

View file

@ -10,7 +10,7 @@ Basic bit manipulation techniques are common and part of general knowledge so th
- `&` (bitwise [AND](and.md)): Performs the logical AND on all corresponding bits of two operands, e.g. `0b0110 & 0b1100` gives 0100 (4 in decimal). This may be used to mask out specific bits, to check if specific bits are set (useful to check the set flags as mentioned above) or to clear (set to zero) specific bits. Consider the flag example from above, if we want to check if value *x* has e.g. the option B set, we simply do `x & OPTION_B` which results in non-zero value if the option is set. Another example may be `myNumber & 0b00001111` (in practice you'll see hexadecimal values, i.e. `myNumber & 0x0F`) which masks out the lowest 4 bits of *myNumber* (which is equivalent to the operation [modulo](mod.md) 16).
- `~` (bitwise [NOT](not.md)): Flips every bit of the number -- pretty straightforward. This is used e.g. for clearing bits as `x & ~(1 << 3)` (clear 3rd bit of *x*).
- `^` (bitwise [XOR](xor.md)): Performs the logical XOR on all corresponding bits of two operands, e.g. `0b0110 ^ 0b1100` gives 1010 (10 in decimal). This is used to e.g. flip specific bits.
- `<<` and `>>` (bitwise shift left/right): Performs bitwise shift left or right (WATCH OUT: shifting by data type width or more is undefined behavior in C). This is typically used to perform fast multiplication (left) and division (right) by powers of two (2, 4, 8, 16, ...), just as we can quickly multiply/divide by 10 in decimal by shifting the decimal point. E.g. `5 << 3` is the same as 5 * 2^3 = 5 * 8 = 40.
- `<<` and `>>` (binary shift left/right): Performs bitwise shift left or right (WATCH OUT: shifting by data type width or more is undefined behavior in C). This is typically used to perform fast multiplication (left) and division (right) by powers of two (2, 4, 8, 16, ...), just as we can quickly multiply/divide by 10 in decimal by shifting the decimal point. E.g. `5 << 3` is the same as 5 * 2^3 = 5 * 8 = 40.
## Specific Bit Hacks

View file

@ -103,7 +103,7 @@ int main(void)
## Variables, Arithmetic, Data Types
Programming is a lot like mathematics, we compute equations and transform numerical values into other values. You probably know in mathematics we use *variables* such as *x* or *y* to denote numerical values that can change (hence variables). In programming we also use variables -- here **[variable](variable.md) is a place in memory which has a name**.
Programming is a lot like mathematics, we compute equations and transform numerical values into other values. You probably know in mathematics we use *variables* such as *x* or *y* to denote numerical values that can change (hence variables). In programming we also use variables -- here **[variable](variable.md) is a place in memory which has a name** (and in this place there will be stored a value that can change over time).
We can create variables named `x`, `y`, `myVariable` or `score` and then store specific values (for now let's only consider numbers) into them. We can read from and write to these variables at any time. These variables physically reside in [RAM](ram.md), but we don't really care where exactly (at which address) they are located -- this is e.g. similar to houses, in common talk we normally say things like *John's house* or *the pet store* instead of *house with address 3225*.
@ -267,7 +267,7 @@ Counting until 5...
4
```
IMPORTANT NOTE: in programming we **count from 0**, not from 1 (this is convenient e.g. in regards to [pointers](pointer.md)). So if we count to 5, we get 0, 1, 2, 3, 4. This is why `i` starts with value 0 and the end condition is `i < 10` (not `i <= 10`).
IMPORTANT NOTE: in programming we **count from 0**, not from 1 (this is convenient e.g. in regards to [pointers](pointer.md)). So if we count to 5, we get 0, 1, 2, 3, 4. This is why `i` starts with value 0 and the end condition is `i < 5` (not `i <= 5`).
Generally if we want to repeat the `for` loop *N* times, the format is `for (int i = 0; i < N; ++i)`.
@ -349,11 +349,11 @@ int main(void)
## Functions (Subprograms)
Functions are extremely important, no program besides the most primitive ones can be made without them.
Functions are extremely important, no program besides the most primitive ones can be made without them (well, in theory any program can be created without functions, but in practice such programs would be extremely complicated and unreadable).
**[Function](function.md) is a subprogram** (in other languages functions are also called procedures or subroutines), i.e. it is code that solves some smaller subproblem that you can repeatedly invoke, for instance you may have a function for computing a [square root](sqrt.md), for encrypting data or for playing a sound from speakers. We have already met functions such as `puts`, `printf` or `rand`.
Functions are similar to but **NOT the same as mathematical functions**. Mathematical function (simply put) takes a number as input and outputs another number computed from the input number, and this output number depends only on the input number and nothing else. C functions can do this too but they can also do additional things such as modify variables in other parts of the program or make the computer do something (such as play a sound or display something on the screen) -- these are called **[side effects](side_effect.md)**; things done besides computing and output number from an input number. For distinction mathematical functions are called *pure* functions and functions with side effects are called non-pure.
Functions are similar to but **NOT the same as mathematical functions**. Mathematical function (simply put) takes a number as input and outputs another number computed from the input number, and this output number depends only on the input number and nothing else. C functions can do this too but they can also do additional things such as modify variables in other parts of the program or make the computer do something (such as play a sound or display something on the screen) -- these are called **[side effects](side_effect.md)**; things done besides computing an output number from an input number. For distinction mathematical functions are called *pure* functions and functions with side effects are called non-pure.
**Why are function so important?** Firstly they help us divide a big problem into small subproblems and make the code better organized and readable, but mainly they help us respect the [DRY](dry.md) (*Don't Repeat Yourself*) principle -- this is extremely important in programming. Imagine you need to solve a [quadratic equation](quadratic_equation.md) in several parts of your program; you do NOT want to solve it in each place separately, you want to make a function that solves a quadratic equation and then only invoke (call) that function anywhere you need to solve your quadratic equation. This firstly saves space (source code will be shorter and compiled program will be smaller), but it also makes your program manageable and eliminates bugs -- imagine you find a better (e.g. faster) way to solving quadratic equations; without functions you'd have to go through the whole code and change the algorithm in each place separately which is impractical and increases the chance of making errors. With functions you only change the code in one place (in the function) and in any place where your code invokes (calls) this function the new better and updated version of the function will be used.
@ -440,13 +440,13 @@ The output is:
16
```
The function power takes two parameters: `x` and `n`, and returns `x` raised to the `n`s power. Note that unlike the first function we saw here the return type is `int` because this function does return a value. **Notice the command `return`** -- it is a special command that causes the function to terminate and return a specific value. In function that return a value (their return type is not `void`) there has to be a `return` command. In function that return nothing there may or may not be one, and if there is, it has no value after it (`return;`);
The function power takes two parameters: `x` and `n`, and returns `x` raised to the `n`s power. Note that unlike the first function we saw here the return type is `int` because this function does return a value. **Notice the command `return`** -- it is a special command that causes the function to terminate and return a specific value. In functions that return a value (their return type is not `void`) there has to be a `return` command. In function that return nothing there may or may not be one, and if there is, it has no value after it (`return;`);
Let's focus on how we invoke the function -- in programming we say we **call the function**. The function call in our code is `power(2,i)`. If a function returns a value (return type is not `void`), it function call can be used in any expression, i.e. almost anywhere where we can use a variable or a numerical value -- just imagine the function computes a return value and this value is **substituted to the place where we call the function**. For example we can imagine the expression `power(3,1) + power(3,0)` as simply `3 + 1`.
Let's focus on how we invoke the function -- in programming we say we **call the function**. The function call in our code is `power(2,i)`. If a function returns a value (return type is not `void`), its function call can be used in any expression, i.e. almost anywhere where we can use a variable or a numerical value -- just imagine the function computes a return value and this value is **substituted to the place where we call the function**. For example we can imagine the expression `power(3,1) + power(3,0)` as simply `3 + 1`.
If a function return nothing (return type is `void`), it can't be used in expressions, it is used "by itself"; e.g. `playBeep();`. (Function that do return a value can also be used like this -- their return value is in this case simply ignored.)
If a function returns nothing (return type is `void`), it can't be used in expressions, it is used "by itself"; e.g. `playBeep();`. (Function that do return a value can also be used like this -- their return value is in this case simply ignored.)
We call a function by writing its name (`power`), then adding brackets (`(` and `)`) and inside them we put **arguments** -- specific values that will substitute the corresponding parameters inside the function (here `x` will take the value `2` and `n` will take the current value of `i`). If the function takes no parameters (the function list is `void`), we simply put nothing inside the brackets (e.g. `playBeep();`);
We call a function by writing its name (`power`), then adding brackets (`(` and `)`) and inside them we put **arguments** -- specific values that will substitute the corresponding parameters inside the function (here `x` will take the value `2` and `n` will take the current value of `i`). If the function takes no parameters (the function parameter list is `void`), we simply put nothing inside the brackets (e.g. `playBeep();`);
Here comes the nice thing: **we can nest function calls**. For example we can write `x = power(3,power(2,1));` which will result in assigning the variable `x` the value of 9. **Functions can also call other functions** (even themselves, see [recursion](recursion.md)), but only those that have been defined before them in the source code (this can be fixed with so called [forward declarations](forward_decl.md)).
@ -1548,7 +1548,7 @@ When a function calls itself, we have to ensure that we won't end up in infinite
But what is this even good for? Recursion is actually very common in math and programming, many problems are recursive in nature. Many things are beautifully described with recursion (e.g. [fractals](fractal.md)). But remember: anything a recursion can achieve can also be achieved by iteration (loop) and vice versa. It's just that sometimes one is more elegant or more computationally efficient.
Let's see this on a typical example of the mathematical function called [factorial](factorial.md). Factorial of *N* is defined as *N* x *(N - 1)* x *(N - 2)* x ... x 1. It can also be defined recursively as: factorial of *N* is 1 if *N* is 0, otherwise *N* x *N - 1*. Here is some code:
Let's see this on a typical example of the mathematical function called [factorial](factorial.md). Factorial of *N* is defined as *N* x *(N - 1)* x *(N - 2)* x ... x 1. It can also be defined recursively as: factorial of *N* is 1 if *N* is 0, otherwise *N* times factorial of *N - 1*. Here is some code:
```
#include <stdio.h>
@ -1589,7 +1589,7 @@ Another thing to mention is that we can have **pointers to functions**; this is
## Dynamic Allocation (Malloc)
Dynamic memory allocation means the possibility of reserving additional memory for our program at run time, whenever we need it. This is opposed to static memory allocation, i.e. reserving memory for use at compile time (when compiling, before the program runs). We've already been doing static allocation whenever we created a variable -- compiler automatically reserves as much memory for our variables as is needed. But what if we're writing a program but don't yet know how much memory it will need? Maybe the program will be reading a file but we don't know how big that file is going to be -- how much memory should we reserve? Dynamic allocation allows us to reserve this memory with functions when the program is actually runing and already knows how much of it should be reserved.
Dynamic memory allocation means the possibility of reserving additional memory ([RAM](ram.md)) for our program at run time, whenever we need it. This is opposed to static memory allocation, i.e. reserving memory for use at compile time (when compiling, before the program runs). We've already been doing static allocation whenever we created a variable -- compiler automatically reserves as much memory for our variables as is needed. But what if we're writing a program but don't yet know how much memory it will need? Maybe the program will be reading a file but we don't know how big that file is going to be -- how much memory should we reserve? Dynamic allocation allows us to reserve this memory with functions when the program is actually runing and already knows how much of it should be reserved.
It must be known that dynamic allocation comes with a new kind of bug known as a **[memory leak](memory_leak.md)**. It happens when we reserve a memory and forget to free it after we no longer need it. If this happens e.g. in a loop, the program will continue to "grow", eat more and more RAM until operating system has no more to give. For this reason, as well as others such as simplicity, it may sometimes be better to go with only static allocation.
@ -1654,7 +1654,7 @@ The line starting with `char *inputChars = malloc(...` creates a pointer to `cha
## Debugging, Optimization
[Debugging](debugging.md) means localizing and fixing [bugs](bug.md) (errors) in your program. In practice there are always bugs, even in very short programs (you've probably already figured that out yourself), some small and insignificant and some pretty bad ones that make your program unusable or vulnerable.
[Debugging](debugging.md) means localizing and fixing [bugs](bug.md) (errors) in your program. In practice there are always bugs, even in very short programs (you've probably already figured that out yourself), some small and insignificant and some pretty bad ones that make your program unusable, vulnerable or even dangerous.
There are two kinds of bugs: **[syntactic](syntax.md) errors** and **[semantic](semantics.md) errors**. A syntactic error is when you write something not obeying the C grammar, it's like a typo or grammatical error in a normal language -- these errors are very easy to detect and fix, a compiler won't be able to understand your program and will point you to the exact place where the error occurs. A semantic error can be much worse -- it's a logical error in the program; the program will compile and run but the program will behave differently than intended. The program may crash, leak memory, give wrong results, run slowly, corrupt files etc. These errors may be hard to spot and fix, especially when they happen in rare situations. We'll be only considering semantic errors from now on.
@ -1666,7 +1666,7 @@ Of course, you may use the prints in other ways, for example to detect at which
What if the program isn't exactly crashing but is giving wrong results? Then you need to trace the program step by step (not exactly line by line, but maybe function by function) and check which step has a problem in it. If for example your game AI is behaving stupid, you firstly check (with prints) if it correctly detects its circumstances, then you check whether it makes the correct decision based on the circumstances, then you check whether the pathfinding algorithm finds the correct path etc. At each step you need to know what the correct behavior should be and you try to find where the behavior is broken.
Knowing how to fix a bug isn't everything, we also need to find the bugs in the first place. **[Testing](testing.md)** is the process of trying to find bugs by simply running and using the program. Remember, testing can't prove there are no bugs in the program, it can only prove bugs exits. You can do testing manually or automate the tests. Automated tests are very important for preventing so called **[regressions](regression.md)** (so the tests are called regression tests). Regression happens when during further development you break some of its already working features (it is very common, don't think it won't be happening to you). Regression test (which can simply be just a normal C program) simply automatically checks whether the already implemented functions still give the same results as before (e.g. if *sin(0) = 0* etc.). These tests should be run and pass before releasing any new version of the program (or even before any commit of new code).
Knowing how to fix a bug isn't everything, we also need to find the bugs in the first place. **[Testing](testing.md)** is the process of trying to find bugs by simply running and using the program. Remember, testing can't prove there are no bugs in the program, it can only prove bugs exist. You can do testing manually or automate the tests. Automated tests are very important for preventing so called **[regressions](regression.md)** (so the tests are called regression tests). Regression happens when during further development you break some of its already working features (it is very common, don't think it won't be happening to you). Regression test (which can simply be just a normal C program) simply automatically checks whether the already implemented functions still give the same results as before (e.g. if *sin(0) = 0* etc.). These tests should be run and pass before releasing any new version of the program (or even before any commit of new code).
[Optimization](optimization.md) is also a process of improving an already working program, but here we try to make the program more efficient -- the most common goal is to make the program faster, smaller or consume less [RAM](ram.md). This can be a very complex task, so we'll only mention it briefly.

View file

@ -5,9 +5,9 @@ Fixed point arithmetic is a simple and often [good enough](good_enough.md) metho
Fixed point has at least these advantages over floating point:
- **It doesn't require a special hardware coprocessor** for efficient execution and so doesn't introduce a [dependency](dependency.md). Programs using floating point will run extremely slowly on systems without float hardware support as they have to emulate the complex hardware in software, while fixed point will run just as fast as integer arithmetic. For this reason fixed point is very often used in [embedded](embedded.md) computers.
- It is **easier to understand and better predictable**, less tricky, [KISS](kiss.md), [suckless](sukless.md). (Float's IEEE 754 standard is 58 pages long, the paper *What Every Computer Scientist Should Know About Floating-Point Arithmetic* has 48 pages.)
- It is **natural, easier to understand and therefore better predictable**, less tricky, [KISS](kiss.md), [suckless](sukless.md). (Float's IEEE 754 standard is 58 pages long, the paper *What Every Computer Scientist Should Know About Floating-Point Arithmetic* has 48 pages.)
- Is easier to implement and so **supported in many more systems**. Any language or format supporting integers also supports fixed point.
- Isn't ugly and **doesn't waste values** (unlike IEEE 754 with positive and negative zero, denormalized numbers, many [NaNs](nan.md) etc.).
- Isn't ugly and in [two's complement](twos_complement.md) **doesn't waste values** (unlike IEEE 754 with positive and negative zero, denormalized numbers, many [NaNs](nan.md) etc.).
## How It Works
@ -25,6 +25,8 @@ in our system has to be normalized like this:
(`000010.00` * `000000.10`) / 4 = `000001.00` (2.0 * 0.5 = 1.0)
SIDE NOTE: in practice you may see division replaced by the shift operator (instead of `/4` you'll see `>> 2`).
With this normalization we also have to **think about how to bracket expressions to prevent rounding errors and [overflows](overflow.md)**, for example instead of `(x / y) * 4` we may want to write `(x * 4) / y`; imagine e.g. *x* being `00000010` (0.5) and *y* being `00000100` (1.0), the former would result in 0 (incorrect, rounding error) while the latter correctly results in 0.5. The bracketing depends on what values you expect to be in the variables so it can't really be done automatically by a compiler or library (well, it might probably be somehow handled at [runtime](runtime.md), but of course, that will be slower). There are also ways to prevent overflows e.g. with clever [bit hacks](bit_hack.md).
The normalization is basically the only thing you have to think about, apart from this everything works as with integers. Remember that **this all also works with negative number in [two's complement](twos_complement.md)**, so you can use a signed integer type without any extra trouble.

View file

@ -10,7 +10,7 @@ In software development temporary forking is used for implementing individual fe
There are two main kinds of forks:
- **soft forks**: Soft fork introduces changes that somehow stay compatible with the original project and can potentially even be merged back later, the fork exists as a different but synchronized version of the original project and as the original gets updates, the fork automatically gets these updates as well. Temporary [git](git.md) forks during development are soft forks, also for example [Linux-libre](linux_libre.md) is a soft fork of [Linux](linux.md) as it adds a set of scripts that automatically remove proprietary blobs from the current version of Linux; [patches](patch.md) of [suckless](suckless.md) software can also be seen as soft forks. It is typical that a soft fork somehow maintains just a set of changes against the original, e.g. in a form of a [diff](diff.md) or [script](script.md), i.e. soft fork is kind of a lightweight fork for which the original project stays a [dependency](dependency.md).
- **soft forks** (also dynamic forks): Soft fork introduces changes that somehow stay compatible with the original project and can potentially even be merged back later, the fork exists as a different but synchronized version of the original project and as the original gets updates, the fork automatically gets these updates as well. Temporary [git](git.md) forks during development are soft forks, also for example [Linux-libre](linux_libre.md) is a soft fork of [Linux](linux.md) as it adds a set of scripts that automatically remove proprietary blobs from the current version of Linux; [patches](patch.md) of [suckless](suckless.md) software can also be seen as soft forks. It is typical that a soft fork somehow maintains just a set of changes against the original, e.g. in a form of a [diff](diff.md) or [script](script.md), i.e. soft fork is kind of a lightweight fork for which the original project stays a [dependency](dependency.md).
- **hard forks**: Hard fork splits from the original project in such a way that it can no longer be easily merged back, it diverges in a very different way and stops being synchronized with the original. For example [darkplaces](darkplaces.md) is a hard fork of the [Quake](quake.md) 1 engine. Hard fork typically just copies all the data of the original project to a new repository and start modifying them freely. This has the disadvantage of having to repeat work on the original and the fork (e.g. if a new bug is discovered in the original after the split, it has to be manually fixed in both versions). This is one of the reasons why hard forks very often split off of projects that aren't actively developed anymore.
**Is forking good?** Yes, to create anything new it is basically necessary to build on top of someone else's work, stand on someone else's shoulders. Some people criticize too much forking; for example some cry about [Linux](linux.md) [distro](distro.md) fragmentation, they say there are too many of distros and that people should rather focus their energy on creating a single or at least fewer good operating systems, i.e. that forking is kind of "wasting effort". [LRS](lrs.md) supports any kind of wild forking and experimentation, we believe the exploration of many directions to be necessary in order to find the right one, in a [good society](less_retarded_society.md) waste of work won't be happening -- that's an issue of a [competitive society](capitalism.md), not forking.