Termbank
  1. A
    1. Abstraction
    2. Alias
    3. Argument
    4. Array
  2. B
    1. Binary code file
    2. Binary number
    3. Bit
    4. Bitwise negation
    5. Bitwise operation
    6. Byte
  3. C
    1. C library
    2. C-function
    3. C-variable
    4. Character
    5. Code block
    6. Comment
    7. Compiler
    8. Complement
    9. Conditional statement
    10. Conditional structure
    11. Control structure
  4. D
    1. Data structure
    2. Duck typing
  5. E
    1. Error message
    2. Exception
  6. F
    1. Flag
    2. Float
  7. H
    1. Header file
    2. Headers
    3. Hexadecimal
  8. I
    1. Immutable
    2. Initialization
    3. Instruction
    4. Integer
    5. Interpreter
    6. Introduction
    7. Iteroitava
  9. K
    1. Keyword
  10. L
    1. Library
    2. Logical operation
  11. M
    1. Machine language
    2. Macro
    3. Main function
    4. Memory
    5. Method
  12. O
    1. Object
    2. Optimization
  13. P
    1. Parameter
    2. Placeholder
    3. Pointer
    4. Precompiler
    5. Precompiler directive
    6. Prototype
    7. Python console
    8. Python format
    9. Python function
    10. Python import
    11. Python list
    12. Python main program
    13. Python variable
    14. Python-for
    15. Pääfunktio
    16. printf
  14. R
    1. Resource
    2. Return value
  15. S
    1. Statement
    2. Static typing
    3. String
    4. Syntax
  16. T
    1. Terminal
    2. Type
    3. Typecast
  17. U
    1. Unsigned
  18. V
    1. Value
  19. W
    1. Warning
    2. while
Completed: / exercises

Calculating Prime Numbers (15p)

In Autumn 2025, the y86-assembly language project involves creating a program that checks whether all numbers in a given sequence are prime numbers, that is, natural number greater than 1 that are not a product of two smaller natural numbers. We can say it in another way: A prime number is a natural number greater than 1 that is divisible only by 1 and itself.
You can find a list of 1000 first prime numbers in wikipedia. We are listing here the prime numbers smaller than 1000:
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997

A bit of background

The importance of prime numbers

Think about the last time you typed a password on a website. Behind the scenes, typography is just pixels… but the connection between your browser and the server is encrypted using keys based on huge prime numbers. Every time you see a lock icon in the address bar (that means that your website is safe), prime numbers are working quietly in the background.
When you type a password or send a message in a chat, that data travels across the network as bits. Anyone on the path could read those bits… unless we encrypt them.
In a very simplified, modern cryptography often works like this:
Prime numbers come in here because many encryption systems (like RSA) are built on a simple idea:
Let's see how we can generate a criptographic key:
Everyone can use your public key to encrypt data to you, but only you, with the private key, can decrypt it. The security comes from the fact that an attacker only sees n, and figuring out its prime factors is computationally expensive if p and q are large enough.
Summarizing mathematically, criptography is based on:

Checking if a number is prime number

When numbers are small, deciding if they are prime is simple: try dividing them by the first prime numbers and check if they are divisible by any of them (and hence the number is COMPOSITE, that is, not prime). But as soon as numbers grow, to tenths of digitats this becomes extremely slow even for a computer. Over time, different methods have been developed to check if a number is prime, ranging from basic school-level reasoning to algorithms used in real cryptographic systems.
Let's check few of the methods that has been developed along the years.
  1. Sieve of Eratosthenes: The Sieve of Eratosthenes is often the first “smart” algorithm students see. Instead of testing one number at a time, we create a list of all integers from 2 up to some limit N. Then we repeatedly pick the next number in the list that is not crossed out, and cross out all its multiples. After going through all the numbers following this process the remaining numbers unmarked are the primes up to N. This method is surprisingly efficient when we want to know all primes in a range. However, it has two main drawbacks for large-scale computation. First, it needs memory proportional to N, because we must store the whole table. Second, it is designed for “all primes up to N”, not for testing a single huge number. When N becomes extremely large, for example something like 10^100, storing such a table is completely impractical. However if the numbers we are dealing with are not very big, we cannot discard this method that basically would consists in creating a list of elements (prime numbers) and check if the element is in the list.
  2. Exact primality tests: Between school-level methods and fully probabilistic algorithms there are more advanced exact primality tests. These tests always give the right answer, meaning, they are deterministic. They never call a composite number “prime” or the other way around. Examples include more clever forms of division using only primes as candidates, or more theoretical algorithms such as the AKS (Agrawal–Kayal–Saxena) primality test. These methods are interesting from a mathematical point of view and can be used for numbers of moderate size. Still, when we reach the range used in modern cryptography (hundreds or thousands of bits), even many exact algorithms become too slow to be practical in everyday software.
  3. Probabilistic primality tests: To handle very large numbers efficiently, modern systems often use probabilistic primality tests. These algorithms do not try to find a divisor. Instead, they look at how the number behaves in modular arithmetic and use this behaviour to decide whether the number is composite or probably prime (it is not deterministic). The key point is that they can detect composite numbers very quickly, and when they classify a number as probably prime, the chance of being wrong can be made extremely small by repeating the test with different parameters. This makes them ideal for applications like key generation, where we need to test many large random numbers and do not want to spend minutes or hours on each one.

Fermat Primality Test

NOTE: You do not need to fully understand this whole section to complete the exercise. it will give you some interesting background.
A simple example of a probabilistic test is the Fermat primality test, based on Fermat’s little theorem. The theorem says that if p is a prime number and a is an integer that is not divisible by p, then raising a to the power p−1 gives a number that, when divided by p (or (a^(p−1)) / p) leaves a remainder of 1. Expressed more formally the Fermat Primality test:
a^{p-1} \equiv 1 \pmod{p}
The Fermat tests use this theorem and a number n (odd, larger than 2), chooses a base a with 1 < a < n, and checks whether
a^{\,n-1} \equiv 1 \pmod{n}
If this operation does not hold, then n is definetly composite (and then not prime). If it does hold, we say that n is a Fermat probable prime to base a. However, we cannot ensure that n is prime. There are composite numbers, called pseudoprimes, that satisfy this statement for many or even all choices of a.

Miller-Rabin test

NOTE: You do not need to fully understand this whole section to complete the exercise. it will give you some interesting background.
Using Fermat theorem, the Millar Rabin primarity test builds a more robust probabilistic tests that improve on the weaknesses of the Fermat test. It adds extra structure so that fewer composite numbers can slip through as probably prime . The Miller–Rabin primality test is a widely used example: it is fast, works very well on large integers and, when combined with a few repetitions, gives an error probability so small that it is acceptable in cryptographic systems.
Miller–Rabin starts by taking the exponent from the Fermat test, n−1, and decomposing it as
n - 1 = 2^s \cdot d
where d is and odd number. This factorisation is always possible, because every even number can be written as a power of two times an odd number. For example:
20 = 2^2 \cdot 5
Or other example:
144 = 2^4 \cdot 9
If we substitue the new expression in the Fermat test we get that we can compute that:
a^{2^s \cdot d} \equiv 1 \pmod{n}
And knowing that:
A \equiv 1 \pmod{n} \;\Longleftrightarrow\; A - 1 \equiv 0 \pmod{n}
We have that the previous expression is also equivalent to:
a^{2^s \cdot d} -1 \equiv 0 \pmod{n}
If we develop the expression:
a^{2^s d}-1
knowing that
(x+1)(x-1) = x^2 - 1
we have that:
\begin{aligned} a^{2^{s} d} - 1 &\Longleftrightarrow a^{2^{s-1} \cdot 2 \cdot d} - 1 \Longleftrightarrow \left(a^{2^{s-1} d}\right)^{2} - 1\\\\ &\Longleftrightarrow \left(a^{2^{s-1} d} + 1\right)\left(a^{2^{s-1} d} - 1\right)\\\\ &\Longleftrightarrow \left(a^{2^{s-1} d} + 1\right)\left(\left(a^{2^{s-2} d}\right)^{2} - 1\right)\\\\ &\Longleftrightarrow \left(a^{2^{s-1} d} + 1\right)\left(a^{2^{s-2} d} + 1\right)\left(a^{2^{s-2} d} - 1\right) \end{aligned}
and finally:
\left(a^{\,2^{\,s-1} d} + 1\right) \left(a^{\,2^{\,s-2} d} + 1\right) \cdots \left(a^{\,d} + 1\right) \left(a^{\,d} - 1\right)
Then we can conclude that:
\begin{aligned} a^{2^s d} &\equiv 1 \pmod{n}\\\\ &\Longleftrightarrow \left(a^{2^{s-1} d} + 1\right) \left(a^{2^{s-2} d} + 1\right) \cdots \left(a^{d} + 1\right) \left(a^{d} - 1\right) \equiv 0 \pmod{n} \end{aligned}
It can be deduced that if a number n is prime, then the product of several terms must be congruent to zero modulo n, what means that at least one of those factors must itself be congruent to zero modulo n.
Hence at least one of the following equalities must hold:
a^{d} - 1 \equiv 0 \pmod{n}
or
a^{d} + 1 \equiv 0 \pmod{n}
or
a^{2 d} + 1 \equiv 0 \pmod{n}
or
a^{2^{2} d} + 1 \equiv 0 \pmod{n}
or
a^{2^{s-1} d} + 1 \equiv 0 \pmod{n}
In a more compact form, the possible “good cases” are:
a^{d} \equiv 1 \pmod{n}
Or, for some r with 0 < r < s-1, the following holds:
a^{2^{r} d} \equiv -1 \pmod{n}
If any of these equalities holds, the number n passes the test for this base a (it is probably prime with respect to a). If none of the factors is ≡ 0 mod (n) (that is, we never obtain either 1 or -1 where we should), then the product cannot be ≡ 0 mod(n), and we conclude that n is composite for that base a.
This is exactly what Miller–Rabin does when it tests each facto: it checks whether a 1 appears at the beginning ( a^d) or whether a -1 appears somewhere in the chain of powers a^(2^r d).
If some of these conditions are met, the number is probably prime. We could use another base to have higher probability of success. However, if none of this conditions is met, we can ensure that the number is composite
Summarizing:
n - 1 = 2^s \cdot d
where d is odd.
Miller–Rabin then tests the sequence:
a^d, a^{2d}, a^{4d}, a^{8d},\ldots, a^{2^{s} d} = a^{n-1}
The rules are:

Requirements for the project

In this project, we are generating random numbers (stored in a sequence in memory). You must check if some of the numbers in the memory-stored sequence are prime numbers. To that end, you MUST use the Miller-Rabin algorithm. This is actually that is widely use to produce RSA keys.
  1. The project does not require checking whether all numbers are prime. Just return the first prime stored in the sequence.
  2. The implementation must follow the Miller Rabin algorithm presented before. Other solutions will not be accepted, although you may discuss alternative solutions found online in the project’s reflection section.
  3. While the y86 architecture is 64-bit, the largest number to be handled has only 10 figures, meaning the full range is not used.
  4. You may reuse assembly code from the exercises for implementing the algorithm.
The program must have the following features:
  1. The program starts at the main: block without any prior initializations.
    1. Do not use .pos 0. We will use that for testing in Lovelace. Just start writing your code. Consider that the first few instructions are for writing code. But its number is neligible.
  2. The program reads the sequence starting at memory location provided in %r8 (always bigger than 0x1800.)
    1. The sequence ends with the number zero.
  3. The program stops execution when it encounters the first number that is a prime number. This number should be returned in the %rax register.
  4. If none of the numbers are prime (all are composite), the program returns the value 0 in the %rax register.
  5. The goal is for the program to execute in approximately a maximum of 250000 machine instructions for the provided test cases. This is not an absolute requirement; see the guidelines below.

Miller- Rabin algorithm to implement

NOTE: You do not need to understand this part
We are implementing a variation of the Miller-Rabin algorithm. In this case, instead of precomputing r and d to test the sequence we are going to use a small trick.
We know from Miller-Rabin test that
n - 1 = 2^{s} \cdot d
if we call p
p = n - 1 = 2^{s} \cdot d
We see that
p = 2^{s} \cdot d \;\Longleftrightarrow\; \frac{p}{2} = 2^{s-1} \cdot d
Hence, by halving repeatidly the p we test all the factors in the Miller-Rabin test.
Using this shortcut, we could implement the algorithm as follows in C:
NOTE: This is the code you should implement EVEN if you do not fully understand it
Note that the functions modpow and halve have been implemented in previous exercises. If you do were not be able to solve them, follow the instructions below.
#define PRIME 1		/* true  = Prime     */
#define COMPOSITE 0	/* false = Composite */

/* Convenience typedef for unsigned 64-bit integers. */
typedef unsigned long long u64;

/**** PROTOTYPES***** / 
/*
 * Calculates the modular exponent of the given "base" raised to the power of
 * "exp" and of modulo "mod": base ^ exp % mod.
 *
 * The modulus must be at least two and the base at least one, otherwise the
 * behaviour is undefined.
 */
u64 modpow(u64 base, const u64 exp, const u64 mod);

/*
 * Divides the given number by two, rounding down. A simple software
 * implementation for right shifting by one.
 */
u64 halve(const u64 number);

/**** FUNCTION IMPLEMENTATION ****/
/*
 * Perform one round of Miller-Rabin tests on the primality of the given
 * number and using the given witness number as the base. The number to be
 * tested must be an odd number at least three, and the witness must be a
 * positive number smaller than the number being tested. Otherwise the
 * behaviour is undefined.
 *
 * The result of the test is either PRIME or COMPOSITE (the return value). If
 * the test says COMPOSITE, the number being tested is guaranteed to be a
 * composite number (no false negatives). However, a result of PRIME does not
 * mean the number being tested is a prime number (false positives are
 * possible).
 */
 int miller_rabin_round(const u64 number, const u64 witness)
{
	u64 p = number - 1;

	if (modpow(witness, p, number) != 1)
		return COMPOSITE;

	for (;;) {
		if (p & 1)
			return PRIME;
		p = halve(p);
		const u64 result = modpow(witness, p, number);

		if (result == 1)
			continue;
		else if (result == number - 1)
			return PRIME;
		else
			return COMPOSITE;
	}
}
You must repeat the same algorithm for each one of the following witnesses:
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37
The witness numbers are chosen as they are the minimal set of bases for Miller-Rabin who can determine weather a number is prime or composite without checking other bases.
Important aspects to consider:
# Explain here what you have tried and why do you think your code is not working. 
modpow:
    nop
    nop 
    nop
    ret

Testing

Please, do not use Lovelace for testing, you can create your own list of numbers to check. This might be an example code that you can add for testing
.pos 0 
irmovq $0x1800, %r8 

.pos 0x1800
.align 8
.quad 3
.quad 7
.quad 0
You can use some list of primes in internet
When you upload the code to Lovelace, remove the test. Remember, in your final code, you should not include the .pos 0 . It is added by Lovelace to write the code.

Guidelines for the Project

  1. The project must be completed independently.
    1. While discussions with others are allowed, the code must be written individually.
  2. The project may be submitted incomplete.
    1. Bugs are acceptable, and the project does not need to pass all test cases completely.
    2. For the project to be accepted, the submitted code must show genuine effort and should work at least partially. Code that is still in its initial stages will not be accepted.
    3. The submitted assembly code must be well-documented with comments.
  3. Plagiarism rules are the same as for the other modules.
    1. In the comments, list others with whom you discussed the project to avoid suspicions of plagiarism due to similar solutions. Same
    2. You are not allowed to submit another student's code as your own.
    3. Same applies if you are using AI. YOU MUST CLEARLY INDICATE HOW YOU HAVE USED IT.

Implementation

The project can be implemented by reusing your own code from the exercises. However, this might result in an inefficient solution since large numbers will lead to a quick increase in instruction count due to multiplication, division, and other opreations are not optimized. Optimization opportunities can be explored here.
A good approach is to first create a working version of the program without worrying about the number of instructions. Once this version works, optimization can be considered.
The instruction count can be estimated by reviewing the code and manually calculating the number of instructions executed during runtime. For example:
In the reflection section of the project, consider how the program could be made more efficient. For instance:

Assembly Programming Guidelines

Programming in assembly is more challenging than high-level languages, so a few general tips:
  1. Plan your program thoroughly, just as you would for a high-level language.
    1. A "try and error" approach doesn’t work for more complex assembly programs.
    2. Using concepts from "structured programming" is beneficial. Divide the program into functional sections that can be implemented as subroutines and combined into a working program.
      1. What subroutines are needed? How should they be placed in memory to minimize jumps/branches? How are parameters passed?
      2. Could any code from the exercises be reused?
    3. Assign consistent purposes to registers in advance (and stick to them):
      1. Some registers are only for "global variables."
      2. Some registers are for "temporary variables."
      3. Some registers are for subroutine "parameters" (should the stack be used?).
    4. Using the stack:
      1. The stack grows "downwards" in memory, so ensure it doesn’t overwrite the code...
      2. Extra data can remain on the stack at the end of the program, as long as the required result is at the bottom (or topmost in memory).
  2. Test your algorithm on paper or implement it in C to understand the intermediate results assembly code should produce.
    1. In the C code, you could name variables after registers.
  3. Test your code in a simulator.
    1. Test subroutines as standalone programs.
    2. Test conditional statements similarly. Use all possible result types (e.g., <0, ==0, >0).
    3. For jumps, it’s often sufficient to examine the ZF flag (is the result zero or not zero?).
  4. If your program exceeds 10,000 instructions, you can increase the maximum instruction limit using a simulator flag.
    1. For example, run the simulator with the command ssim fibonacci.yo -l 50000 for a 50,000-instruction limit.
  5. The code must be well-documented with comments.
    1. Test this by stepping away from the code for a few days, then returning to it.
  6. Debugging/testing your program can be done by inserting halt instructions at appropriate locations, then checking the intermediate results/state in the simulator. For example, after implementing part of the algorithm, stop execution with a halt command and inspect registers/memory. You can also use brk instructions to create breakpoints if you use the recommended online simulator. However, be sure that they are removed before sending the file to Lovelace. Lovelace do not recognize those.
  7. It's definitely worth asking for help from the exercises or the Discord chat if needed. The staff is hired to assist you.

Grading

Minimum requirements

Grading in detail

The project is worth a maximum of 15 points, distributed as follows:
  1. A functioning y86-assembly program meeting the specifications: 0–9 points
    1. Submissions exceeding 1 500 000 instructions will be accepted (even if the test wont continue in Lovelace) , and the program may include bugs. Instruction count influences points, and optimization options should be discussed in the reflection section.
    2. The submitted code must demonstrate genuine effort to solve the problem. At minimum it should be able to be executed without errors in the emulator.
    3. Maximum number of instructions accepted in each Lovelace test: 1 500 000
  2. Well-documented and readable assembly code with subroutine calls and consistent register usage: 0–3 points
  3. Reflection on your implementation: 0–3 points
    1. Address the reflection questions above. Runtime instruction count (or an estimate) must be included.
    2. Note: Reflection alone is not sufficient for passing the project!
    3. The reflection section should be 10–15 lines in the comments of your code file. Example:
# TKJ Project 2025 Prime number
# Olli Student,  email
#
# Reflection:
# - Runtime instruction count: xxxx
# - I optimized the program using method x..
# - ...

# Code starts here
main:
More specific Assessment guidelines are provided in the return box. In this module, there is no feedback meeting.
NOTE:
Grading takes into consideration, among other things, the number of instructions used, the number of test that succeed and other aspects such as code structure, comments and reflection.
We have estimated the amount of steps that the most heavy test might need and define a few threshold to check the optimization of the code.:

Submission

The project deadline is 2025-12-22 23:59

Project Submission

Submit your project code (ys file) to this checker by the deadline. Follow the instructions provided in the exercise section for uploading code to Lovelace. That is, the program should begin from the main block with no initializations before it, etc.
Remember to include your name and student number in the code comments, as well as any other individuals you collaborated with during the project.
Warning: You have not logged in. You cannot answer.
?
Abstraction is a process through which raw machine language instructions are "hidden" underneath the statements of a higher level programming language. Abstraction level determines how extensive the hiding is - the higher the abstraction level, the more difficult it is to exactly say how a complex statement will be turned into machine language instructions. For instance, the abstraction level of Python is much higher than that of C (in fact, Python has been made with C).
Alias is a directive for the precompiler that substitus a string with another string whenever encountered. In it's basic form it's comparable to the replace operation in a text editor. Aliases are define with the #define directeve, e.g. #define PI 3.1416
Argument is the name for values that are given to functions when they are called. Arguments are stored into parameters when inside the function, although in C both sides are often called just arguments. For example in printf("%c", character); there are two arguments: "%c" format template and the contents of the character variable.
Array is a common structure in programming languages that contains multiple values of (usually) the same type. Arrays in C are static - their size must be defined when they are introduced and it cannot change. C arrays can only contain values of one type (also defined when introduced).
Binary code file is a file that contains machine language instructions in binary format. They are meant to be read only by machines. Typically if you attempt to open a binary file in a text editor, you'll see just a mess of random characters as the editor is attempting to decode the bits into characters. Most editors will also warn that the file is binary.
Binary number is a number made of bits, i.e. digits 0 and 1. This makes it a base 2 number system.
A bit is the smallest unit of information. It can have exactly two values: 0 and 1. Inside the computer everything happens with bits. Typically the memory contains bitstrings that are made of multiple bits.
Bitwise negation is an operation where each bit of a binary number is negated so that zeros become ones and vice versa. The operator is ~.
Bitwise operations are a class of operations with the common feature that they manipulate individual bits. For example bitwise negation reverses each bit. Some operations take place between two binary values so that bits in the same position affect each other. These operations include and (&), or (|) and xor (^). There's also shift operations (<< and >>) where the bits of one binary number are shifted to the left or right N steps.
Byte is the size of one memory slot - typically 8 bits. It is the smallest unit of information that can be addressed from the computer's memory. The sizes of variable types are defined as bytes.
External code in C is placed in libraries from which they can be taken to use with the #include directive. C has its own standard libraries, and other libraries can also be included. However any non-standard libraries must be declared to the compiler. Typically a library is made of its source code file (.c) and header file (.h) which includes function prototypes etc.
Functions in C are more static than their Python counterparts. A function in C can only have ne return value and its type must be predefined. Likewise the types of all parameers must be defined. When a function is called, the values of arguments are copied into memory reserved for the function parameters. Therefore functions always handle values that are separate from the values handled by the coe that called them.
C variables are statically typed, which means their type is defined as the variable is introduced. In addition, C variables are tied to their memory area. The type of a variable cannot be changed.
Character is a single character, referred in C as char. It can be interpreted as an ASCII character but can also be used as an integer as it is the smallest integer that can be stored in memory. It's exactly 1 byte. A character is marked with single quotes, e.g. 'c'.
Code block is a group of code lines that are in the same context. For instance, in a conditional structure each condtion contains its own code block. Likewise the contents of a function are in their own code block. Code blocks can contain other code blocks. Python uses indentation to separate code blocks from each other. C uses curly braces to mark the beginning and end of a code block.
Comments are text in code files that are not part of the program. Each language has its own way of marking comments. Python uses the # character, C the more standard //. In C it's also possible to mark multiple lines as comments by placing them between /* and */.
A compiler is a program that transforms C source code into a binary file containing machine language instructions that can be executed by the computer's processor. The compiler also examines the source code and informs the user about any errors or potential issues in the code (warnings). The compiler's behavior can be altered with numerous flags.
Complement is a way to represent negative numbers, used typically in computers. The sign of a number is changed by flipping all its bits. In two's complement which is used in this course, 1 is added to the result after flipping.
Conditional statement is (usually) a line of code that defined a single condition, followed by a code block delimited by curly braces that is entered if the condition evaluates as true. Conditional statements are if statements that can also be present with the else keyword as else if. A set of conditional statements linked together by else keywords are called conditional structures.
Conditional structure is a control structure consisting of one or more conditional statements. Most contrl structures contain at least two branches: if and else. Between these two there can also be any number of else if statements. It is however also possible to have just a single if statement. Each branch in a conditional structure cotains executable code enclosed within a block. Only one branch of the structure is ever entered - with overlapping conditions the first one that matches is selected.
Control structures are code structures that somehow alter the program's control flow. Conditional structures and loops belong to this category. Exception handling can also be considered as a form of control structure.
Data structure is a comman name for collection that contain multiple values. In Python these include lists, tuples and dictionaries. In C the most common data structures are arrays and structs.
Python's way of treating variable values is called dynamic typing aka duck typing. The latter comes from the saying "if it swims like a duck, walks like a duck and quacks like a duck, it is a duck". In other words, the validity of a value is determined by its properties in a case-by-case fashion rather than its type.
An error message is given by the computer when something goes wrong while running or compiling a program. Typically it contains information about the problem that was encountered and its location in the source code.
An exception is what happens when a program encounters an error. Exceptions have type (e.g. TypeError) that can be used in exception handling within the program, and also as information when debugging. Typically exceptions also include textual description of the problem.
Flags are used when executing programs from the command line interface. Flags are options that define how the program behaves. Usually a flag is a single character prefixed with a single dash (e.g. -o) or a word (or multiple words connected with dashes) prefixed with two dashes (e.g. --system. Some flags are Boolean flags which means they are either on (if present) or off (if not present). Other flags take a parameter which is typically put after the flag separated either by a space or = character (e.g. -o hemulen.exe.
Floating point numbers are an approximation of decimal numbers that are used by computers. Due to their archicture computers aren't able to process real decimal numbers, so they use floats instead. Sometimes the imprecision of floats can cause rounding errors - this is good to keep in mind. In C there are two kinds of floating point numbers: float and double, where the latter has twice the number of bits.
Header files use the .h extension, and they contain the headers (function prototypes, type definitions etc.) for a .c file with the same name.
Headers in C are used to indicate what is in the code file. This includes things like function prototypes. Other typical content for headers are definition of types (structs etc.) and constants. Headers can be at the beginning of the code file, but more often - especially for libraries - they are in placed in a separate header (.h) file.
Hexadecimal numbers are base 16 numbers that are used particularly to represent memory addresses and the binary contents of memory. A hexadecimal number is typically prefixed with 0x. They use the letters A-F to represent digits 10 to 15. Hexadecimals are used because each digit represents exactly 4 bits which makes transformation to binary and back easy.
In Python objects were categorized into mutable and immutable values. An immutable value cannot have its contents changed - any operations that seemingly alter the object actually create an altered copy in a new memory location. For instance strings are immutable in Python. In C this categorization is not needed because the relationship of variables and memory is tighter - the same variable addresses the same area of memory for the duration of its existence.
When a variable is given its initial value in code, the process is called initialization. A typical example is the initialization of a number to zero. Initialization can be done alongside with introduction: int counter = 0; or separately. If a variable has not been initialized, its content is whatever was left there by the previous owner of the memory area.
Instruction set defines what instructions the processor is capable of. These instructions form the machine language of the processor architecture.
Integers themselves are probably familiar at this point. However in C there's many kinds of integers. Integer types are distinguished by their size in bits and whether they are signed or not. As a given number of bits can represent up to (2 ^ n) different integers, the maximum value for a signed integer is (2 * (n - 1))
Python interpreter is a program that transforms Python code into machine language instructions at runtime.
The moment a variable's existence is announed for the first is called introduction. When introduced, a variable's type and name must be defined, e.g. int number;. When a variable is introduced, memory is reserved for it even though nothing is written there yet - whatever was in the memory previously is still there. For this reason it's often a good idea to initialize variables when introducing them.
Iteroitava objekti on sellainen, jonka voi antaa silmukalle läpikäytäväksi (Pythonissa for-silmukalle). Tähän joukkoon kuuluvat yleisimpinä listat, merkkijonot ja generaattorit. C:ssä ei ole silmukkaa, joka vastaisi Pythonin for-silmukan toimintaa, joten taulukoiden yms. läpikäynti tehdään indeksiä kasvattavilla silmukoilla.
Keywords are words in programming languages that have been reserved. Good text editors generally use a different formatting for keywords (e.g. bold). Usually keywords are protected and their names cannot be used for variables. Typical keywords include if and else that are used in control structures. In a way keywords are part of the programming language's grammar.
A library is typically a toolbox of functions around a single purpose. Libraries are taken to use with the include directive. If a library is not part of the C standard library, its use must also be told to the compiler.
Logical operation refers to Boole's algebra, dealing with truth values. Typical logical operations are not, and, or which are often used in conditional statements. C also uses bitwise logical operations that work in the same way but affect each bit separately.
Machine language is made of instructions understood by the processor. Machine language is often called Assembly and it is the lowest level where it's reasonable for humans to give instructions to computers. Machine language is used at the latter part of this course - students taking the introduction part do not need to learn it.
Macro is an alias that defines a certain keyword to be replaced by a piece of code. When used well, macros can create more readable code. However, often the opposite is true. Using macros is not recommended in this course, you should just be able to recognize one when you see it.
In C the main function is the starting point when the program is run. The command line arguments of the program are passed on to the main function (although they do not have to be received), and its return value type is int. At its shortest a main function can defined as int main().
When programs are run, all their data is stored in the computer's memory. The memory consists of memory slots with an address and contents. All slots are of equal size - if an instance of data is larger, a continuous area of multiple memory slots is reserved.
Method is a function that belongs to an object, often used by the object to manipulate itself. When calling a method, the object is put before the method: values.sort().
Object is common terminology in Python. Everything in Python is treated as objects - this means that everything can be referenced by a variable (e.g. you can use a variable to refer to a function). Objects are typically used in object-oriented languages. C is not one.
Optimization means improving the performance of code, typically by reducing the time it takes to run the code or its memory usage. The most important thing to understand about opimization is that it should not be done unless it's needed. Optimization should only be considered once the code is running too slowly or doesn't fit into memory. Optimization should also not be done blindly. It's important to profile the code and only optimize the parts that are most wasteful.
A parameter is a variable defined alongside with a function. Parameters receive the values of the function's arguments when it's called. This differentation between parameters and arguments is not always used, sometimes both ends of the value transfer are called arguments.
Placeholders are used in string formatting to mark a place where a value from e.g. a variable will be placed. In Python we used curly braces to mark formatting placeholders. In C the % character is used which is followed by definitions, where the type of the value is mandatory. For instance "%c" can only receive a char type variable.
Pointers in C are special variables. A pointer contains a memory address of the memory location where the actual data value is located. In a sense they work like Python variables. A variable can be defined as a pointer by postfixing its type with * when it's being introduced, e.g. int* value_ptr; creates a pointer to an integer. The contents of the memory address can be fetched by prefixing the variable name with * (e.g. *value_ptr. On the other hand, the address of a memory adress can be fetched by prefixing a variable name with &, (e.g. &value.
The C precompiler is an apparatus that goes through all the precompiler directives in the code before the program is actually compiled. These directives include statements which add the source code of the included libraries into the program, and define directives that can define constant values (aliases) and macros.
Directives are instructions that are addressed at the precompiler. They are executed and removed from the code before the actual compilation. Directives start with the # character. The most common one is include which takes a library into use. Another common one is define, which is used e.g. to create constant values.
Prototype defines a function's signature - the type of its return value, its name and all the arguments. A prototype is separate from the actual function definition. It's just a promise that the function that matches the prototype will be found in the code file. Prototypes are introduced at the beginning of the file or in a separate header file. In common cases the prototype definition is the same as the line that actually starts the function introduction.
Interactive interpreter or Python console is a program where users can write Python code lines. It's called interactive because each code line is executed after its been fully written, and the interpreter shows the return value (if any).
The format method of string in Python is a powerful way to include variable values into printable text. The string can use placeholders to indicate where the format method's arguments are placed.
Python functions can have optional parameters that have a given default value. In Python the values of arguments in a function call are transferred to function parameters through reference, which means that the values are the same even though they may have different names. Python functions can have multiple return values.
In Python the import statement is used for bringing in modules/libraries - either built-in ones, thrid party modules or other parts of the same application. In Python the names from the imported module's namespace are accessible through the module name (e.g. math.sin). In C libraries are taken to use with include, and unlike Python import it brings the library's namespace into the program's global namespace.
Python lists were discovered to be extremely effective tools in Elementary Programming. A Python list is an ordered collection of values. Its size is dynamic (i.e. can be changed during execution) and it can include any values - even mixed types. Lists can also include other lists etc.
In Python main program is the part of code that is executed when the program is started. Usually the main program is at the end of the code file and most of the time under if __name__ == "__main__": if statement. In C there is no main program as such, code execution starts with the main function instead.
In Python a variable is a reference to a value, a connection between the variable's name in code and the actual data in memory. In Python variables have no type but their values do. The validity of a value is tested case by case when code is executed. In these ways they are different from C variables, and in truth Python variables are closer to C pointers.
Pythonin for-silmukka vastaa toiminnaltaan useimmissa kielissä olevaa foreach-silmukkaa. Se käy läpi sekvenssin -esim. listan - jäsen kerrallaan, ottaen kulloinkin käsittelyssä olevan jäsenen talteen silmukkamuuttujaan. Silmukka loppuu, kun iteroitava sekvenssi päättyy.
Pääfunktio on C:ssä ohjelman aloituspiste ja se korvaa Pythonista tutun pääohjelman. Oletuksena pääfunktion nimi on main ja se määritellään yksinkertaisimmillaan int main().
Resource referes to the processing power, memory, peripheral devices etc. that are availlable in the device. It includes all the limitations within which programs can be executed and therefore defines what is possible with program code. On a desktop PC resources are - for a programmer student - almost limitless, but on embedded devices resources are much more scarce.
Return value is what a function returns when its execution ends. In C functions can only have one return value, while in Python there can be multiple. When reading code, return value can be understood as something that replaces the function call after the function has been executed.
A statement is a generic name for a single executable set of instructions - usually one line of code.
C uses static typing This means that the type of variables is defined as they are created, and values of different types cannot be assigned to them. The validity of a value is determined by its type (usually done by the compiler). Python on the other hand uses dynamic typing aka.duck typing.
In Python all text is handled as strings and it has no type for single characters. However in C there are no strings at all - there's only character arrays. A character array can be defined like a string however, e.g. char animal[7] = "donkey"; where the number is the size of the array + 1. The +1 is neede because the string must have space for the null terminator '\0' which is automatically added to the end of the "string".
Syntax is the grammar of a programming language. If a text file does not follow the syntax of code, it cannot be executed as code, or in the case of C, it cannot be compiled.
Terminal, command line interface, command line prompt etc. are different names to the text-based interface of the operating system. In Windows you can start the command line prompt by typing md to the Run... window (Win+R). Command line is used to give text-based commands to the operating system.
The data in a computer's memory is just bits, but variables have type. Type defines how the bits in memory should be interpreted. It also defines how many bits are required to store a value of the type. Types are for instance int, float and char.
Typecast is an operation where a variable is transformed to another type. In the elementary course this was primarily done with int and float functions. In C typecast is marked a bit differently: floating = (float) integer}. It's also noteworthy that the result must be stored in a variable that is the proper type. it is not possible to change the type of an existing variable.
Unsigned integer is a an integer type where all values are interpreted as positive. Since sign bit is not needed, unsigned integers can represent twice as large numbers as signed integers of the same size. An integer can be introduced as unsigned by using the unsigend keyword, e.g. unsigned int counter;.
In the elementary programming course we used the term value to refer to all kinds of values handled by programs be it variables, statement results or anything. In short, a value is data in the computer's memory that can be referenced by variables. In C the relationship between a variable and its value is tighter as variables are strictly tied to the memory area where its value is stored.
A warning is a notification that while executing or - in this course particularly - compiling it, something suspicious was encountered. The program may still work, but parts of it may exhibit incorrect behavior. In general all warnings should be fixed to make the program stable.
One way to print stuff in C is the printf function, which closely resembles Python's print function. It is given a printable string along with values that will be formatted into the string if placeholders are used. Unlike Python, C's printf doesn't automatically add a newline at the end. Therefore adding \n at the end is usually needed.
Out of loops, while is based on repetition through checking a condition - the code block inside the loop is repeated until the loop's condition is false. The condition is defined similarly to conditional statements, e.g. while (sum < 21).