Math For Programmers PDF
Math For Programmers PDF
1 Prime numbers 1
1.1 Integer factorization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.1 Using composite number as a container . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.2 Using composite number as a container (another example) . . . . . . . . . . . . . . . . . . . . 4
1.2 Coprime numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3 Semiprime numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4 How RSA1 works . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4.1 Fermat little theorem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.4.2 Euler’s totient function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.4.3 Euler’s theorem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.4.4 RSA example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.4.5 So how it works? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.4.6 Breaking RSA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.4.7 The difference between my simplified example and a real RSA algorithm . . . . . . . . . . . . 10
1.4.8 The RSA signature . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.4.9 Hybrid cryptosystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2 Modulo arithmetics 13
2.1 Quick introduction into modular arithmetic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.1.1 Modular arithmetic on CPUs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.1.2 Remainder of division by modulo 2n . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.1.3 Getting random numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.2 Modulo inverse, part I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.2.1 No remainder? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.3 Modulo inverse, part II . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.4 Reversible linear congruential generator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.5 Getting magic number using extended Euclidean algorithm . . . . . . . . . . . . . . . . . . . . . . . . 22
3 Probability 25
3.1 Text strings right in the middle of compressed data . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2 Autocomplete using Markov chains . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.2.1 Dissociated press . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.2.2 Autocomplete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.2.3 Further work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.2.4 The files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.2.5 Read more . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.3 random.choices() in Python 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
1
Rivest–Shamir–Adleman cryptosystem
3
4 Combinatorics 45
4.1 Soldering a headphones cable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.2 Vehicle license plate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
4.3 Forgotten password . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.4 Executable file watermarking/steganography using Lehmer code and factorial number system . . . . . 53
4.5 De Bruijn sequences; leading/trailing zero bits counting . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.5.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.5.2 Trailing zero bits counting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.5.3 Leading zero bits counting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.5.4 Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.5.5 Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.5.6 Generation of De Bruijn sequences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.5.7 Other articles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
6 Logarithms 73
6.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
6.1.1 Children’s approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
6.1.2 Scientists’ and engineers’ approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
6.2 Logarithmic scale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
6.2.1 In human perception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
6.2.2 In electronics engineering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
6.2.3 In IT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
6.2.4 Web 2.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
6.3 Multiplication and division using addition and subtraction . . . . . . . . . . . . . . . . . . . . . . . . 77
6.3.1 Logarithmic slide rule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
6.3.2 Logarithmic tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
6.3.3 Working with very small and very large numbers . . . . . . . . . . . . . . . . . . . . . . . . . 80
6.3.4 IEEE 754: adding and subtracting exponents . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
6.4 Exponentiation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
6.5 Square root . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
6.6 Base conversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
6.7 Binary logarithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
6.7.1 Denoting a number of bits for some value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
6.7.2 Calculating binary logarithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
6.7.3 O(log n) time complexity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
6.8 Common (base 10) logarithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
6.9 Natural logarithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
6.9.1 Savings account in your bank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
6.9.2 Exponential decay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
2
Cyclic redundancy check
4
7 Symbolic computation 97
7.1 Rational data type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Thanks
Thanks to those who helped: Slava “Avid” Kazakov, Serhiy Matviychuk.
5
6
Chapter 1
Prime numbers
Prime numbers are the numbers which has no divisors except itself and 1. This can be represented graphically.
Let’s take 9 balls or some other objects. 9 balls can be arranged into rectangle:
ooo
ooo
ooo
So are 12 balls:
oooo
oooo
oooo
Or:
ooo
ooo
ooo
ooo
ooooooo
Or:
o
o
o
1
o
o
o
o
It’s not possible to form a rectangle using 7 balls, or 11 balls or any other prime number.
The fact that balls can be arranged into rectangle shows that the number can be divided by the number which is rep-
resented by height and width of rectangle. Balls of prime number can be arranged vertically or horizontally, meaning,
there are only two divisors: 1 and the prime number itself.
This mean that 100 can be constructed using 2 and 5 prime numbers (22 · 52 ):
Let’s find a first 5 prime numbers, each number for each character:
2
Listing 1.4: Wolfram Mathematica
In []:= Map[ Prime [#] &, Range [5]]
Out []= {2, 3, 5, 7, 11}
Build a huge number using prime numbers as bases and ASCII codes as exponents, then get a product of all them
(272 · 3101 · 5108 · 7108 · 11111 ):
A first number in each pair is prime number and the second is exponent. Get the text string back:
That allows to have some fun. Let’s add exclamation point to the end of string by manipulating only the big number.
ASCII code of exlamation point is 33. The next prime number after 11 is 13. So add it (by multiplying by 1333 ):
3
Listing 1.9: Wolfram Mathematica
In []:= factored = FactorInteger [tmp]
Out []= {{2 , 72}, {3, 101} , {5, 108} , {7, 108} , {11 , 111} , {13 , 33}}
Wow, that works. Will it be possible to remove one ’l’ character from the string at the third position? The ’l’ has the
ASCII code of 108 and it is exponent for two prime numbers in our expression: 5 (first ’l’) and 7 (second ’l’).
To knock out the character, we divide the big number by the corresponding prime number with the exponent of 108
(divide by 5108 ):
Then let’s set the number at fourth posistion to 123. The fourth prime number is 7 (the percent sign in Mathematica
denotes the last result):
4
Then let’s set the number at fifth position to 456. The fifth prime number is 11:
Then let’s decrement the number at fourth position, the fourth prime number is 7:
Let’s factor the composite number and get all the numbers we set inside container (1, 122, 456):
5
3 and 5 are coprimes. So are 7 and 10. So are 4, 5 and 9.
Coprime numbers are the numerator and denominator in fraction which cannot be reduced further (irreducible frac-
tion). For example, 130 65
14 is 7 after reduction (or simplification), 65 and 7 are coprime to each other, but 130 and 14 are
not (they has 2 as common divisor).
One application of coprime numbers in engineering is to make number of cogs on cogwheel and number of chain
elements on chain to be coprimes. Let’s imagine bike cogwheels and chain:
If you choose 5 as number of cogs on one of cogwhell and you have 10 or 15 or 20 chain elements, each cog on cogwheel
will meet some set of the same chain elements. For example, if there are 5 cogs on cogwheel and 20 chain elements,
each cog will meet only 4 chain elements and vice versa: each chain element has its own cog on cogwheel. This is bad
because both cogwheel and chain will run out slightly faster than if each cog would interlock every chain elements at
some point. To reach this, number of cogs and chain elements could be coprime numbers, like 5 and 11, or 5 and 23.
That will make each chain element interlock each cog evenly, which is better.
In 1974 the Arecibo message was sent with a radio signal aimed at a star cluster
. It consisted of 1679 binary digits intended to be interpreted as a 23 ×73
bitmap image . The number 1679 = 23 ×73 was chosen because it is a semiprime
and therefore can only be broken down into 23 rows and 73 columns , or 73 rows
and 23 columns .
( https://en.wikipedia.org/wiki/Semiprime )
6
1.4.1 Fermat little theorem
Fermat little theorem states that if p is prime, this congruence is valid for any a in the environment of modulo artihemtic
of base p:
ap−1 ≡ 1 (mod p).
There are proofs, which are, perhaps, too tricky for this article which is intended for beginners. So far, you can just
take it as granted.
This theorem may be used to sieve prime numbers. So you take, for example, 10 and test it. Let’s take some random a
value (123) (Wolfram Mathematica):
We’ve got 3, which is not 1, indicating the 10 is not prime. On the other hand, 11 is prime:
This method is not perfect, some composite p numbers can lead to 1, for example p=1105, but can be used as a method
to sieve vast amount of prime numbers candidates.
7
Listing 1.18: Wolfram Mathematica
In []:= p = Prime [ RandomInteger [50]]
Out []= 89
In []:= n = p*q
Out []= 3827
3827 is published as public key, named public key modulus or modulo. It is semiprime. There is also public key expo-
nent e, which is not secret, is often 65537, but we will use 17 to keep all results tiny.
Now The Sender wants to send a message (123 number) to The Receiver and he/she uses one-way function:
3060 is encrypted message, which can be decrypted only using p and q values separately. This is one-way function, be-
cause only part of exponentiation result is left. One and important consequence is that even The Sender can’t decrypt
it. This is why you can encrypt a piece of text in PGP/GnuPG to someone using his/her public key, but can’t decrypt it.
Perhaps, that’s how CryptoLockers works, making impossible to decrypt the files.
To recover message (123), p and q values must be known.
First, we get the result of Euler’s totient function (p − 1)(q − 1) (this is the point where p and q values are needed):
Now we calculating decrypting exponent using multiplicative modulo inverse (multiplicative inverse was also de-
scribed in here (2) (e−1 (mod totient = (p − q)(q − 1))):
8
Listing 1.22: Wolfram Mathematica
In [18]:= Mod[ encrypted ^d, n]
Out [18]= 123
So the d exponent forms another one-way function, restoring the work of what was done during encryption.
This allows...
Or in Mathematica:
9
1.4.6 Breaking RSA
We can try to factor n semiprime (or RSA modulus) in Mathematica:
And we getting correct p and q, but this is possible only for small values. When you use some big ones, factorizing is
extremely slow, making RSA unbreakable, if implemented correctly.
The bigger p, q and n numbers, the harder to factorize n, so the bigger keys in bits are, the harder it to break.
1.4.7 The difference between my simplified example and a real RSA algorithm
In my example, public key is n = pq (product) and secret key are p and q values stored separately. This is not very
efficient, to calculate totient and decrypting exponent each time. So in practice, a public key is n and e, and a secret
key is at least n and d, and d is stored in secret key precomputed.
For example, here is my PGP public key2 :
dennis@ ...:∼$ gpg --export - options export -reset -subkey - passwd --export -secret -
subkeys 0 x3B262349 \! | pgpdump
Old: Secret Key Packet (tag 5) (533 bytes )
Ver 4 - new
Public key creation time - Tue Jun 30 02:08:38 EEST 2015
Pub alg - RSA Encrypt or Sign(pub 1)
RSA n (4096 bits) - ...
RSA e(17 bits) - ...
...
... so there are available openly big (4096 bits) n and e (17 bits).
And here is my PGP secret key:
dennis@ ...:∼$ gpg --export - options export -reset -subkey - passwd --export -secret -
subkeys 0 x55B5C64F \! | pgpdump
gpg: about to export an unprotected subkey
10
Pub alg - RSA Encrypt or Sign(pub 1)
RSA n (4096 bits) - ...
RSA e(17 bits) - ...
...
Old: Secret Subkey Packet (tag 7) (1816 bytes )
Ver 4 - new
Public key creation time - Tue Jun 30 02:08:38 EEST 2015
Pub alg - RSA Encrypt or Sign(pub 1)
RSA n (4096 bits) - ...
RSA e(17 bits) - ...
RSA d (4093 bits) - ...
RSA p (2048 bits) - ...
RSA q (2048 bits) - ...
RSA u (2048 bits) - ...
Checksum - 94 53
...
Now he publishes n = pq (3827), e (17 in our example), the message (456) and the signature (2282). Some other
Consumers can verify its signature using e exponent and n:
... this is another illustration that e and d exponents are complement each other, by modulo totient = (p − 1)(q − 1).
The signature can only be generated with the access to p and q values, but it can be verified using product (n = pq)
value.
11
12
Chapter 2
Modulo arithmetics
This is yet another application of modulo arithmetic, which many of us encountered in childhood.
Given a counting rhyme, like:
( https://en.wikipedia.org/wiki/Eeny,_meeny,_miny,_moe )
... predict, who will be choosen.
If I’m correct, that rhyme has 16 items, and if a group of kids constist of, say, 5 kids, who will be choosen? 16 mod 5 = 1,
meaning, the next kid after the one at whom counting had begun.
13
Or 7 kids, 16 mod 7 = 2. Count two kids after the first one.
If you can calculate this quickly, you can probably get an advantage by choosing a better place within a circle...
Now let’s recall old mechanical counters which were widespread in pre-digital era:
Figure 2.1: The picture was stolen from http://www.featurepics.com/ — sorry for that!
This counter has 6 wheels, so it can count from 0 to 106 − 1 or 999999. When you have 999999 and you increase the
counter, it will resetting to 000000— this situation is usually understood by engineers and computer programmers as
overflow. And if you have 000000 and you decrease it, the counter will show you 999999. This situation is often called
“wrap around”. See also: http://en.wikipedia.org/wiki/Integer_overflow.
14
And if your code is 32-bit one in 64-bit environment, CPU registers are bigger, so the whole result can be stored there,
but high half is hidden behind the scenes – because no 32-bit code can access it.
By the way, this is the reason why remainder calculation is often called ”division by modulo”. C/C++ has a percent sign
(“%”) for this operation, but some other PLs like Pascal and Haskell has ”mod” operator.
Usually, almost all sane computer programmers works with variables as they never wrapping around and value here is
always in some limits which are defined preliminarily. However, this implicit division operation or ”wrapping around”
can be exploited usefully.
15
return buffer ;
}
( https://github.com/git/git/blob/aa1c6fdf478c023180e5ca5f1658b00a72592dc6/hex.c )
This function returns a pointer to the string containing hexadecimal representation of SHA1 digest
(like ”4e1243bd22c66e76c2ba9eddc1f91394e57f9f83”). But this is plain C and you can calculate SHA1 for some block,
get pointer to the string, then calculate SHA1 for another block, get pointer to the string, and both pointers are still
points to the same string buffer containing the result of the second calculation. As a solution, it’s possible to allo-
cate/deallocate string buffer each time, but more hackish way is to have several buffers (4 are here) and fill the next
each time. The bufno variable here is a buffer counter in 0..3 range. Its value increments each time, and its value is
also always kept in limits by AND operation (3 & ++bufno).
The author of this piece of code (seemingly Linus Torvalds himself) went even further and forgot (?) to initialize bufno
counter variable, which will have random garbage at the function start. Indeed: no matter, which buffer we are starting
each time! This can be mistake which isn’t affect correctness of the code, or maybe this is left so intentionally – I don’t
know.
No matter what compiler do you use, you can think about it as 10 is subtraced from rand() result, as long as there is
still a number bigger than 10. Hence, result is remainder of division of rand() result by 10.
One nasty consequence is that neither 0x8000 nor 0x80000000 cannot be divided by 10 evenly, so you’ll get some
numbers slightly more often than others.
I tried to calculate in Mathematica. Here is what you get if you write <i>rand()
In []:= Counts [Map[Mod [#, 10] &, Range [0, 16^^8000 - 1]]]
Out []= <|0 -> 3277 , 1 -> 3277 , 2 -> 3277 , 3 -> 3277 , 4 -> 3277 ,
5 -> 3277 , 6 -> 3277 , 7 -> 3277 , 8 -> 3276 , 9 -> 3276| >
16
In []:= Counts [Map[Mod [#, 100] &, Range [0, 16^^8000 - 1]]]
Out []= <|0 -> 328 , 1 -> 328 , 2 -> 328 , 3 -> 328 , 4 -> 328 , 5 -> 328 ,
6 -> 328 , 7 -> 328, 8 -> 328 , 9 -> 328 , 10 -> 328 , 11 -> 328 ,
12 -> 328, 13 -> 328 , 14 -> 328 , 15 -> 328 , 16 -> 328 , 17 -> 328 ,
18 -> 328, 19 -> 328 , 20 -> 328 , 21 -> 328 , 22 -> 328 , 23 -> 328 ,
24 -> 328, 25 -> 328 , 26 -> 328 , 27 -> 328 , 28 -> 328 , 29 -> 328 ,
30 -> 328, 31 -> 328 , 32 -> 328 , 33 -> 328 , 34 -> 328 , 35 -> 328 ,
36 -> 328, 37 -> 328 , 38 -> 328 , 39 -> 328 , 40 -> 328 , 41 -> 328 ,
42 -> 328, 43 -> 328 , 44 -> 328 , 45 -> 328 , 46 -> 328 , 47 -> 328 ,
48 -> 328, 49 -> 328 , 50 -> 328 , 51 -> 328 , 52 -> 328 , 53 -> 328 ,
54 -> 328, 55 -> 328 , 56 -> 328 , 57 -> 328 , 58 -> 328 , 59 -> 328 ,
60 -> 328, 61 -> 328 , 62 -> 328 , 63 -> 328 , 64 -> 328 , 65 -> 328 ,
66 -> 328, 67 -> 328 , 68 -> 327 , 69 -> 327 , 70 -> 327 , 71 -> 327 ,
72 -> 327, 73 -> 327 , 74 -> 327 , 75 -> 327 , 76 -> 327 , 77 -> 327 ,
78 -> 327, 79 -> 327 , 80 -> 327 , 81 -> 327 , 82 -> 327 , 83 -> 327 ,
84 -> 327, 85 -> 327 , 86 -> 327 , 87 -> 327 , 88 -> 327 , 89 -> 327 ,
90 -> 327, 91 -> 327 , 92 -> 327 , 93 -> 327 , 94 -> 327 , 95 -> 327 ,
96 -> 327, 97 -> 327 , 98 -> 327 , 99 -> 327| >
…now larger part of numbers happens slightly seldom, these are 68...99.
This is sometimes called modulo bias. It’s perhaps acceptable for videogames, but may be critical for scientific simu-
lations, including Monte Carlo method.
Constructing a PRNG1 with uniform distribution may be tricky, there are couple of methods:
http://www.reddit.com/r/algorithms/comments/39tire/using_a_01_generator_generate_a_random_number/,
http://www.prismmodelchecker.org/casestudies/dice.php.
int main ()
{
printf ("%d\n", div17 (1700) ); // result=100
printf ("%d\n", div17 (34)); // result=2
printf ("%d\n", div17 (2091) ); // result=123
};
1
Pseudorandom number generator
17
How it works?
Let’s imagine, we work on 4-bit CPU, it has 4-bit registers, each can hold a value in 0..15 range.
Now we want to divide by 3 using multiplication. Let’s find modulo inverse of 3 using Wolfram Mathematica:
The ”magic number” for division by 3 is 11. Multiply by 11 instead of dividing by 3 and you’ll get a result (quotient).
This works, let’s divide 6 by 3. We can now do this by multiplying 6 by 11, this is 66=0x42, but on 4-bit register, only 0x2
will be left in register (0x42 ≡ 2 mod 24 ). Yes, 2 is correct answer, 6/3=2.
Let’s divide 3, 6 and 9 by 3, by multiplying by 11 (m).
A “protruding” asterisk(s) (“*”) in the last non-empty chunk is what will be left in 4-bit register. This is 1 in case of 33, 2
if 66, 3 if 99.
In fact, this ”protrusion” is defined by 1 in the equation we’ve solved. Let’s replace 1 with 2:
Now the new ”magic number” is 6. Let’s divide 3 by 3. 3*6=18=0x12, 2 will be left in 4-bit register. This is incorrect, we
have 2 instead of 1. 2 asterisks are ”protruding”. Let’s divide 6 by 3. 6*6=36=0x24, 4 will be left in the register. This is
also incorrect, we now have 4 ”protruding” asterisks instead of correct 2.
Replace 1 in the equation by 0, and nothing will ”protrude”.
2.2.1 No remainder?
Now the problem: this only works for dividends in 3x form, i.e., which can be divided by 3 with no remainder. Try to
divide 4 by 3, 4*11=44=0x2c, 12 will be left in register, this is incorrect. The correct quotient is 1.
We can also notice that the 4-bit register is ”overflown” during multiplication twice as much as in ”incorrect” result in
low 4 bits.
Here is what we can do: use only high 4 bits and drop low 4 bits. 4*11=0x2c and 2 is high 4 bits. Divide 2 by 2, this is 1.
18
Let’s ”divide” 8 by 3. 8*11=88=0x58. 5/2=2, this is correct answer again.
Now this is the formula we can use on our 4-bit CPU to divide numbers by 3: ”x*3 » 4 / 2” or ”x*3 » 5”. This is the same
as almost all modern compilers do instead of integer division, but they do this for 32-bit and 64-bit registers.
int f(int a)
{
return a/9;
};
push ebp
mov ebp , esp
mov ecx , [ebp+arg_0 ]
mov edx , 954437177 ; 38E38E39h
mov eax , ecx
imul edx
sar edx , 1
mov eax , ecx
sar eax , 1Fh
mov ecx , edx
sub ecx , eax
mov eax , ecx
pop ebp
retn
f endp
19
This function still works, without division operation. How?
From school-level mathematics, we can remember that division by 9 can be replaced by multiplication by 19 . In fact,
sometimes compilers do so for floating-point arithmetics, for example, FDIV instruction in x86 code can be replaced
by FMUL. At least MSVC 6.0 will replace division by 9 by multiplication by 0.111111... and sometimes it’s hard to be
sure, what operation was in the original source code.
But when we operate over integer values and integer CPU registers, we can’t use fractions. However, we can rework
fraction like that:
1·M agicN umber
result = x
9 =x· 1
9 =x· 9·M agicN umber
Given the fact that division by 2n is very fast (using shifts), we now should find that M agicN umber, for which the
following equation will be true: 2n = 9 · M agicN umber.
Division by 232 is somewhat hidden: lower 32-bit of product in EAX is not used (dropped), only higher 32-bit of product
(in EDX) is used and then shifted by additional 1 bit.
954437177 232+1
In other words, the assembly code we have just seen multiplicates by 232+1 , or divides by 954437177 . To find a
divisor we just have to divide numerator by denominator. Using Wolfram Alpha, we can get 8.99999999.... as result
(which is close to 9).
Many people miss “hidden” division by 232 or 264 , when lower 32-bit part (or 64-bit part) of product is not used. This
is why division by multiplication is difficult to understand at the beginning.
uint32_t state ;
uint32_t rand ()
{
state = state *214013+2531011;
return (state > >16) &0 x7FFF ;
};
The last bit shift is attempt to compensate LCG weakness and we may ignore it so far. Will it be possible to make an
inverse function to rand(), which can reverse state back? First, let’s try to think, what would make this possible? Well,
if state internal variable would be some kind of BigInt or BigNum container which can store infinitely big numbers,
then, although state is increasing rapidly, it would be possible to reverse the process. But state isn’t BigInt/BigNum,
it’s 32-bit variable, and summing operation is easily reversible on it (just subtract 2531011 at each step). As we may
know now, multiplication is also reversible: just multiply the state by modular multiplicative inverse of 214013!
uint32_t state ;
2
Linear congruential generator
20
void next_state ()
{
state = state *214013+2531011;
};
void prev_state ()
{
state =state -2531011; // reverse summing operation
state = state *3115528533; // reverse multiply operation. 3115528533 is modular
inverse of 214013 in 232 .
};
prev_state ();
printf ("state =%d\n", state );
prev_state ();
printf ("state =%d\n", state );
prev_state ();
printf ("state =%d\n", state );
};
state =12345
state = -1650445800
state =1255958651
state = -456978094
state =1255958651
state = -1650445800
state =12345
It’s hard to find a real-world application of reversible LCG, but this could be the one: a media player with forward/back-
ward buttons. Once shuffle is clicked, random number is generated (number of item to be played). User clicks forward:
get a new random number by calculating the next state. User clicks backward: get it by calculating the previous state.
Thus, a user could navigate through some ”virtual” (but consistent) playlist, which is even not present in media player’s
memory!
21
2.5 Getting magic number using extended Euclidean algorithm
Extended Euclidean algorithm can find x/y for given a/b in the diophantine equation: ax + by = gcd(a, b).
x/y are also known as “Bézout coefficients”.
However, since a/b are coprime to each other, gcd(a, b) = 1, and the algorithm will find x/y for the ax + by = 1
equation.
Let’s plug a = 3 and b = 216 (like if we finding magic constant for 16-bit CPU):
# include <assert .h>
# include <stdio .h>
# include <stdint .h>
while (r!=0)
{
int quotient =old_r /r;
tmp=r; r=old_r - quotient *r; old_r =tmp;
tmp=s; s=old_s - quotient *s; old_s =tmp;
tmp=t; t=old_t - quotient *t; old_t =tmp;
};
printf ("GCD: %d\n", old_r );
if ( old_r !=1)
{
printf ("%d and %d are not coprimes !\n", a, b);
return ;
};
printf (" Bézout coefficients : %d %d\n", old_s , old_t );
printf (" quotients by the GCD (s,t): %d %d\n", s, t);
void main ()
{
22
EGCD (3, 0 x10000 );
};
GCD: 1
Bézout coefficients : -21845 1
quotients by the GCD (s,t): 65536 -3
corrected coefficients : 43691(0 xaaab ) 2(0 x2)
push %ebp
mov %esp ,% ebp
mov 0x8 (% ebp) ,%eax
mov $0xcccccccd ,% edx
mul %edx
mov %edx ,% eax
shr $0x3 ,% eax
pop %ebp
ret
This is in fact the magic number for division by 5. And there is 3 instead of 2 in the SHR instruction, so the result is
divided by 2.
Extended Euclidean algorithm is probably an efficient way of finding magic number, but needless to say, this equation
can be solved using other ways. In “SAT/SMT by Example”3 you can find a method of finding ”magic number” using
SMT-solver.
3
https://yurichev.com/writings/SAT_SMT_by_example.pdf
23
24
Chapter 3
Probability
% wget https :// www. kernel .org/pub/ linux / kernel /v4.x/linux -4.10.2. tar.gz
% wget https :// cdn. kernel .org/pub/ linux / kernel /v2 .3/ linux -2.3.3. tar.bz2
One of Linux kernel patches in compressed form has the “Linux” word itself:
% wget https :// cdn. kernel .org/pub/ linux / kernel /v4.x/ testing /patch -4.6 - rc4.gz
Other English words I’ve found in other compressed Linux kernel trees:
25
linux -4.6.2. tar.gz: [maybe ] at 0 x68e78ec
linux -4.10.14. tar.xz: [ OCEAN ] at 0 x6bf0a8
linux -4.7.8. tar.gz: [FUNNY ] at 0 x29e6e20
linux -4.6.4. tar.gz: [DRINK ] at 0 x68dc314
linux -2.6.11.8. tar.bz2: [ LUCKY ] at 0 x1ab5be7
linux -3.0.68. tar.gz: [ BOOST ] at 0 x11238c7
linux -3.0.16. tar.bz2: [APPLE ] at 0 x34c091
linux -3.0.26. tar.xz: [ magic ] at 0 x296f7d9
linux -3.11.8. tar.bz2: [TRUTH ] at 0 xf635ba
linux -3.10.11. tar.bz2: [logic ] at 0 x4a7f794
There is a nice illustration of apophenia and pareidolia (human’s mind ability to see faces in clouds, etc) in Lurkmore,
Russian counterpart of Encyclopedia Dramatica. As they wrote in the article about electronic voice phenomenon1 ,
you can open any long enough compressed file in hex editor and find well-known 3-letter Russian obscene word, and
you’ll find it a lot: but that means nothing, just a mere coincidence.
And I was interested in calculation, how big compressed file must be to contain all possible 3-letter, 4-letter, etc, words?
In my naive calculations, I’ve got this: probability of the first specific byte in the middle of compressed data stream
1 1 1 1
with maximal entropy is 256 , probability of the 2nd is also 256 , and probability of specific byte pair is 256·256 = 2562.
1
Probabilty of specific triple is 256 3 . If the file has maximal entropy (which is almost unachievable, but …) and we live
in an ideal world, you’ve got to have a file of size just 2563 = 16777216, which is 16-17MB. You can check: get any
compressed file, and use rafind2 to search for any 3-letter word (not just that Russian obscene one).
It took ≈ 8-9 GB of my downloaded movies/TV series files to find the word “beer” in them (case sensitive). Perhaps,
these movies wasn’t compressed good enough? This is also true for a well-known 4-letter English obscene word.
My approach is naive, so I googled for mathematically grounded one, and have find this question: “Time until a con-
secutive sequence of ones in a random bit sequence” 2 . The answer is: (p−n −1)/(1−p), where p is probability of each
1
event and n is number of consecutive events. Plug 256 and 3 and you’ll get almost the same as my naive calculations.
So any 3-letter word can be found in the compressed file (with ideal entropy) of length 2563 =≈ 17M B, any 4-letter
word — 2564 = 4.7GB (size of DVD). Any 5-letter word — 2565 =≈ 1T B.
For the piece of text you are reading now, I mirrored the whole kernel.org website (hopefully, sysadmins can forgive
me), and it has ≈ 430GB of compressed Linux Kernel source trees. It has enough compressed data to contain these
words, however, I cheated a bit: I searched for both lowercase and uppercase strings, thus compressed data set I need
is almost halved.
This is quite interesting thing to think about: 1TB of compressed data with maximal entropy has all possible 5-byte
chains, but the data is encoded not in chains itself, but in the order of chains (no matter of compression algorithm,
etc).
Now the information for gamblers: one should throw a dice ≈ 42 times to get a pair of six, but no one will tell you,
when exactly this will happen. I don’t remember, how many times coin was tossed in the “Rosencrantz & Guildenstern
Are Dead” movie, but one should toss it ≈ 2048 times and at some point, you’ll get 10 heads in a row, and at some
other point, 10 tails in a row. Again, no one will tell you, when exactly this will happen.
Compressed data can also be treated as a stream of random data, so we can use the same mathematics to determine
probabilities, etc.
1
http://archive.is/gYnFL
2
http://math.stackexchange.com/questions/27989/time-until-a-consecutive-sequence-of-ones-in-a-random-bit-sequence/
27991#27991
26
If you can live with strings of mixed case, like “bEeR”, probabilities and compressed data sets are much lower: 1283 =
2M B for all 3-letter words of mixed case, 1284 = 268M B for all 4-letter words, 1285 = 34GB for all 5-letter words,
etc.
Moral of the story: whenever you search for some patterns, you can find it in the middle of compressed blob, but that
means nothing else then coincidence. In philosophical sense, this is a case of selection/confirmation bias: you find
what you search for in “The Library of Babel” 3 .
What are most chess moves played after 1.e4 e5 2.Nf3 Nf6? A big database of chess games can be queried, showing
statistics:
( from https://chess-db.com/ )
Statistics shown is just number of games, where a corresponding 3rd move was played.
The same database can be made for natural language words.
3
A short story by Jorge Luis Borges
27
# python 3! due to random.choices()
sentences =data.lower (). replace ('\r',' '). replace ('\n',' '). replace ('?','.').
replace ('!','.'). replace ('“','.'). replace ('”','.'). replace ("\"","."). replace (
'‘',' '). replace ('-',' '). replace ('’',' '). replace ('\'',' '). split (".")
for s in sentences :
words =s. replace (',',' '). split (" ")
words = remove_empty_words (words )
if len(words )==0:
continue
for i in range (len(words )):
if i >=1:
update_occ (first , words [i-1] , words [i])
if i >=2:
update_occ (second , words [i -2]+" "+words [i-1] , words [i])
"""
print (" first table :")
for k in first :
print (k)
# https :// stackoverflow .com/ questions /613183/ how -do -i-sort -a- dictionary -by -
value
s= sorted (first [k]. items () , key= operator . itemgetter (1) , reverse =True)
print (s [:20])
print ("")
"""
"""
print (" second table :")
28
for k in second :
print (k)
# https :// stackoverflow .com/ questions /613183/ how -do -i-sort -a- dictionary -by -
value
s= sorted ( second [k]. items () , key= operator . itemgetter (1) , reverse =True)
print (s [:20])
print ("")
"""
# https://docs.python.org/3/library/random.html#random.choice
def gen_random_from_tbl (t):
return random . choices (list(t.keys ()), weights =list(t. values ()))[0]
text_len =len(text)
last_idx =text_len -1
And here are some 1st-order Markov chains. First part is a first word. Second is a list of words + probabilities of appear-
ance of each one, in the Sherlock Holmes stories. However, probabilities are in form of words’ numbers.
In other word, how often each word appears after a word?
return
[('to ', 28) , ('or ', 12) , ('of ', 8) , ('the ', 8) , ('for ', 5) , ('with ', 4) , ('by ',
4) , ('journey ', 4), ('and ', 3) , ('when ', 2) , ('ticket ', 2) , ('from ', 2) , ('at
', 2) , ('you ', 2) , ('i', 2) , ('since ', 1) , ('on ', 1) , ('caused ', 1) , ('but ',
1) , ('it ', 1)]
of
[('the ', 3206) , ('a', 680) , ('his ', 584) , ('this ', 338) , ('it ', 304) , ('my ',
29
295) , ('course ', 225) , ('them ', 167) , ('that ', 138) , ('your ', 137) , ('our ',
135) , ('us ', 122) , ('her ', 116) , ('him ', 111) , ('an ', 105) , ('any ', 102) , ('
these ', 92) , ('which ', 89) , ('all ', 82) , ('those ', 75)]
by
[('the ', 532) , ('a', 164) , ('his ', 67) , ('my ', 39) , ('no ', 34) , ('this ', 31) , ('
an ', 30) , ('which ', 29) , ('your ', 21) , ('that ', 19) , ('one ', 17) , ('all ', 17)
, ('jove ', 16) , ('some ', 16) , ('sir ', 13) , ('its ', 13) , ('him ', 13) , ('their
', 13) , ('it ', 11) , ('her ', 10)]
this
[('is ', 177) , ('agreement ', 96) , ('morning ', 81) , ('was ', 61) , ('work ', 60) , ('
man ', 56) , ('case ', 43) , ('time ', 39) , ('matter ', 37) , ('ebook ', 36) , ('way ',
36) , ('fellow ', 28) , ('room ', 27) , ('letter ', 24) , ('one ', 24) , ('i', 23) ,
('young ', 21) , ('very ', 20) , ('project ', 18) , (' electronic ', 18)]
no
[('doubt ', 179) , ('one ', 134) , ('sir ', 61) , ('more ', 56) , ('i', 55) , ('no ', 42) ,
('other ', 36) , ('means ', 36) , ('sign ', 34) , ('harm ', 23) , ('reason ', 22) , ('
difficulty ', 22) , ('use ', 21) , ('idea ', 20) , ('longer ', 20) , ('signs ', 18) ,
('good ', 17) , ('great ', 16) , ('trace ', 15) , ('man ', 15)]
the return
[('of ', 8) , ('track ', 1) , ('to ', 1)]
return of
[(' sherlock ', 6) , ('lady ', 1) , ('the ', 1)]
for the
[('use ', 22) , ('first ', 12) , ('purpose ', 9) , ('sake ', 8) , ('rest ', 7) , ('best ',
7) , ('crime ', 6), ('evening ', 6) , ('ebooks ', 6) , ('limited ', 6) , ('moment ',
6) , ('day ', 5), ('future ', 5) , ('last ', 5) , ('world ', 5) , ('time ', 5) , ('loss
', 4) , ('second ', 4), ('night ', 4) , ('remainder ', 4)]
use of
[('the ', 15) , ('anyone ', 12) , ('it ', 6) , ('project ', 6) , ('and ', 6) , ('having ',
2) , ('this ', 1) , ('my ', 1) , ('a', 1) , ('horses ', 1) , ('some ', 1) , ('arguing ',
1), ('troubling ', 1), (' artificial ', 1) , ('invalids ', 1) , ('cocaine ', 1) , ('
disguises ', 1) , ('an ', 1) , ('our ', 1)]
you may
[('have ', 17) , ('copy ', 13) , ('be ', 13) , ('do ', 9) , ('choose ', 7) , ('use ', 6) ,
('obtain ', 6), ('convert ', 6) , ('charge ', 6) , ('demand ', 6) , ('remember ', 5) ,
('find ', 5) , ('take ', 4) , ('think ', 3) , ('possibly ', 3) , ('well ', 3) , ('know
', 3) , ('not ', 3) , ('say ', 3) , ('imagine ', 3)]
the three
[('of ', 8) , ('men ', 4) , ('students ', 2) , ('glasses ', 2) , ('which ', 1) , ('strips
30
', 1) , ('is ', 1), ('he ', 1) , ('gentlemen ', 1) , (' enterprising ', 1) , ('massive
', 1) , ('quarter ', 1), ('randalls ', 1) , ('fugitives ', 1) , ('animals ', 1) , ('
shrieked ', 1), ('other ', 1) , ('murderers ', 1) , ('fir ', 1) , ('at ', 1)]
it was
[('a', 179) , ('the ', 78) , ('not ', 68) , ('only ', 40) , ('in ', 30) , ('evident ', 28)
, ('that ', 28) , ('all ', 25) , ('to ', 21) , ('an ', 18) , ('my ', 17) , ('at ', 17) ,
('impossible ', 17) , ('indeed ', 15) , ('no ', 15) , ('quite ', 15) , ('he ', 14) , ('
of ', 14) , ('one ', 12) , ('on ', 12)]
Now two words is a key in dictionary. And we see here an answer for the question ”how often each words appears after
a sequence of two words?”
Now let’s generate some rubbish:
it is just to the face of granite still cutting the lower part of the shame
which i would draw up so as i can t have any visitors if he is passionately
fond of a dull wrack was drifting slowly in our skin before long vanished in
front of him of this arch rebel lines the path which i had never set foot in
the same with the heads of these have been supplemented or more waiting to
tell me all that my assistant hardly knowing whether i had 4 pounds a month
however is foreign to the other hand were most admirable but because i know
why he broke into a curve by the crew had thought it might have been on a
different type they were about to set like a plucked chicken s making the
case which may help to play it at the door and called for the convenience of
a boat sir maybe i could not be made to draw some just inference that a woman
exactly corresponding to the question was how a miners camp had been dashed
savagely against the rock in front of the will was drawn across the golden
rays and it
it is the letter was composed of a tall stout official had come the other way
that she had been made in this i expect that we had news that the office of
the mud settling or the most minute exactness and astuteness represented as i
come to see the advantages which london then and would i could not speak
before the public domain ebooks in compliance with any particular paper
edition as to those letters come so horribly to his feet upon the wet clayey
soil but since your wife and of such damage or cannot be long before the
rounds come again whenever she might be useful to him so now my fine fellow
will trouble us again and again and making a little wizened man darted out of
the baskervilles *** produced by roger squires updated editions will be the
matter and let out forty three diamonds of the attack we carried him into the
back and white feather were but a terrible fellow he is not for your share
in an instant holmes clapped his hands and play with me anyhow i d ha known
you under that name in a considerable treasure was hid for no other
it is the unofficial force the shutter open but so far as to what i say to me
like that which is rather too far from at ease for i knew that we were
driving ; but soon it came just as he opened it myself i was not a usual
signal between you and you thought it might be removed to a magistrate than
upon the luminous screen of the one word would he have wanted there are
business relations between him and we listened to his eyes to the spot as
31
soon as their master s affairs of life since she was but one day we hoped
would screen me from under his arm chair of shining red leather chair his
flask in his chair and gave me his name is sherlock holmes took each face of
a barmaid in bristol and marry her but learned from dr
it is selden the criminal or lunatic who had left a match might mar my career
had reached the tower of the crime was committed before twelve foolish
tradesmen in a bad lot though the authorities are excellent at amassing facts
though what its object might be a better nerve for the creature flapped and
struggled and writhed his hands in her bosom lady hilda was down on the table
and the curious will so suddenly upon the floor were now nearly five
thousand pounds will be impossible but i struck him down and roared into the
shadow of the thing seemed simplicity itself said i you seem most fortunate
in having every characteristic of the place is deserted up to keep some
clandestine appointment and found as i have no desire to settle this little
matter of fact of absolute ignorance and he with a similar pearl without any
light upon what he had found ourselves at the time and my heart sank for
barrymore at the close of november and holmes fears came to think over the
afghan campaign yet shown by this time at which he now and i have seen his
death this morning he walked straight into
By first look, these pieces of text are visually OK, but it is senseless. Some people (including me) find it amusing.
Spammers also use this technique to make email message visually similar to a meaningful text, albeit it is not mean-
ingful at all, rather absurdic and funny.
3.2.2 Autocomplete
It’s surprising how easy this can be turned into something rather practically useful.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import operator
from collections import defaultdict
sentences =data.lower (). replace ('\r',' '). replace ('\n',' '). replace ('?','.').
replace ('!','.'). replace ('“','.'). replace ('”','.'). replace ("\"","."). replace (
'‘',' '). replace ('-',' '). replace ('’',' '). replace ('\'',' '). split (".")
32
third ={}
for s in sentences :
words =s. replace (',',' '). split (" ")
words = remove_empty_words (words )
if len(words )==0:
continue
for i in range (len(words )):
# only two words available:
if i >=1:
update_occ (first , words [i-1] , words [i])
# three words available:
if i >=2:
update_occ (second , words [i -2]+" "+words [i-1] , words [i])
# four words available:
if i >=3:
update_occ (third , words [i -3]+" "+words [i -2]+" "+words [i-1] , words [i
])
"""
print (" third table :")
for k in third :
print (k)
# https :// stackoverflow .com/ questions /613183/ how -do -i-sort -a- dictionary -by -
value
s= sorted (third [k]. items () , key= operator . itemgetter (1) , reverse =True)
print (s [:20])
print ("")
"""
33
test_words =test. split (" ")
# https://stackoverflow.com/questions/613183/how-do-i-sort-a-dictionary-by-value
s= sorted (t.items () , key= operator . itemgetter (1) , reverse =True)
# take 5 from each sorted table
for pair in s[:5]:
print ("%s %d%%" % (pair [0] , ( float (pair [1])/ total ) *100) )
if test_len >=3:
tmp= test_words [last_idx -2]+" "+ test_words [last_idx -1]+" "+ test_words [
last_idx ]
if tmp in third :
print ("* third order . for sequence :",tmp)
print_stat (third [tmp ])
if test_len >=2:
tmp= test_words [last_idx -1]+" "+ test_words [ last_idx ]
if tmp in second :
print ("* second order . for sequence :", tmp)
print_stat ( second [tmp ])
if test_len >=1:
tmp= test_words [ last_idx ]
if tmp in first :
print ("* first order . for word:", tmp)
print_stat (first [tmp ])
print ("")
First, let’s also make 3rd-order Markov chains tables. There are some snippets from it:
the return of
[(' sherlock ', 6) , ('lady ', 1) , ('the ', 1)]
the use of
[('the ', 13) , ('anyone ', 12) , ('project ', 6) , ('having ', 2) , ('a', 1) , ('horses
', 1) , ('some ', 1) , ('troubling ', 1) , (' artificial ', 1) , ('invalids ', 1) , ('
disguises ', 1) , ('it ', 1)]
of the second
[('stain ', 5), ('floor ', 3) , ('page ', 1) , ('there ', 1) , ('person ', 1) , ('party ',
1), ('day ', 1)]
it was in
34
[('the ', 9), ('vain ', 5), ('a', 4) , ('his ', 2) , ('83', 1) , ('one ', 1) , ('motion
', 1) , ('truth ', 1) , ('my ', 1) , ('this ', 1) , ('march ', 1) , ('january ', 1) , ('
june ', 1), ('me ', 1)]
was in the
[('room ', 3), ('habit ', 3) , ('house ', 2) , ('last ', 2) , ('loft ', 2) , ('spring ',
1) , ('train ', 1), ('bar ', 1) , ('power ', 1) , ('immediate ', 1) , ('year ', 1) , ('
midst ', 1), ('forenoon ', 1) , ('centre ', 1) , ('papers ', 1) , ('best ', 1) , ('
darkest ', 1), ('prime ', 1) , ('hospital ', 1) , ('nursery ', 1)]
murder of the
[(' honourable ', 1), ('discoverer ', 1)]
particulars of the
[('crime ', 1), ('inquest ', 1) , ('habits ', 1), ('voyage ', 1)]
the death of
[('the ', 8), ('sir ', 8) , ('captain ', 3) , ('their ', 2) , ('this ', 2) , ('his ', 2) ,
('her ', 2), ('dr ', 2) , ('sherlock ', 1) , ('mrs ', 1) , ('a', 1) , ('van ', 1) , ('
that ', 1), ('drebber ', 1) , ('mr ', 1) , (' stangerson ', 1) , ('two ', 1) , ('selden
', 1) , ('one ', 1) , (' wallenstein ', 1)]
one of the
[('most ', 23) , ('best ', 3) , ('main ', 3) , ('oldest ', 2) , ('greatest ', 2) , ('
numerous ', 2), ('corners ', 2) , ('largest ', 2) , ('papers ', 2) , ('richest ', 2) ,
('servants ', 2) , ('moor ', 2) , ('finest ', 2) , ('upper ', 2) , ('very ', 2) , ('
broad ', 2), ('side ', 2) , ('highest ', 2) , (' australian ', 1) , ('great ', 1)]
so far as
[('i', 17) , ('to ', 8), ('that ', 2) , ('the ', 2) , ('it ', 2) , ('was ', 1) , ('we ', 1)
, ('they ', 1) , ('he ', 1) , ('you ', 1) , ('a', 1) , ('your ', 1) , ('this ', 1) , ('
his ', 1) , ('she ', 1)]
You see, they looks as more precise, but tables are just smaller. You can’t use them to generate rubbish. 1st-order
tables big, but ”less precise”.
And here I test some 3-words queries, like as if they inputted by user:
"i can tell"
35
you 35%
me 25%
us 6%
him 5%
the 4%
36
* third order. for sequence : i did not
know 22%
say 9%
even 3%
tell 3%
mind 3%
* second order . for sequence : did not
know 11%
take 3%
wish 3%
go 2%
say 2%
* first order. for word: not
a 4%
be 4%
have 3%
been 3%
to 2%
37
* first order. for word: been
a 5%
in 4%
the 2%
so 2%
taken 1%
38
the 4%
and 3%
* first order. for word: do
you 24%
not 20%
with 6%
so 4%
it 4%
"it is just"
39
"you shall "
Perhaps, results from all 3 tables can be combined, with the data from 3rd order table used in highest priority (or
weight).
And this is it — this can be shown to user. Aside of Conan Doyle works, your software can collect user’s input to adapt
itself for user’s lexicon, slang, memes, etc. Of course, user’s ”tables” should be used with highest priority.
I have no idea, what Apple/Android devices using for hints generation, when user input text, but this is what I would
use as a first idea.
As a bonus, this can be used for language learners, to get the idea, how a word is used in a sentence.
40
3.2.4 The files
... including Conan Doyle’s stories (2.5M). https://github.com/DennisYurichev/Math-for-programmers/tree/
master/prob/markov. Surely, any other texts can be used, in any language...
Another related post is about typos: https://yurichev.com/blog/fuzzy_string/.
”1” is generated in 25% of cases, ”2” in 60% and ”3” in 15%. Well, almost.
Here is another use of it.
You know, when you send an email, the final destination is a server somewhere. But it may be irresponsible. So
network engineers add additional servers, ”relays”, which can hold your email for some time.
For example, what is about gmail.com?
% dig gmail.com MX
...
;; ANSWER SECTION :
gmail .com. 3600 IN MX 5 gmail -smtp -in.l. google .com.
4
https://docs.python.org/3/library/random.html#random.choices
41
gmail .com. 3600 IN MX 10 alt1.gmail -smtp -in.l. google .
com.
gmail .com. 3600 IN MX 20 alt2.gmail -smtp -in.l. google .
com.
gmail .com. 3600 IN MX 30 alt3.gmail -smtp -in.l. google .
com.
gmail .com. 3600 IN MX 40 alt4.gmail -smtp -in.l. google .
com.
...
The first server is primary (marked with 5). Other 4 (alt...) are relays. They can hold emails for [email protected] if the
main server is down. Of course, relays also can be down. So an MTA (Message transfer agent) tries to send an email
via the first server in list, then via the second, etc. If all are down, MTA is waiting for some time (not infinitely).
( https://tools.ietf.org/html/rfc5321 )
Now if you want your MTA be polite, you can make it poke relays with some probability, unloading the main mail server.
In any case, the internal network withing Google is way better than a link between you and any of these mail servers.
And it would be OK to drop an email to any of these mail servers listed in MX records.
I’m using reciprocal weights (1/x) because the lower priority, the higher probability it is to be chosen.
>>> [ random . choices ( range (4) , weights =[1/5 , 1/10 , 1/20 , 1/40]) [0] for x in range
(100) ]
[1, 1, 2, 1, 0, 2, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 1, 1, 1, 0, 1, 0, 2, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 3, 0, 0,
2, 1, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
1, 0, 2, 1, 0, 0, 2, 0, 0, 0, 3, 2, 0, 1, 2, 0, 1, 1, 3, 1, 1, 1, 1]
42
1000? (I’m using the collections.Counter5 here for gathering statistics).
>>> Counter ([ random . choices ( range (4) , weights =[1/5 , 1/10 , 1/20 , 1/40]) [0] for x
in range (1000) ])
535 emails will be sent via the first (primary) mail server, 268/129/68 – via corresponding relays.
This is probably not how MTAs usually operates, but this is how it could be done.
5
https://docs.python.org/3/library/collections.html#collections.Counter
43
44
Chapter 4
Combinatorics
(Just 6 ways.)
What if there are 4 wires?
import itertools
45
('red ', 'yellow ', 'green ', 'blue ')
('red ', 'yellow ', 'blue ', 'green ')
('green ', 'red ', 'blue ', 'yellow ')
('green ', 'red ', 'yellow ', 'blue ')
('green ', 'blue ', 'red ', 'yellow ')
('green ', 'blue ', 'yellow ', 'red ')
('green ', 'yellow ', 'red ', 'blue ')
('green ', 'yellow ', 'blue ', 'red ')
('blue ', 'red ', 'green ', 'yellow ')
('blue ', 'red ', 'yellow ', 'green ')
('blue ', 'green ', 'red ', 'yellow ')
('blue ', 'green ', 'yellow ', 'red ')
('blue ', 'yellow ', 'red ', 'green ')
('blue ', 'yellow ', 'green ', 'red ')
('yellow ', 'red ', 'green ', 'blue ')
('yellow ', 'red ', 'blue ', 'green ')
('yellow ', 'green ', 'red ', 'blue ')
('yellow ', 'green ', 'blue ', 'red ')
('yellow ', 'blue ', 'red ', 'green ')
('yellow ', 'blue ', 'green ', 'red ')
(24 ways.)
This is what is called permutation in combinatorics.
There was 13: 1 and 3 together , I'm sure , but not sure where , 13 as first 2
digits , last 2 digits or in the middle .
And there was also 6, or maybe 8, or maybe even 9, not sure which , but one of
them.
Combinatorics textbooks are abound with exercises like this: can you enumerate all possible 4-digit numbers con-
strained in this way?
import itertools
part1_list =["13"]
part2_list =["6" , "8" , "9"]
part3_list =["0" , "1" , "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9"]
46
1360
1306
6130
6013
...
9139
9913
9139
9913
( 180 numbers )
This is something you can query registered vehicle database with...
part1_list =["jake", " melissa ", " oliver ", "emily "]
part2_list =["1987", "1954", "1963"]
part3_list =["!","@","#","$","%","&","*","-","=","_","+",".",","]
jake1987 !
jake !1987
1987 jake!
1987! jake
! jake1987
...
1963 emily ,
1963 , emily
,emily1963
,1963 emily
47
( 936 of them in total )
But nested for’s are not aesthetically pleasing. They can be replaced with ”cartesian product” operation:
import itertools
part1_list =["jake", " melissa ", " oliver ", "emily "]
part2_list =["1987", "1954", "1963"]
part3_list =["!","@","#","$","%","&","*","-","=","_","+",".",","]
And this is a way to memorize it: the length of the final result equals to lengths of all input lists multiplied with each
other (like ”product”).
import itertools
part1_list =["jake", " melissa ", " oliver ", "emily "] # 4 elements
part2_list =["1987", "1954", "1963"] # 3 elements
part3_list =["!","@","#","$","%","&","*","-","=","_","+",".",","] # 13 elements
...
48
part1_list =["jake", " melissa ", " oliver ", "emily "]
part2_list =["1987", "1954", "1963"]
part3_list =["!","@","#","$","%","&","*","-","=","_","+",".",","]
JAke1987 !
JAkE1987 !
JAKe1987 !
JAKE1987 !
jake !1987
jakE !1987
jaKe !1987
jaKE !1987
...
,1963 eMIly
,1963 eMIlY
,1963 eMILy
,1963 eMILY
,1963 Emily
,1963 EmilY
,1963 EmiLy
( 56160 passwords )
Now leetspeak1 This is somewhat popular only among youngsters, but still, this is what people of all age groups do:
replacing ”o” with ”0” in their passwords, ”e” with ”3”, etc. Let’s add this as well:
import itertools , string
part1_list =["jake", " melissa ", " oliver ", "emily "]
part2_list =["1987", "1954", "1963"]
part3_list =["!","@","#","$","%","&","*","-","=","_","+",".",","]
49
s="".join(i)
t=[]
for char in s:
if char. isalpha ():
to_be_appended =[ string .lower (char), string .upper (char)]
if char. lower () == 'e':
to_be_appended . append ('3')
elif char. lower () == 'i':
to_be_appended . append ('1')
elif char. lower () == 'o':
to_be_appended . append ('0')
t. append ( to_be_appended )
else:
t. append ([ char ])
for q in itertools . product (*t):
print "".join(q)
jake1987 !
jakE1987 !
jak31987 !
jaKe1987 !
jaKE1987 !
jaK31987 !
...
,1963 EM1lY
,1963 EM1Ly
,1963 EM1LY
,19633 mily
,19633 milY
,19633 miLy
( 140400 passwords )
Obviously, you can’t try all 140400 passwords on Facebook, Twitter or any other well-protected internet service. But
this is a peace of cake to brute-force them all on password protected RAR-archive or feed them all to John the Ripper,
HashCat, etc.
All the files: https://github.com/DennisYurichev/Math-for-programmers/tree/master/comb/password.
import itertools
50
parts =[" den", "xenia", "1979" , "1985" , " secret ", "!" , "$", "^"]
Here we enumerate all combinations of given strings, 1-string combinations, then 2-, up to 5-string combinations. No
string can appear twice.
...
denxenia1979
denxenia1985
denxeniasecret
denxenia !
denxenia$
denxenia ^
den19791985
den1979secret
den1979 !
...
xenia1985secret$ ^
xenia1985 !$^
xeniasecret !$^
19791985 secret !$
19791985 secret !^
...
(218 passwords)
Now let’s permute all string in all possible ways:
import itertools
parts =[" den", "xenia", "1979" , "1985" , " secret ", "!" , "$", "^"]
...
^den
xenia1979
1979 xenia
xenia1985
1985 xenia
xeniasecret
51
secretxenia
xenia !
!xenia
...
^! $1985secret
^! $secret1985
^ $1985secret !
^$1985 ! secret
^ $secret1985 !
^ $secret !1985
^$!1985 secret
^$! secret1985
(8800 passwords)
And finally, let’s alter all Latin characters in lower/uppercase ways and add leetspeek, as I did before:
...
dEnxenia
dEnxeniA
dEnxenIa
...
D3nx3N1a
D3nx3N1A
52
D3nXenia
D3nXeniA
D3nXenIa
...
^$1979 !1985
^ $19851979 !
^$1985 !1979
^$ !19791985
^$ !19851979
( 1,348,657 passwords )
Again, you can’t try to crack remote server with so many attempts, but this is really possible for password-protected
archive, known hash, etc...
53
.rdata :0040 D5A2 align 8
.rdata :0040 D5A8 aR6018Unexpecte :
.rdata :0040 D5A8 text "UTF -16 LE", 'R6018 ',0Dh ,0 Ah
.rdata :0040 D5A8 text "UTF -16 LE", '- unexpected heap error ',0Dh ,0
Ah ,0
.rdata :0040 D5EA align 10h
Can we hide some information there? Not in string themselves, but in order of strings? Given the fact, that compiler
doesn’t guarantee at all, in which order the strings will be stored in object/executable file.
Let’s say, we’ve got 26 text strings, and we can swap them how we want, because their order isn’t important at all. All
possible permutations of 26 objects is 26! = 403291461126605635584000000.
How much information can be stored here? log2(26!)= 88, i.e., 88 bits or 11 bytes!
11 bytes of your data can be converted to a (big) number and back, OK.
What is next? Naive way is: enumerate all permutations of 26 objects, number each, find permutation of the number
we’ve got and permute 26 text strings, store to file and that’s it. But we can’t iterate over 403291461126605635584000000
permutations.
This is where factorial number system 2 and Lehmer code 3 can be used. In short, for all my non-mathematically
inclined readers, this is a way to find a permutation of specific number without use of any significant resources. And
back: gived specific permutation, we can find its number.
This piece of code I’ve copypasted from https://gist.github.com/lukmdo/7049748 and reworked slightly:
# python 3.x
import math
54
return num
return code
perm = base.copy ()
for i in range (len(base) - 1):
j = code[i]
if i != i+j:
print (" swapping %d, %d" % (i, i+j))
perm[i], perm[i+j] = perm[i+j], perm[i]
return perm
p = base.copy ()
55
n = len(base)
pos_map = {v: i for i, v in enumerate (base)}
w = []
for i in range (n):
d = pos_map [perm[i]] - i
w. append (d)
if not d:
continue
t = pos_map [perm[i]]
pos_map [p[i]], pos_map [p[t]] = pos_map [p[t]], pos_map [p[i]]
p[i], p[t] = p[t], p[i]
return w
num2= int_from_perm (list( range (26)), [14 , 17, 9, 19, 11, 16, 23, 0, 2, 13, 20,
56
18, 21, 24, 10, 1, 22, 4, 7, 6, 15, 12, 5, 3, 8, 25])
print (" recovered num=", num2)
s2= number_to_bin_string (num2)
print (" recovered s=", s2)
I’m encoding a ”HelloWorld” binary string (in fact, any 11 bytes can be used) into a number. Number is then converted
into Lehmer code. Then the perm_from_code() function permute initial order according to the input Lehmer code:
s= HelloWorld
num= 341881320659934023674980
Lehmer code= [14, 16, 7, 16, 7, 11, 17, 7, 1, 4, 10, 7, 9, 11, 6, 2, 6, 1, 2, 4,
0, 0, 0, 0, 0, 0]
swapping 0, 14
swapping 1, 17
swapping 2, 9
swapping 3, 19
swapping 4, 11
swapping 5, 16
swapping 6, 23
swapping 7, 14
swapping 8, 9
swapping 9, 13
swapping 10, 20
swapping 11, 18
swapping 12, 21
swapping 13, 24
swapping 14, 20
swapping 15, 17
swapping 16, 22
swapping 17, 18
swapping 18, 20
swapping 19, 23
permutation / order= [14, 17, 9, 19, 11, 16, 23, 0, 2, 13, 20, 18, 21, 24, 10, 1,
22, 4, 7, 6, 15, 12, 5, 3, 8, 25]
This is it: [14, 17, 9, 19, 11, 16, 23, 0, 2, 13, 20, 18, 21, 24, 10, 1, 22, 4, 7, 6, 15, 12, 5, 3, 8, 25]. First put 14th string, then 17s
string, then 9th one, etc.
Now you’ve got a binary file from someone and want to read watermark from it. Get an order of strings from it and
convert it back to binary string:
If you have more text strings (not unusual for most executable files), you can encode more.
100 strings: log2(100!) = 524 bits = 65 bytes.
1000 strings: log2(1000!) = 8529 bits = 1066 bytes! You can store some text here!
57
How would you force a C/C++ compiler to make specific order of text strings? This is crude, but workable:
The result has exactly 100 digits, which is 2 times less than our initial idea can offer. By scanning visually this 100-digits
array, you’ll find any number in 00..99 range. All numbers are overlapped with each other: second half of each number
is also first half of the next number, etc.
58
Here is another. We need a sequence of binary bits with all 3-bit numbers in it:
Sequence length is just 8 bits, but it has all binary numbers in 000..111 range. You may visually spot 000 in the middle
of sequence. 111 is also present: two first bits of it at the end of sequence and the last bit is in the beginning. This is so
because De Bruijn sequences are cyclic.
There is also visual demonstration: http://demonstrations.wolfram.com/DeBruijnSequences/.
The symbols of a De Bruijn sequence written around a circular object (such as a wheel of a robot)
can be used to identify its angle by examining the n consecutive symbols facing a fixed point.
Indeed: if we know De Bruijn sequence and we observe only part of it (any part), we can deduce exact position of this
part within sequence.
Let’s see, how this feature can be used.
Let’s say, there is a need to detect position of input bit within 32-bit word. For 0x1, the algorithm should report 1. 2 for
0x2. 3 for 0x4. And 31 for 0x80000000.
The result is in 0..31 range, so the result can be stored in 5 bits.
We can construct binary De Bruijn sequence for all 5-bit numbers:
Let’s also recall that division some number by 2n number is the same thing as shifting it by n bits. So if you divide
0xe6bec520 by 1, the result is not shifted, it is still the same. If if divide 0xe6bec520 by 4 (22 ), the result is shifted by 2
bits. We then take result and isolate lowest 5 bits. This result is unique number for each input. Let’s shift 0xe6bec520
by all possible count number, and we’ll get all possible last 5-bit values:
In []:= Table[ BitAnd [ BitShiftRight [ FromDigits [tmp , 2], i], 31] , {i, 0, 31}]
Out []= {0, 16, 8, 4, 18, 9, 20, 10, 5, 2, 17, 24, 12, 22, 27, 29, \
30, 31, 15, 23, 11, 21, 26, 13, 6, 19, 25, 28, 14, 7, 3, 1}
59
In []:= DuplicateFreeQ [%]
Out []= True
Using this table, it’s easy to build a magic table. OK, now working C example:
int main ()
{
// construct magic table
// may be omitted in production code
for (int i=0; i <32; i++)
magic_tbl [(0 xe6bec520 /(1<<i)) & 0x1F ]=i;
// test
for (int i=0; i <32; i++)
{
printf ("input =0x%x, result =%d\n", 1<<i, bitpos (1<<i));
};
};
Here we feed our bitpos() function with numbers in 0..0x80000000 range and we got:
60
input =0 x4000 , result =14
input =0 x8000 , result =15
input =0 x10000 , result =16
input =0 x20000 , result =17
input =0 x40000 , result =18
input =0 x80000 , result =19
input =0 x100000 , result =20
input =0 x200000 , result =21
input =0 x400000 , result =22
input =0 x800000 , result =23
input =0 x1000000 , result =24
input =0 x2000000 , result =25
input =0 x4000000 , result =26
input =0 x8000000 , result =27
input =0 x10000000 , result =28
input =0 x20000000 , result =29
input =0 x40000000 , result =30
input =0 x80000000 , result =31
The bitpos() function actually counts trailing zero bits, but it works only for input values where only one bit is set. To
make it more practical, we need to devise a method to drop all leading bits except of the last one. This method is very
simple and well-known:
This bit twiddling hack can solve the job. Feeding 0x11 to it, it will return 0x1. Feeding 0xFFFF0000, it will return
0x10000. In other words, it leaves lowest significant bit of the value, dropping all others.
It works because negated value in two’s complement environment is the value with all bits flipped but also 1 added
(because there is a zero in the middle of ring). For example, let’s take 0xF0. -0xF0 is 0x10 or 0xFFFFFF10. ANDing 0xF0
and 0xFFFFFF10 will produce 0x10.
Let’s modify our algorithm to support true trailing zero bits count:
int main ()
{
// construct magic table
61
// may be omitted in production code
for (int i=0; i <32; i++)
magic_tbl [(0 xe6bec520 /(1<<i)) & 0x1F ]=i;
// test:
printf ("%d\n", tzcnt (0 xFFFF0000 ));
printf ("%d\n", tzcnt (0 xFFFF0010 ));
};
It works!
16
4
But it has one drawback: it uses division, which is slow. Can we just multiplicate De Bruijn sequence by the value with
the bit isolated instead of dividing sequence? Yes, indeed. Let’s check in Mathematica:
The result is just too big to fit in 32-bit register, but can be used. MUL/IMUL instruction 32-bit x86 CPUs stores 64-bit
result into two 32-bit registers pair, yes. But let’s suppose we would like to make portable code which will work on any
32-bit architecture. First, let’s again take a look on De Bruijn sequence Mathematica first produced:
There is exactly 5 bits at the end which can be dropped. The ”magic” constant will be much smaller:
The ”magic” constant is now ”divided by 32 (or 1»5)”. This mean that the result of multiplication of some value with
one isolated bit by new magic number will also be smaller, so the bits we need will be stored at the high 5 bits of the
result.
De Bruijn sequence is not broken after 5 lowest bits dropped, because these zero bits are ”relocated” to the start of
the sequence. Sequence is cyclic after all.
62
// not working for i==0
int tzcnt ( uint32_t i)
{
uint32_t a=i & (-i);
// 5 bits we need are stored in 31..27 bits of product, shift and isolate them
after multiplication:
return magic_tbl [((0 x735f629 *a) >>27) & 0x1F ];
};
int main ()
{
// construct magic table
// may be omitted in production code
for (int i=0; i <32; i++)
magic_tbl [(0 x735f629 <<i >>27) & 0x1F ]=i;
// test:
printf ("%d\n", tzcnt (0 xFFFF0000 ));
printf ("%d\n", tzcnt (0 xFFFF0010 ));
};
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
For example, 0x100 becomes 0x1ff, 0x1000 becomes 0x1fff, 0x20000 becomes 0x3ffff, 0x12340000 becomes 0x1fffffff.
It works because all 1 bits are gradually propagated towards the lowest bit in 32-bit number, while zero bits at the left
of most significant 1 bit are not touched.
It’s possible to add 1 to resulting number, so it will becomes 0x2000 or 0x20000000, but in fact, since multiplication
by magic number is used, these numbers are very close to each other, so there are no error.
This example I used in my reverse engineering exercise from 15-Aug-2015: https://yurichev.com/blog/2015-aug-18/.
int v [64]=
{ -1,31, 8,30, -1, 7,-1,-1, 29,-1,26, 6, -1,-1, 2,-1,
-1,28,-1,-1, -1,19,25,-1, 5,-1,17,-1, 23 ,14 , 1,-1,
9,-1,-1,-1, 27,-1, 3,-1, -1,-1,20,-1, 18 ,24 ,15 ,10 ,
-1,-1, 4,-1, 21, -1 ,16 ,11 , -1,22,-1,12, 13,-1, 0,-1 };
63
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x *= 0 x4badf0d ;
return v[x >> 26];
}
This piece of code I took from here. It is slightly different: the table is twice bigger, and the function returns -1 if input
value is zero. The magic number I found using just brute-force, so the readers will not be able to google it, for the sake
of exercise. (By the way, I’ve got 12,665,720 magic numbers which can serve this purpose. This is about 0.294
The code is tricky after all, and the moral of the exercise is that practicing reverse engineer sometimes may just observe
input/outputs to understand code’s behaviour instead of diving into it.
4.5.4 Performance
The algorithms considered are probably fastest known, they has no conditional jumps, which is very good for CPUs
starting at RISCs. Newer CPUs has LZCNT and TZCNT instructions, even 80386 had BSF/BSR instructions which can
be used for this: https://en.wikipedia.org/wiki/Find_first_set. Nevertheless, these algorithms can be still
used on cheaper RISC CPUs without specialized instructions.
4.5.5 Applications
Number of leading zero bits is binary logarithm of value: 6.
These algorithms are also extensively used in chess engines programming, where each piece is represented as 64-bit
bitmask (chess board has 64 squares): http://chessprogramming.wikispaces.com/BitScan.
There are more: https://en.wikipedia.org/wiki/Find_first_set#Applications.
64
Chapter 5
This is what is called “avalanche effect” in cryptography: one bit flip of input can affect many bits of output. Go figure
out which bits must be also flipped to preserve specific remainder.
65
You can build such a divisor in hardware, but it would require at least one adder or subtractor, you will have a carry-
ripple problem in simple case, or you would have to create more complicated circuit.
if ( denom == tmp)
return 1;
// align divisor:
while (denom <= tmp)
{
denom = denom << 1;
current = current << 1;
}
66
tmp -= denom ;
answer |= current ;
}
current = current >> 1;
denom = denom >> 1;
}
printf ("tmp/ remainder =%d\n", tmp); // remainder!
return answer ;
}
( https://github.com/DennisYurichev/Math-for-programmers/blob/master/GF2/div.c )
Let’s divide 1234567 by 813 and find remainder:
uint8_t *buf;
int buf_pos ;
int buf_bit_pos ;
int get_bit ()
{
if ( buf_pos == -1)
return -1; // end
67
buf_bit_pos =7;
}
else
buf_bit_pos --;
return rt;
};
for (;;)
{
int bit= get_bit ();
if (bit == -1)
{
printf ("exit. remainder =%d\n", tmp);
return tmp;
};
tmp=tmp <<1;
tmp=tmp|bit;
( https://github.com/DennisYurichev/Math-for-programmers/blob/master/GF2/div_both.c )
Let’s divide 1234567 by 813 and find remainder:
tmp =0, can 't subtract
tmp =0, can 't subtract
tmp =0, can 't subtract
tmp =0, can 't subtract
tmp =0, can 't subtract
tmp =0, can 't subtract
tmp =0, can 't subtract
tmp =0, can 't subtract
tmp =0, can 't subtract
68
tmp =0, can 't subtract
tmp =0, can 't subtract
tmp =1, can 't subtract
tmp =2, can 't subtract
tmp =4, can 't subtract
tmp =9, can 't subtract
tmp =18, can 't subtract
tmp =37, can 't subtract
tmp =75, can 't subtract
tmp =150 , can 't subtract
tmp =301 , can 't subtract
tmp =602 , can 't subtract
1205 greater or equal to 813
new tmp =392
tmp =785 , can 't subtract
1570 greater or equal to 813
new tmp =757
1515 greater or equal to 813
new tmp =702
1404 greater or equal to 813
new tmp =591
1182 greater or equal to 813
new tmp =369
tmp =738 , can 't subtract
1476 greater or equal to 813
new tmp =663
1327 greater or equal to 813
new tmp =514
1029 greater or equal to 813
new tmp =216
tmp =433 , can 't subtract
exit. remainder =433
69
It’s important to say that unlike in algebra, GF(2) polynomials are never evaluated here. x is symbol is present mereley
as a convention. People represent GF(2) ”structures” as polynomials to emphasize the fact that ”numbers” are isolated
from each other.
Now, subtraction and addition are the same operations in GF(2) and actually works as XOR. This is present in many
tutorials, so I’ll omit this here.
Also, by convention, whenever you compare two numbers in GF(2), you only compare two most significant bits, and
ignore the rest.
5.6 CRC32
Now we can take the binary division algorithm and change it a little:
70
// so we have to only check shifted_bit value
if ( shifted_bit )
{
// use only 32 bits of polynomial, ingore 33th bit, which is
always 1:
tmp=tmp^ divisor ;
};
};
// bit shuffling/negation for compatibility once again:
return ∼bitrev32 (tmp);
}
( https://github.com/DennisYurichev/Math-for-programmers/blob/master/GF2/div_both.c )
And voila, this is the function which computes CRC32 for the input 32-bit value.
• Only MSB is checked during comparison. But the MSB of all CRC polynomials is always 1, so we only need to
check MSB (33th bit) of the tmp variable.
• There are 32+32=64 iterations instead of 32. As you can see, only MSB of tmp affects the whole behaviour of the
algorithm. So when tmp variable is filled by 32 bits which never affected anything so far, we need to ”blow out”
all these bits through 33th bit of tmp variable to get correct remainder (or CRC32 sum).
All the rest algorithms you can find on the Internet are optimized version, which may be harder to understand. No
algorithms used in practice “blows” anything “out” due to optimization. Many practical algorithms are either bytewise
(process input stream by bytes, not by bits) or table-based.
My goal was to write two functions, as similar to each other as possible, to demonstrate the difference.
So the CRC value is in fact remainder of division of input date by CRC polynomial in GF(2) environment. As simple as
that.
5.7 Rationale
Why use such an unusual mathematics? The answer is: many GF(2) operations can be done using bit shifts and XOR,
which are very cheap operations.
Electronic circuit for CRC generator is extremely simple, it consists of only shift register and XOR gates. This one is for
CRC16:
71
Figure 5.1:
72
Chapter 6
Logarithms
6.1 Introduction
6.1.1 Children’s approach
When children argue about how big their favorite numbers are, they speaking about how many zeroes it has: “x has
n zeroes!” “No, my y is bigger, it has m > n zeroes!”
This is exactly notion of common (base 10) logarithm.
Googol (10100 ) has 100 zeroes, so log10 (googol) = 100.
Let’s take some big number, like 12th Mersenne prime:
Wow, it’s so big. How can we measure it in childish terms? How many digits it has? We can count using common (base
10) logarithm:
So it has 39 digits.
Another question, how may decimal digits 1024-bit RSA key has?
73
5208500576883815068234246288147391311054082723716335051068458629823994\
7245938479716304835356329624224137216
For example:
It was common for scientific handheld calculators to use the first 10 digits of significand and ignore everything behind.
Storing the whole number down to the last digit is 1) very expensive; 2) hardly useful.
The number in IEEE 754 format (most popular way of representing real numbers in computers) has these three parts,
however, it has different base (2 instead of 10).
74
Another logarithmic scale familiar to anyone is decibel. Even on cheap mp3 players and smartphones, where the
volume is measured in conventional percents, this scale is logarithmic, and the difference between 50% and 60% may
be much larger in sound pressure terms.
Yet another familiar to anyone logarithmic scale is Richter magnitude scale 1 . The Richter scale is practical, because
when people talk about earthquakes, they are not interesting in exact scientific values (in Joules or TNT equivalent),
they are interesting in how bad damage is.
Figure 6.2: Frequency response (also known as Bode plot) of some loudspeaker
Both axis on this plot are logarithmic: y axis is loudness in decibel and x axis is frequency in Hertz. Needless to say,
the typical loudspeaker has bass/medium speaker + tweeter (high frequency speaker). Some of more advanced loud-
speaker has 3 speakers: bass, medium and tweeter. Or even more. And since the plot is logarithmic, each of these
2 or 3 speakers has their own part of plot, and these parts has comparable size. If the x axis would be linear instead
of logarithmic, the main part of it would be occupied by frequency response of tweeter alone, because it has widest
frequency range. While bass speaker has narrowest frequency range, it would have very thin part of the plot.
y axis (vertical) of the plot is also logarithmic (its value is shown in decibels). If this axis would be linear, the main part
of it would be occupied by very loud levels of sound, while there would be thinnest line at the bottom reserved for
normal and quiet level of sounds.
Both of that would make plot unusable and impractical. So both axis has logarithmic scale. In strict mathematics
terms, the plot shown is called log-log plot, which means that both axis has logarithmic scale.
1
https://en.wikipedia.org/wiki/Richter_magnitude_scale
75
Summarizing, both electronics engineers and HiFi audio enthusiasts use these plots to compare quality of speakers.
These plots are often used in loudspeakers reviews 2 .
6.2.3 In IT
git, like any other VCS, can show a graph, how many changes each file got in each commit, for example:
$ git log --stat
...
commit 2 fb3437fa753d59ba37f3d11c7253583d4b87c99
Author : Dennis Yurichev <dennis@yurichev .com >
Date: Wed Nov 19 14:14:07 2014 +0200
...
This scale is not logarithmical (I had a look into git internals), but this is exact place where logarithmical scale can be
used. When software developer got such report, he/she don’t interesting in exact numbers of lines changed/added/re-
moved. He/she wants to see an outlook: which files got most changes/additions/removals, and which got less.
There is also a constraint: the space on the terminal is limited, so it’s not possible to draw a minus or plus sign for each
changed line of code.
2
Some of speakers of USSR era (like Latvian Radiotehnika S-30 and S-90) had such plots right on the surface of speaker box, presumably, for
marketing purposes.
76
Another example is Bitcoin client “signal reception strength”, apparently, modeled after mobile phone signal indicator:
These bars indicating, how many connections client currently has. Let’s imagine, client can support up to 1000 con-
nections, but user is never interesting in precise number, all he/she wants to know is how good its link with Bitcoin
network is. I don’t know how Bitcoin calculates this, but I think one bar could indicate that client has only 1 connection,
two bars — 2-5, three bars — up to 10-20, and four bars — anything bigger. This is also logarithmic scale. On contrary,
if you divide 1000 by 4 even parts, and one bar will fired if you’ve got 250 connections, two bars if you’ve got 500, etc,
this would make the indicator useless, such indicators are no better than simple “on/off” lamp.
It’s like summing number of zeroes of two numbers. Let’s say, you need to multiply 100 by 1000. Just sum num-
ber of their zeroes (2 and 3). The result if the number with 5 zeroes. It’s the same as log10 (100) + log10 (1000) =
log10 (100000).
77
Figure 6.4: Initial state of slide rule
Now shift the core of rule so C scale at 1 will point to 1.2 at D scale:
Find 2 at C scale and find corresponding value at D scale (which is 2.4). Indeed, 1.2 · 2 = 2.4. It works because by
sliding scales we actually add distance between 1 and 1.2 (at any scale) to the distance between 1 and 2 (at any scale).
But since these scales logarithmic, addition of logarithmic values is the same as multiplication.
Values on scales can be interpreted as values of other order of magnitude. We can say that 1 at C scale is actually point
to 12 at D scale. Find 1.8 at D scale (which is 18 now), it points somewhere between 21 and 22. It’s close: 12 · 18 = 216.
78
Figure 6.6: Example from Wikipedia
First, find common (base of 10) logarithms of each number using logarithmic table:
Then add these numbers. Find the number you got in table of powers of 10 (10x , also called “anti-log table”):
Resulting number is a product. The whole process may be faster than to multiply using long multiplication method
using paper-n-pencil taught in schools.
79
Screenshots I took from the Bradis’ book, once popular in USSR. Another well-known book in western world with
logarithmic and other tables is Daniel Zwillinger - CRC Standard Mathematical Tables and Formulae (up to 30th edition,
the logarithmic tables are dropped after).
int main ()
{
double a =1.234e -300;
double b =2.345678901234e -24;
double c =3.456789e -50;
printf ("%.30 e\n", a*b/c);
};
The output is 1.429261797122261460966983388190 × 10−274 , which is incorrect. When using debugger, we can see
that the multiplication operation raises inexact exception and underflow exception in FPU. The division operation also
raises inexact exception.
Let’s check in Wolfram Mathematica:
In []:= a*b/c
Out []= 8.37357*10^ -275
The underflow exception raised in my C program because result of multiplication is in fact 2.894567764122756∗10−324 ,
which is even smaller than smallest denormalized number FPU can work with.
Let’s rework our example to compute it all using natural logarithms (exp(x) is a C standard function, which computes
ex and log(x) here is loge (x) (or ln(x))):
4
Denormalized numbers in double-precision floating point format are numbers between ≈ 10324 and ≈ 10308
80
Listing 6.6: C code
# include <stdio .h>
# include <math.h>
int main ()
{
double a =1.234e -300;
double b =2.345678901234e -24;
double c =3.456789e -50;
printf ("%.30 e\n", exp(log(a)+log(b)-log(c)));
};
int main ()
{
double a =1.234 e+300;
double b =2.345678901234 e+24;
double c =3.456789 e+50;
printf ("%.30 e\n", a*b/c);
};
When this program running, its result is “inf”, meaning ∞, i.e., overflow occurred. When using debugger, we can see
than the multiplication operation raises inexact exception plus overflow exception. The correct value in Wolfram Math-
ematica is...
In []:= b = 2.345678901234*10^24;
In []:= c = 3.456789*10^50;
In []:= a*b/c
Out []= 8.37357*10^273
81
int main ()
{
double a =1.234 e+300;
double b =2.345678901234 e+24;
double c =3.456789 e+50;
printf ("%.30 e\n", exp(log(a)+log(b)-log(c)));
};
Given that, the FPU may process significands and exponents separately during multiplication, but when it processes
exponents of two numbers, they are just summed up. For example:
signif icand1 × 210 · signif icand2 × 250 = signif icand3 × 260 (6.5)
…precise values of significands are omitted, but we can be sure, if the first number has exponent of 10, the second has
50, the exponent of the resulting number will be ≈ 60.
Conversely, during division, exponent of divisor is subtracted from the exponent of the dividend.
I don’t have access to Intel or AMD FPU internals, but I can peek into OpenWatcom FPU emulator libraries 6 .
Here is summing of exponents during multiplication:
https://github.com/open-watcom/open-watcom-v2/blob/86dbaf24bf7f6a5c270f5a6a50925f468d8d292b/
bld/fpuemu/386/asm/fldm386.asm#L212.
And here is subtracting of exponents during division:
https://github.com/open-watcom/open-watcom-v2/blob/e649f6ed488eeebbc7ba9aeed8193d893288d398/
bld/fpuemu/386/asm/fldd386.asm#L237.
5
https://en.wikipedia.org/wiki/Logarithmic_number_system
6
It was a time in 1980s and 1990s, when FPU was expensive and it could be bought separately in form of additional chip and added to x86
computer. And if you had run a program which uses FPU on the computer where it’s missing, FPU emulating library might be an option. Much
slower, but better than nothing.
82
Here is also multiplication function from FPU emulator in Linux kernel: https://github.com/torvalds/linux/
blob/da957e111bb0c189a4a3bf8a00caaecb59ed94ca/arch/x86/math-emu/reg_u_mul.S#L93.
6.4 Exponentiation
Using equation 6.3 we may quickly notice that
bn = |b × ·{z
· · × }b = base(logbase (b))∗n (6.7)
n
That works with any logarithmic base. In fact, this is the way how exponentiation is computed on computer. x86 CPU
and x87 FPU has no special instruction for it.
This is the way how pow() function works in Glibc: https://github.com/lattera/glibc/blob/master/sysdeps/
x86_64/fpu/e_powl.S#L189:
...
7: fyl2x // log2(x) : y
8: fmul %st (1) // y*log2(x) : y
fst %st (1) // y*log2(x) : y*log2(x)
frndint // int(y*log2(x)) : y*log2(x)
fsubr %st , %st (1) // int(y*log2(x)) : fract (y*log2(x))
fxch // fract (y*log2(x)) : int(y*log2(x))
f2xm1 // 2^ fract (y*log2(x)) -1 : int(y*log2(x))
faddl MO(one) // 2^ fract (y*log2(x)) : int(y*log2(x))
fscale // 2^ fract (y*log2(x))*2^ int(y*log2(x)) : int(y*
log2(x))
fstp %st (1) // 2^ fract (y*log2(x))*2^ int(y*log2(x))
...
x87 FPU has the following instructions used Glibc’s version of pow() function: FYL2X (compute y · log2 x), F2XM1 (com-
pute 2x –1). Even more than that, FYL2X instruction doesn’t compute binary logarithm alone, it also performs multi-
plication operation, to provide more easiness in exponentiation computation.
It works because calculating 2x (exponentiation with base 2) is faster than exponentiation of arbitrary number.
Using hacker’s tricks, it’s also possible to take advantage of the IEEE 754 format and SSE instructions set:
http://stackoverflow.com/a/6486630/4540328.
83
√
2
log2 x
x=2 2 (6.8)
This leads to an interesting consequence: if you have a value stored in logarithmical form and you need to take square
root of it and leave it in logarithmical form, all you need is just to divide it by 2.
And since floating point numbers encoded in IEEE 754 has exponent encoded in logarithmical form, you need just
to shift it right by 1 bit to get square root: https://en.wikipedia.org/wiki/Methods_of_computing_square_
roots#Approximations_that_depend_on_the_floating_point_representation.
Likewise, cube root and nth root can be calculated using logarithm of corresponding base:
√
b
logb x
x=b b (6.9)
loga (x)
logy (x) = (6.10)
loga (y)
So, to compute common (base 10) logarithm using available x87 FPU instructions, we may use this equation:
log2 (x)
log10 (x) = (6.11)
log2 (10)
1
logy (x) = (6.12)
logx (y)
Knowing that, and the fact that x87 FPU has FYL2X instruction (compute y · log2 x), logarithm base conversion can be
done using multiplication:
84
This piece of code I found inside of Windows NT4 ( src/OS/nt4/private/fp32/tran/i386/87tran.asm ), this func-
tion is capable of computing both common and natural logarithms:
lab fFYL2Xm
fxch
or cl , cl ; if arg is negative
JSNZ Yl2XArgNegative ; return a NAN
fyl2x ; compute y*log2(x)
ret
Binary logarithm of some number is the number of how many bits needs to be allocated.
If you have a variable which always has 2x form, it’s a good idea to store a binary logarithmic representation (log2 (x))
instead of it. There are at least two reasons: 1) the programmer shows to everyone that the number has always 2x
form; 2) it’s error-prone, it’s not possible to accidentally store a number in some other form to this variable, so this is
some kind of protection; 3) logarithmic representation is more compact. There is, however, performance issue: the
number must be converted back, but this is just one shifting operation (1<<log_n).
Here is an example from NetBSD NTP client ( netbsd-5.1.2/usr/src/dist/ntp/include/ntp.h ):
85
# define NTP_MINPOLL 4 /* log2 min poll interval (16 s) */
# define NTP_MINDPOLL 6 /* log2 default min poll (64 s) */
# define NTP_MAXDPOLL 10 /* log2 default max poll ( 17 m) */
# define NTP_MAXPOLL 17 /* log2 max poll interval ( 36 h) */
If you need to generate bitmasks in range 1, 2, 4, 8...0x80000000, it is good idea to assign self-documenting name to
iterator variable:
Now about compactness, here is the fragment I found in OpenBSD, related to SGI IP22 architecture 7
( OS/OpenBSD/sys/arch/sgi/sgi/ip22_machdep.c ):
Here is another example of using binary logarithm in Mozilla JavaScript engine (JIT compiler) 8 . If some number is
multiplied by 2x , the whole operation can be replaced by bit shift left.
The following code ( js/src/jit/mips/CodeGenerator-mips.cpp ), when translating multiplication operation into
7
http://www.linux-mips.org/wiki/IP22
8
http://fossies.org/linux/seamonkey/mozilla/js/src/jit/mips/CodeGenerator-mips.cpp
86
MIPS machine code, first, get assured if the number is really has 2x form, then it takes binary logarithm of it and gen-
erates MIPS SLL instruction, which states for “Shift Left Logical”.
Listing 6.18: Mozilla JavaScript JIT compiler (translating multiplication operation into MIPS bit shift instruction)
bool
CodeGeneratorMIPS :: visitMulI (LMulI *ins)
{
default :
uint32_t shift = FloorLog2 ( constant );
...
Thus, for example, x = y · 1024 (which is the same as x = y · 210 ) translates into x = y << 10.
inline uint_fast8_t
CountLeadingZeroes32 ( uint32_t aValue )
{
return __builtin_clz ( aValue );
9
http://fossies.org/linux/seamonkey/mozilla/mfbt/MathAlgorithms.h
87
}
Latest x86 CPUs has LZCNT (Leading Zeroes CouNT) instruction for that 10 , but there is also BSR (Bit Scan Reverse)
instruction appeared in 80386, which can be used for the same purpose. More information about this instruction on
various architectures: https://en.wikipedia.org/wiki/Find_first_set.
There are also quite esoteric methods to count leading zeroes without this specialized instruction: http://yurichev.
com/blog/de_bruijn/.
Best possible strategy is to divide the range in halves. The range is shorten at each step by half. At the very end,
the range has lenght of 1, and this is correct answer. Maximal number of steps using the strategy described here are
log2 (initial_range). In our example, initial range is 100, so the maximum number of steps is 6.64... or just 7. If the
initial range is 200, maximum number of steps are log2 (200) = 7.6.. or just 8. The number of steps increasing by 1
when the range is doubled. Indeed, doubled range indicates that the guesser needs just one more step at the start,
not more. If the initial range is 1000, numbers of steps are log2 (1000) = 9.96... or just 10.
This is exactly O(log n) time complexity.
Now let’s consider couple of practical real-world algorithms. One interesting thing is that if the input array is sorted,
10
GNU __builtin_clz() function on x86 architecture can be thunk for LZCNT
11
https://en.wikipedia.org/wiki/Time_complexity
12
http://rosettacode.org/wiki/Guess_the_number
88
and its size is known, and we need to find some value in it, the algorithm works exactly in the same way as child’s num-
ber guessing game! The algorithm starts in the middle of array and compare the value there with the value sought-after.
Depending on the result (larger or lesser), the cursor is moved left or right and operating range is decreasing by half.
This is called binary search13 , and there is the bsearch() function in standard C/C++ library14 .
Another prominent example in CS is binary trees. They are heavily used internally in almost any programming lan-
guage, when you use set, map, dictionary, etc.
Here is a simple example with the following numbers (or keys) inserted into binary tree: 0, 1, 2, 3, 5, 6, 9, 10, 11, 12, 20,
99, 100, 101, 107, 1001, 1010.
10
1 100
0 5 20 107
3 6 12 99 101 1001
2 9 11 1010
And here is how binary tree search works: put cursor at the root. Now compare the value under it with the value
sought-after. If the value we are seeking for is lesser than the current, take a move into left node. If it’s bigger, move to
the right node. Hence, each left descendant node has value lesser than in ascendant node. Each right node has value
which is bigger. The tree must be rebalanced after each modification (I gave examples of it in my book about reverse
engineering (http://beginners.re/, 51.4.4)). Nevertheless, lookup function is very simple, and maximal number
of steps is logn (number_of _nodes). We’ve got 17 elements in the tree at the picture, log2 (17) = 4.08..., indeed, there
are 5 tiers in the tree.
10 is a number inherently linked with human’s culture, since almost all humans has 10 digits. Decimal system is a result
of it. Nevertheless, 10 has no special meaning in mathematics and science in general. So are common logarithms.
One notable use is a decibel logarithmic scale, which is based of common logarithm.
Common logarithms are sometimes used to calculate space for decimal number in the string or on the screen. How
many characters you should allocate for 64-bit number? 20, because log10 (264 ) = 19.2....
13
https://en.wikipedia.org/wiki/Binary_search_algorithm
14
http://en.cppreference.com/w/cpp/algorithm/bsearch
89
Functions like itoa()15 (which converts input number to a string) can calculate output buffer size precisely, calculating
common logarithm of the input number.
current = initial
# 40 years
for year in range (40):
# what you get at the end of each year?
current = current + current *APY
print "year=", year , " amount at the end", current
The thing is that the final amount (268.50...) is aimed toward e constant.
Now there is another bank, which offers to recapitalize your deposit each month. We’ll rewrite our script slightly:
90
initial =100 # $100
APY =0.025 # Annual percentage yield = 2.5%
current = initial
# 40 years
for year in range (40):
for month in range (12):
# what you get at the end of each month?
current = current + current *( APY /12)
print "year=", year , "month =", month , " amount ", current
current = initial
# 40 years
for year in range (40):
for month in range (12):
for day in range (30):
# what you get at the end of each day?
current = current + current *( APY /12/30)
print "year=", year , "month =", month , "day=", day , " amount ", current
91
...
year= 39 month= 11 day= 26 amount 271.762123927
year= 39 month= 11 day= 27 amount 271.780996297
year= 39 month= 11 day= 28 amount 271.799869977
year= 39 month= 11 day= 29 amount 271.818744968
From electronics engineering course we may know that the capacitor discharging by half after RC ln(2) seconds,
where C is capacity of capacitor in farads and R resistance of resistor in ohms. Given 1kΩ resistor and 1000µF ca-
pacitor, what its voltage after 1 seconds will be? after 2 seconds? It’s discharge can be calculated using this equation:
−t
V = V0 · e RC
…where V0 is initial charge in volts, t is time in seconds and e is base of natural logarithm.
Let’s see it in Wolfram Mathematica:
v = 1; (* initial voltage *)
92
GridLines -> {{ Log [2], Log [2]*2 , Log [2]*3} , {0.5 , 0.25 , 0.125}} ,
Epilog -> {Text [" ln (2)", {Log [2] , 0.05}] ,
Text [" ln (2) *2" , {Log [2]*2 , 0.05}] ,
Text [" ln (2) *3" , {Log [2]*3 , 0.05}] ,
Text ["1/2" , {0.1 , 0.5}] , Text ["1/4" , {0.1 , 0.25}] ,
Text ["1/8" , {0.1 , 0.128}]} , AxesLabel -> {seconds , voltage }]
As we can see, 21 of initial charge is left after ln(2) seconds ( 0.69...), and 41 of charge is left after ln(4) seconds ( 1.38...).
Indeed, if we interesting in precise time in seconds, when charge will be x1 , just calculate ln(x).
1
Now here is the same plot, but I added two more labels, 3 and 17 :
93
Figure 6.10: Capacitor voltage during discharge
…and we see that these points corresponds to ln(3) and ln(7). That means, 1
3 of charge is left after ln(3) ≈ 1.098...
seconds and 71 of charge after ln(7) ≈ 1.945... seconds.
Radioactive decay
Radioactive decay is also exponential decay. Let’s take Polonium 210 as an example16 . It’s half-life (calculated) is
≈ 138.376 days. That means that if you’ve got 1kg of Polonium 210, after ≈ 138 days, half of it (0.5 kg) left as 210 Po and
another half is transformed into 206 Pb (isotope of lead17 ). After another ≈ 138 days, you’ll get 43 of isotope of lead and
1 210 Po. After another ≈ 138 days, amount of Polonium will be halved yet another time, etc.
4 will left as
N = N0 e−λt
…where N is number of atoms at some point of time, N0 is initial number of atoms, t is time, λ is decay constant.
Decay of Polonium is exponential, but decay constant is the constant, defining how fast (or slow) it will fall.
Here we go in Mathematica, let’s get a plot for 1000 days:
hl = Log [2]/l
138.376
16
https://en.wikipedia.org/wiki/Polonium
17
https://en.wikipedia.org/wiki/Isotopes_of_lead#Lead-206
94
Plot[E^(-l*t), {t, 0, 1000} ,
GridLines -> {{hl , hl*2, hl *3} , {0.5 , 0.25 , 0.125}} ,
Epilog -> {Text [" hl", {hl , 0.05}] , Text [" hl *2" , {hl*2, 0.05}] ,
Text [" hl *3", {hl*3, 0.05}] , Text ["1/2" , {30 , 0.5}] ,
Text ["1/4" , {30 , 0.25}] , Text ["1/8" , {30 , 0.128}]} ,
AxesLabel -> {days , atoms }]
Beer froth
There is even the paper (got Ig Nobel prize in 2002), author’s of which demonstrates that beer froth is also decays expo-
nentially: http://iopscience.iop.org/0143-0807/23/1/304/, https://classes.soe.ucsc.edu/math011a/
Winter07/lecturenotes/beerdecay.pdf.
95
Figure 6.12: Results from the paper
The paper can be taken as a joke, nevertheless, it’s a good demonstration of exponential decay.
Conclusion
Capacitor discharge and radioactive decay obeys the same law of halving some amount after equal gaps of time:
Decay constant in case of capacitor discharge defined by product of resistance and capacity. The bigger one of them,
the slower decay.
Natural logarithm is used to calculate gap of time (half-life or half-time) judging by decay constant.
96
Chapter 7
Symbolic computation
Some numbers can only be represented in binary system approximately, like 13 and π. If we calculate 31 ·3 step-by-step,
we may have loss of significance. We also know that sin( π2 ) = 1, but calculating this expression in usual way, we can
also have some noise in result. Arbitrary-precision arithmetic1 is not a solution, because these numbers cannot be
stored in memory as a binary number of finite length.
How we could tackle this problem? Humans reduce such expressions using paper and pencil without any calcula-
tions. We can mimic human behaviour programmatically if we will store expression as tree and symbols like π will be
converted into number at the very last step(s).
This is what Wolfram Mathematica2 does. Let’s start it and try this:
In []:= x + 2*8
Out []= 16 + x
Since Mathematica has no clue what x is, it’s left as is, but 2 · 8 can be reduced easily, both by Mathematica and by
humans, so that is what has done. In some point of time in future, Mathematica’s user may assign some number to x
and then, Mathematica will reduce the expression even further.
Mathematica does this because it parses the expression and finds some known patterns. This is also called term rewrit-
ing3 . In plain English language it may sounds like this: “if there is a + operator between two known numbers, replace
this subexpression by a computed number which is sum of these two numbers, if possible”. Just like humans do.
Mathematica also has rules like “replace sin(π) by 0” and “replace sin( π2 ) by 1”, but as you can see, π must be pre-
served as some kind of symbol instead of a number.
So Mathematica left x as unknown value. This is, in fact, common mistake by Mathematica’s users: a small typo in an
input expression may lead to a huge irreducible expression with the typo left.
Another example: Mathematica left this deliberately while computing binary logarithm:
1
https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic
2
Another well-known symbolic computation system are Maxima and SymPy
3
https://en.wikipedia.org/wiki/Rewriting
97
Because it has a hope that at some point in future, this expression will become a subexpression in another expression
and it will be reduced nicely at the very end. But if we really need a numerical answer, we can force Mathematica to
calculate it:
Characters in the expression are just unresolved symbols4 with no connections to numbers or other expressions, so
Mathematica left them as is.
Another real world example is symbolic integration5 , i.e., finding formula for integral by rewriting initial expression
using some predefined rules. Mathematica also does it:
Benefits of symbolic computation are obvious: it is not prone to loss of significance6 and round-off errors7 , but draw-
backs are also obvious: you need to store expression in (possible huge) tree and process it many times. Term rewriting
is also slow. All these things are extremely clumsy in comparison to a fast FPU8 .
“Symbolic computation” is opposed to “numerical computation”, the last one is just processing numbers step-by-step,
using calculator, CPU9 or FPU.
Some task can be solved better by the first method, some others – by the second one.
98
This is not symbolic computation in any way, but this is slightly better than storing ratios/fractions as just floating point
numbers.
Drawbacks are clearly visible: you need more memory to store ratio instead of a number; and all arithmetic functions
are more complex and slower, because they must handle both numbers and ratios.
Perhaps, because of drawbacks, some programming languages offers separate (rational) data type, as language fea-
ture, or supported by a library 13 : Haskell, OCaml, Perl, Ruby, Python (fractions), Smalltalk, Java, Clojure, C/C++14 .
13
More detailed list: https://en.wikipedia.org/wiki/Rational_data_type
14
By GNU Multiple Precision Arithmetic Library
99
100
Chapter 8
Graph theory
Graph is a group of nodes, some of them may be connected with each other, some are not. One of the popular examples
is the map of country: there are cities and roads. ”Cities” are called ”nodes” or ”vertices” in mathematics lingo, while
”roads” are called ”edges”. Another popular example of graph is computer network, including Internet. Computer
network is graph indeed, but it’s closer to ”sparse graph”, because for the most part, computer networks are trees.
community =
Graph [{ John <-> Mark , John <-> Alice , Mark <-> Alice , Tim <-> Alice ,
Matthew <-> John , Matthew <-> Mark , Tim <-> John , Drake <-> Tim ,
Bob <-> Drake , Bill <-> Mark , Bob <-> Alice , Tim <-> Mark},
VertexLabels -> "Name "]
101
Let’s try to find largest clique:
Indeed, each of these four persons is connected to each among other 3. Wolfram Mathematica can highlight subgraph
in graph:
[00:11] <synire > How would one find the path of an application installed using
terminal ?
[00:11] <zykotick9 > synire : " whereis foo"
[00:11] <synire > zykotick9 : thanks !
It’s not a rule, but well-established practice, so we can recover the information, which users talks to which users most
often. Let’s say, we would build a link between two IRC users if 1) they talk to each other at least 10-11 days (not neces-
sary consequent); 2) do this at least 6 months (not necessary consequent).
The largest cliques of #ubuntu IRC channel in 10-11 years period are these:
* clique size 11
['ubottu ', 'ActionParsnip ', 'ikonia ', 'Ben64 ', 'zykotick9 ', 'theadmin ', '
dr_willis ', 'MonkeyDust ', 'usr13 ', 'bekks ', 'iceroot ']
* clique size 10
102
['ubottu ', 'ActionParsnip ', 'ikonia ', 'jrib ', 'bazhang ', 'Pici ', 'iceroot ', '
theadmin ', 'IdleOne ', 'erUSUL ']
* clique size 10
['ubottu ', 'ActionParsnip ', 'ikonia ', 'jrib ', 'bazhang ', 'Pici ', 'iceroot ', '
theadmin ', 'zykotick9 ', 'usr13 ']
* clique size 10
['ubottu ', 'ActionParsnip ', 'ikonia ', 'jrib ', 'bazhang ', 'Pici ', 'iceroot ', '
sebsebseb ', 'IdleOne ', 'erUSUL ']
* clique size 10
['ubottu ', 'ActionParsnip ', 'ikonia ', 'jrib ', 'Dr_Willis ', 'Pici ', 'edbian ', '
IdleOne ', 'Jordan_U ', 'theadmin ']
* clique size 10
['ubottu ', 'ActionParsnip ', 'ikonia ', 'jrib ', 'Dr_Willis ', 'Pici ', 'edbian ', '
IdleOne ', 'Jordan_U ', 'sebsebseb ']
* clique size 10
['ubottu ', 'ActionParsnip ', 'ikonia ', 'jrib ', 'Dr_Willis ', 'Pici ', 'erUSUL ', '
iceroot ', 'IdleOne ', 'theadmin ']
* clique size 10
['ubottu ', 'ActionParsnip ', 'ikonia ', 'jrib ', 'Dr_Willis ', 'Pici ', 'erUSUL ', '
iceroot ', 'IdleOne ', 'sebsebseb ']
* clique size 10
['ubottu ', 'ActionParsnip ', 'ikonia ', 'jrib ', 'Dr_Willis ', 'Pici ', 'erUSUL ', '
iceroot ', 'ubuntu ', 'sebsebseb ']
* clique size 10
['ubottu ', 'ActionParsnip ', 'ikonia ', 'Ben64 ', 'histo ', 'bekks ', 'MonkeyDust ', '
dr_willis ', 'iceroot ', 'usr13 ']
...
Perhaps, these users are frequenters of the channel. List of all cliques are here: https://github.com/DennisYurichev/
Math-for-programmers/blob/master/graph/clique/files/IRC/results.txt. The output is not terse, because
all listed cliques are cliques indeed, and single user or users group can be member of several cliques, that’s correct.
Cliques can be overlapped and be members of bigger cliques. It’s possible to produce more human-like results using
more complex algorithms for finding communities.
The source code of my scripts here: https://github.com/DennisYurichev/Math-for-programmers/tree/master/
graph/clique/files/IRC. I used the excellent networkx graph library.
In []:= g2 =
Graph [{91708 -> 93574 , 93414 -> 91525 , 93414 -> 89579 ,
90407 -> 93896 , 93414 -> 93598 , 93809 -> 5909 , 93698 -> 93801 ,
93163 -> 83317 , 84930 -> 93896 , 93414 -> 92947 , 93414 -> 91708 ,
93792 -> 92887 , 84930 -> 91708 , 91708 -> 84930 , 88400 -> 93698 ,
...
93809 -> 93475 , 93698 -> 92887 , 93801 -> 93670 , 92887 -> 93598}]
103
The resulting graph is:
There some artifacts (at the bottom) which can be ignored so far, I think. There is prominent centers: one huge and two
others are smaller. I’m not sure, but I can suggest these parts of graph are just users who has different sleep paterns,
or, more likely, from different time zones, so each important time zone (like Americas, Europe, Asia/Oceania) may have
their own social communities. But again, I’m not sure, this should be investigated first.
Let’s try to find communities and hightlight them within the graph:
c2 = FindGraphCommunities [g2 ];
HighlightGraph [g2 , Map[ Subgraph [g2 , #] &, c2]]
104
Hard to say if Mathematica right, but this is what it did.
Now let’s take the whole graph of all IRC interactions starting at year 2004 till the summer of 2015. The graph is much
bigger:
105
There are more artifacts.
106
Is it right? Maybe. Needless to say, since timespan is so long (at least 10 years), we can belive that some communities
which may exists in 2004-2006 may be extinct in 2014-2015 (people got older, lost their interest in Ubuntu Linux, etc),
but they all are visible on this graph.
Summary: perhaps, on our next experiment we should filter out IRC data by years and time zones.
107
• Utah-related articles (9): Red Line (TRAX), Utah Transit Authority, Blue Line (TRAX), TRAX (light rail), Salt Lake
City, Green Line (TRAX), FrontRunner, University of Utah, Utah.
• Articles related to Doctor Who (9): Doctor Who (film), Doctor Who, The Doctor (Doctor Who), Eighth Doctor, The
Master (Doctor Who), Gallifrey, TARDIS, Doctor Who Magazine, Seventh Doctor.
• Space (9): New Horizons, Pioneer 11, Voyager 1, Europa (moon), Callisto (moon), Ganymede (moon), Jupiter, Io
(moon), Pioneer 10.
• Hip hop music (9): G-funk, Dr. Dre, Death Row Records, Snoop Dogg, The Chronic, Gangsta rap, West Coast hip
hop, N.W.A, Hip hop music.
• Metal music (9): Master of Puppets, Thrash metal, Cliff Burton, James Hetfield, Kirk Hammett, Metallica, Kill ’Em
All, Ride the Lightning, Dave Mustaine.
• The Beatles (8): Break-up of the Beatles, The Beatles, George Harrison, Let It Be, John Lennon, Paul McCartney,
Ringo Starr, Abbey Road.
Each Wikipedia article within any of these cliques has links to each article in clique.
Full lists of first 1000 largest cliques in English, Russian and Ukrainian Wikipedias plus source code of my scripts is here:
https://github.com/DennisYurichev/Math-for-programmers/tree/master/graph/clique/files/wikipedia.
1
https://yurichev.com/writings/SAT_SMT_by_example.pdf
108
Chapter 9
9.1 GCD
What is Greatest common divisor (GCD)?
Let’s suppose, you want to cut a rectangle by squares. What is maximal square could be?
For a 14*8 rectangle, this is 2*2 square:
**************
**************
**************
**************
**************
**************
**************
**************
->
** ** ** ** ** ** **
** ** ** ** ** ** **
** ** ** ** ** ** **
** ** ** ** ** ** **
** ** ** ** ** ** **
** ** ** ** ** ** **
** ** ** ** ** ** **
** ** ** ** ** ** **
109
**************
**************
**************
**************
**************
**************
**************
->
******* *******
******* *******
******* *******
******* *******
******* *******
******* *******
******* *******
**************
**************
**************
**************
**************
**************
**************
**************
**************
**************
GCD of coprimes is 1.
GCD is also a common set of factors of several numbers. This we can see in Mathematica:
110
Or:
In []:= 11*13*17
Out []= 2431
This unit of time is the smallest time unit which is LARGER than a nanosecond ,
and can in integer quantities exactly represent a single frame duration for
24hz , 25hz , 30hz , 48hz , 50hz , 60hz , 90hz , 100hz , 120hz , and also 1/1000
divisions of each.
This makes it suitable for use via std :: chrono :: duration and std :: ratio
for doing timing work against the system high resolution clock , which is in
nanoseconds ,
but doesn 't get slightly out of sync when doing common frame rates .
In order to accomodate media playback , we also support some common audio sample
rates as well.
This list is not exhaustive , but covers the majority of digital audio formats .
They are 8kHz , 16kHz , 22.05 kHz , 24kHz , 32kHz , 44.1 kHz , 48kHz , 88.2 kHz , 96kHz ,
and 192 kHz.
While humans can 't hear higher than 48kHz , the higher sample rates are used
for working audio files which might later be resampled or retimed .
The NTSC variations (∼29.97 , etc) are actually defined as 24 * 1000/1001 and
30 * 1000/1001 , which are impossible to represent exactly in a way where 1
second is exact ,
so we don 't bother - they 'll be inexact in any circumstance .
111
Details
( https://github.com/OculusVR/Flicks )
Where the number came from? Let’s enumerate all possible time intervals they want to use and find GCD using Math-
ematica:
In []:= GCD [1/24 , 1/24000 , 1/25 , 1/25000 , 1/30 , 1/30000 , 1/48 , 1/50 ,
1/50000 , 1/60 , 1/60000 , 1/90 , 1/90000 , 1/100 , 1/100000 , 1/120 ,
1/120000 , 1/8000 , 1/16000 , 1/22050 , 1/24000 , 1/32000 , 1/44100 ,
1/48000 , 1/88200 , 1/96000 , 1/192000]
1
Rationale: you may want to play a video with 50 fps and, simultaneously, play audio with 96kHz sampling rate. Given
that, you can change video frame after each 14112000 flicks and change one audio sample after each 7350 flicks. Use
any other video fps and any audio sampling rate and you will have all time periods as integer numbers. No ratios any
more.
On contrary, one nanosecond wouldn’t fit: try to represent 1/30 second in nanoseconds, this is (still) ratio: 33333.33333...
nanoseconds.
9.3 LCM
Many people use LCM1 in school. Sum up 41 and 16 . To find an answer mentally, you ought to find Lowest Common
6 4
Denominator, which can be 4*6=24. Now you can sum up 24 + 24 = 10
24 .
3 2 5
But the lowest denominator is also a LCM. LCM of 4 and 6 is 12: 12 + 12 = 12 .
1
Least Common Multiple
112
9.3.1 File copying routine
In GNU coreutils, we can find that LCM is used to find optimal buffer size, if buffer sizes in input and ouput files are
differ. For example, input file has buffer of 4096 bytes, and output is 6144. Well, these sizes are somewhat suspicious.
I made up this example. Nevertheless, LCM of 4096 and 6144 is 12288. This is a buffer size you can allocate, so that you
will minimize number of read/write operations during copying.
https://github.com/coreutils/coreutils/blob/4cb3f4faa435820dc99c36b30ce93c7d01501f65/src/copy.
c#L1246. https://github.com/coreutils/coreutils/blob/master/gl/lib/buffer-lcm.c.
113
114
Chapter 10
Linear algebra
115
R1 =2000
R2 =5000
R3 =1000
V1 =5 # left
V2 =15 # right
s= Solver ()
s.add(i3 == i1+i2)
print s. check ()
m=s. model ()
print m
print m[i1 ]. as_decimal (6)
print m[i2 ]. as_decimal (6)
print m[i3 ]. as_decimal (6)
sat
[i3 = 11/3400 , i1 = 3/3400 , i2 = 1/425]
0.000882?
0.002352?
0.003235?
i1 + i2 -i3 == 0
R1*i1 + R3*i3 == V1
R2*i2 + R3*i3 == V2
Or in matrix form:
116
[ 1, 1, -1 | 0 ]
[ 2000 , 0, 1000 | 5 ]
[ 0, 5000 , 1000 | 15 ]
In [1]:= RowReduce [{{1 , 1, -1, 0}, {2000 , 0, 1000 , 5}, {0, 5000 , 1000 , 15}}]
Out [1]= {{1 ,0 ,0 ,3/3400} ,{0 ,1 ,0 ,1/425} ,{0 ,0 ,1 ,11/3400}}
In [2]:= 3/3400// N
Out [2]= 0.000882353
In [3]:= 1/425// N
Out [3]= 0.00235294
In [4]:= 11/3400// N
Out [4]= 0.00323529
[ 1,0,0 | 3/3400 ]
[ 0,1,0 | 1/425 ]
[ 0,0,1 | 11/3400 ]
2
http://reference.wolfram.com/language/ref/RowReduce.html
3
https://rosettacode.org/wiki/Gaussian_elimination#C
117
void swap_row ( double *a, double *b, int r1 , int r2 , int n)
{
double tmp , *p1 , *p2;
int i;
int main(void)
{
double a[] = {
1, 1, -1,
118
2000 , 0, 1000 ,
0, 5000 , 1000
};
double b[] = { 0, 5, 15 };
double x[3];
int i;
return 0;
}
I run it:
0.000882353
0.00235294
0.00323529
119
120
Chapter 11
Acronyms used
121