-
Numbers always have exact expressions in data.
-
Numbers always have exact expressions in data if I restrict the significant digits I work with and round them off hard enough.
-
Multiplication is associative and commutative.
-
Multiplication is invertible.
-
Addition is associative and commutative.
-
Addition is invertible.
-
Multiplication is distributive over addition.
-
Repeatedly adding a number
ntimes will get me the same result as multiplying that number byn. -
Repeatedly adding a number
nwill get me the same side of the inequality as multiplying that number byn. -
All of the above statements are true if I use a decimal data format instead of IEEE 754 floating-point.
-
All the above statements are true if I use an equality function that allows for a margin of error.
-
All of the above statements are true if I use integers only.
-
Multiplying two integers together will always give me the exact correct answer.
-
Adding two integers together will always give me the exact correct answer.
-
Numbers will always equal what I assign them.
-
Numbers always have exactly one decimal representation.
-
Numbers always have at least one decimal representation.
-
Negative numbers always exist.
-
Fractional numbers always exist.
-
Integer division is always truncated.
-
Subtracting 1 modulo n does the same thing as adding n-1.
-
Integers are stored in memory in the order I read them (big-endian format).
-
Integers are stored in little-endian format.
-
Integers are stored in a format with fixed endianness.
-
Rounded percentages always add up to 100%.
-
Rounded percentages always add up to 100%, given enough precision.
-
Numbers always have commas as thousands separators.
-
The decimal point is always a period.
-
Numbers are always separated by the thousands.
-
Numbers are always separated by equal groups of digits.
-
Numbers are always grouped the same way when written out in words versus when written out in numerals.
-
Numbers are always grouped when there are too many digits to fit in a single group.
-
When abbreviating numbers, the same prefix/suffix always referes to the same number.
-
When writing numbers out in words, the same word always refers to the same number.
-
The same number always has the same prefix or suffix.
-
Numbers are always written in base ten.
-
Numbers are always written in base 2, 8, 10, or 16.
-
Place values are always written starting with 0, 1, 2, etc.
-
Numbers are always written with one character per digit.
-
Numbers are always written with Arabic numerals.
-
Numbers are always written with numerals.
-
Numbers always exist.
IEEE 754 is great for expressing mostly-accurate quantities, not so great when you want decimal precision. The following examples will all be done in Python, which uses IEEE 754 for its floating-point operations.
(I'm using Python 3.6.3 / IPython 6.2.1 if you're interested in reproducing my setup, but I think these examples should be the same in all versions of Python >= 3.0.)
In [1]: 0.1 + 0.2
Out[1]: 0.30000000000000004
There's actually a whole website dedicated to showcasing this "bug" in different languages: http://0.30000000000000004.com/
Numbers always have exact expressions in data if I restrict the significant digits I display and round them off hard enough.
It's not that simple. Numbers not having exact expressions leads to basically all the assumptions you'd make about arithmetic being incorrect in certain cases.
In [2]: a = 1/10; b = 1/11; c = 1/12
In [3]: a * b * c == a * c * b
Out[3]: False
In [4]: 1.0 / 49.0 * 49.0 == 1.0
Out[4]: False
In [5]: a = 1/3; b = 1/4; c = 1/5
In [6]: a + b + c == a + c + b
Out[6]: False
In [7]: 1/2 + 1/3 - 1/3 == 1/2
Out[7]: False
In [8]: a = 1/3; b = 1/4
In [9]: 3 * (a + b) == 3 * a + 3 * b
Out[9]: False
In [10]: 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 == 0.1 * 10
Out[10]: False
Repeatedly adding a number n times will get me the same side of the inequality as multiplying that number by n.
In [11]: sum(0.1 for i in range(48))
Out[11]: 4.799999999999999
In [12]: 0.1 * 48
Out[12]: 4.800000000000001
All the above statements are true if I use a decimal data format instead of IEEE 754 floating-point.
People often use decimal precision when working with currencies, to make sure the above problems don't happen for decimal values that don't round well into binary. But even that's not foolproof:
from decimal import Decimal
a = Decimal(1) / Decimal(2)
b = Decimal(1) / Decimal(3)
c = Decimal(1) / Decimal(4)
# Multiplication is commutative and associative.
In [1]: a * b * c == a * c * b
Out[1]: False
# Multiplication is invertible.
In [2]: Decimal(1) / b * b == Decimal(1)
Out[2]: False
d = Decimal(1) / Decimal(12)
e = Decimal(1) / Decimal(13)
f = Decimal(1) / Decimal(14)
# Addition is commutative and associative.
In [3]: d + e + f == d + f + e
Out[3]: False
# Addition is invertible.
In [4]: Decimal(1) + b - b == Decimal(1)
Out[4]: False
# Multiplication is distributive over addition.
In [5]: 3 * (e + f) == 3 * e + 3 * f
Out[5]: False
# Repeatedly adding a number `n` times will get me the same number as multiplying that number by `n`.
In [6]: sum(Decimal(1) / Decimal(3) for i in range(10)) == Decimal(1) / Decimal(3) * Decimal(10)
Out[6]: False
# Repeatedly adding a number `n` times will get me on the same side of the inequality as multiplying that number by `n`.
In [7]: sum(Decimal(1) / Decimal(38) for i in range(15))
Out[7]: Decimal('0.3947368421052631578947368424')
In [8]: Decimal(1) / Decimal(38) * Decimal(15)
Out[8]: Decimal('0.3947368421052631578947368420')
In [9]: Decimal(15) / Decimal(38)
Out[9]: Decimal('0.3947368421052631578947368421')
Yes, but then equality is no longer transitive, which means that comparisons of numbers to each other are no longer reliable.
class RoundedNumber:
def __init__(self, n):
self.n = n
def __eq__(a, b):
EPSILON = 0.00000001
return -EPSILON < (a.n - b.n) < EPSILON
a = RoundedNumber(8.00000000)
b = RoundedNumber(8.0000000075)
c = RoundedNumber(8.000000015)
In [1]: a == b
Out[1]: True
In [2]: b == c
Out[2]: True
In [3]: a == c
Out[3]: False
Not all languages have a dedicated integer class. In Javascript, for example, all integers are just floating-point numbers where the mantissa is all zeros past the place that the "decimal point" would be if marked by the exponent.
In node.js:
> 14142135623 * 14142135624
199999999993467730000
The exact answer (from Python) is 199999999993467733752. Javascript truncated the last four digits (3752).
In node.js:
> 20000000000000000 + 1
20000000000000000
> c = 199999999999999996;
20000000000000000
This one isn't a programmer misconception so much as a general misconception about numbers - I introduce to you 0.9 repeating, which, of course, equals 1.
Also, 0 and -0 are the same number.
Infinity and -Infinity would like a word with you, in languages where they exist.
In languages where integers are a thing, one thing that will always catch new learners off guard is that division is truncated. For example, in Python 2:
>>> 3 / 2
1
>>> 7 / 3
2
For people who are used to languages with integers, the language suddenly changing to accommodate accurate division can be jarring. When moving from Python 2 to Python 3, division went from being an integer operation to being possibly-integers, possibly-floating point. Now, if you want integer division, you need to use the double slash (//).
In [1]: 2 / 3
Out[1]: 0.6666666666666666
In [2]: 2 // 3
Out[2]: 0
If your integer type is unsigned, subtracting a larger number from a smaller number gives a result that's greater than either of those numbers.
unsigned int a = 1;
unsigned int b = 2;
if (a - b < 0) {
std::cout << "My assumption was correct!"
} else if (a - b > 0) {
std::cout << "Phooey."
} else {
std::cout << "Wait, what?"
}
Phooey.
This will normally give you a compiler warning, but it's still something you should watch out for.
If your subtraction takes you below 0, the modulus operator will return a negative number, which is bad when you're using it to loop through menu options.
On most Intel/x86 systems, integers are stored in little-endian format, which means that the small bytes come first. A number like 0x12345678 would be stored in memory as 78 56 34 12. The order we're used to reading is called the big-endian format.
Not all computer systems are Intel/x86. Systems like the IBM Z architecture use big-endian number storage.
Some exotic systems use little-endian for 8-bit bytes within 16-bit words and then big-endian for 16-bit words within 32-bit words. This would turn 0x12345678 into 34 12 78 56.
If you round your percentages naïvely and then implement a hack to ensure that they add up to 100%, you get errors like this one: https://twitter.com/AWild_Abra/status/966690878965284864
The example in the post above uses the algorithm "Round all entries except the last one whichever way they go, and subtract the last one from 100%", which will always result in the last entry being given -0.000...001% no matter how precise you make the decimals.
In different parts of the world, thousands separators can be periods, spaces, or even apostrophes.
72,385,164 people
1 073 741 824 bytes
28.259.274 euros
500'428'694 sq km
In almost all of Europe, the comma is used for the decimal marker.
28.259.274,38 euros
1 224 795,128 mètres
In the Chinese and Japanese numeral systems, numbers are grouped in 4 digits, using the 万/億/兆 system.
2527兆1438億2518万9984円
382兆3267亿1093万4200元
In the Indian lakh-crore system, only the last three digits are written in the same group. After that, it's every two digits.
3,45,52,78,194 rupees
Numbers are always grouped the same way when written out in words versus when written out in numerals.
Despite the Chinese and Japanese numeral systems using groups of 4, they tend to use groups of 3 when writing out numbers in Arabic numerals, just like in English.
2527兆1438億2518万9984円 = ¥2,527,143,825,189,984
382兆3267亿1093万4200元 = ¥382,326,710,934,200
In standard Canadian usage, four-digit numbers are not grouped (e.g. 5291) but five-digit numbers are (e.g. 15 873). Also, year numbers, despite being 4 digits, are never digit-grouped - the current year is 2018, after all.
Most people are familiar with the controversy over "kilobyte" meaning 1,000 or 1,024 bytes, and the definitions of "megabyte", "gigabyte", and "terabyte" being extrapolated accordingly. Despite the fact that SI standardized on the 1,000 definition, some people still use 1,024 for convenience whenever a power of 2 is involved.
But did you know there are people who use "T" to refer to "thousand" instead of "trillion" in monetary figures (e.g. $235T meaning $235,000 rather than $235,000,000,000,000)? It tripped me up the first time I saw it too.
Also, even when you do use T as trillion, it can mean either 10^12 or 10^18 depending on common usage where you live. Because...
In most English-speaking countries, a billion refers to 10^9. But in older British usage and European countries, a billion is actually 10^12, while 10^9 is a milliard.
Similarly, in the Chinese counting system, a 兆 can be either 10^12 or 10^16 depending on which counting system you're using.
Depending on your usage, you can either be using the SI system or the colloquial numerical abbreviation system.
| Number | SI suffix | Colloquial suffix (short count) | Colloquial suffix (long count) |
|---|---|---|---|
| 47,200 | 47.2k | 47.2K | 47.2K |
| 47,200,000 | 47.2M | 47.2M | 47.2M |
| 47,200,000,000 | 47.2G | 47.2B | 47.2Md or 47,200M |
| 47.2 x 10^12 | 47.2T | 47.2T | 47.2B |
| 47.2 x 10^15 | 47.2P | 47.2Q/Qa | 47.2Bd or 47,200B |
| 47.2 x 10^18 | 47.2E | 47.2Qt/Qi | 47.2T |
| 47.2 x 10^21 | 47.2Z | 47.2Sx | 47.2Td or 47,200T |
| 47.2 x 10^24 | 47.2Y | 47.2Sp | 47.2Q/Qa |
How often we forget that hexadecimal is a thing.
How often we forget that base64 is a thing, but also, the Maya and Aztec used base 20, the Babylonians used base 60, and there is an example of a tribe in Papua New Guinea that uses base 27.
In binary data encoding schemes like base32, base64, or ASCII85, the character representing zero is A, not 0. The character 0 actually maps to a place value of 52.
Sometimes digits are separated by colons instead. Base 256 (a sequence of bytes) is often expressed with groups of two hexadecimal digits, like in RSA fingerprints (10:e4:7c:92:fd:65:ed:d5:3e:54:65:b8:c7:a7:38:2e). Numbers in arbitrary bases above 36 tend to be written as colon-separated decimal numbers (e.g. 112:48:108:121:110:111:109:49:64:108:115:46:83:69 in base 200).
For example, the Eastern Arabic numberals (٠١٢٣٤٥٦٧٨٩) and Thai numerals (๐๑๒๓๔๕๖๗๘๙), among many more, including the Mayan and Babylonian numerals in different bases.
Not only do Roman numerals exist, but languages like Greek and Hebrew historically used a gematria to express numbers, in which certain letters stood for certain values but with no repeated symbols and way more variety in the range of values that could be taken.
For most of human history, we didn't have precise words or symbols for numbers - only words for vague quantities like "one" or "many".