Speaker 1: You know, I was looking at some Python code the
Speaker 1: other day, just a simple script really to sort a
Speaker 1: list of names, and it just struck me how much
Speaker 1: magic is happening there. I type sort and the world
Speaker 1: just arranges itself. It feels seamless, it feels well. Free.
Speaker 2: Free is a very dangerous word in computing.
Speaker 1: Exactly, and that's the fundamental disconnect we're really tackling today.
Speaker 1: We live in this golden era of high level abstraction,
Speaker 1: you know, Java, Swift, Python, Ruby, where we are deliberately
Speaker 1: shielded from the machine. It's comfortable, it is comfortable, but
Speaker 1: the source material we're covering today argues that this shield
Speaker 1: is actually a blindfold.
Speaker 2: It's a provocative stance. We're diving into. Write Great Code
Speaker 2: Volume one, Understanding the Machine by Randall Hyde, and his
Speaker 2: premise is, well, it's uncomfortable for a lot of modern developers.
Speaker 2: He essentially says, if you don't know what the hardware
Speaker 2: is doing with your variables, you aren't writing great code.
Speaker 2: You're just writing code that work by accident.
Speaker 1: Code that works by accident that stings a little.
Speaker 2: It should and look, Heid isn't saying we need to
Speaker 2: go back to writing assembly for everything. He's not a masochist,
Speaker 2: but he is saying that if you want performance, if
Speaker 2: you want that top tier efficiency, you have to be
Speaker 2: able to mentally map your high level syntax down to
Speaker 2: the low level reality.
Speaker 1: So our mission for this deep dive is to well,
Speaker 1: it's to tear down that abstraction layer. We're going to
Speaker 1: look at how the machine actually thinks, not how we
Speaker 1: want it to think exactly. And it starts with something
Speaker 1: that seems philosophical but is actually purely mechanical. The difference
Speaker 1: between a number and a representation.
Speaker 2: Right, this is Chapter one stuff, but it trips people
Speaker 2: up all the time in the human world. If I
Speaker 2: write the number one hundred on a piece of paper,
Speaker 2: the meaning is fixed. It's one hundred items.
Speaker 1: One hundred apples, one hundred dollars, simple.
Speaker 2: Exactly, It's an abstract quantity. But inside a machine there
Speaker 2: are no abstract quantities. Their only representations. Hide uses this
Speaker 2: great example. If you see the symbols one, zero, zero
Speaker 2: inside a computer's memory, what quantity is that?
Speaker 1: My first instinct is, well, it's one hundred.
Speaker 2: And that's your decimal bias, showing what if I tell
Speaker 2: you that representation is.
Speaker 1: In binary binary one zero zero, Okay, that's the quantity
Speaker 1: four precisely. And if I tell you it's.
Speaker 2: Exodesimal hexodescimal, then it's two hundred and fifty.
Speaker 1: Six same symbols, three totally different values. See, the machine
Speaker 1: doesn't care about the ink on the page. It cares
Speaker 1: about the interpretation of the bit pattern.
Speaker 2: And that matters because the algorithms depend on that specific
Speaker 2: internal structure to work efficiently.
Speaker 1: Yes, if you treat everything as abstract math, you miss
Speaker 1: out on all the shortcuts the hardware offers.
Speaker 2: And the hardware is rigid. I mean, we have to
Speaker 2: talk about the physical reality here. Why are we stuck
Speaker 2: with binary? Why zeros and ones? Why not you know,
Speaker 2: zero through nine?
Speaker 1: It just comes down with reliability. At the hardware level,
Speaker 1: you're dealing with electricity voltage, and it is incredibly difficult
Speaker 1: to build a circuit that can reliably distinguish between ten
Speaker 1: different voltage levels like.
Speaker 2: Point one bolts point two volts point three.
Speaker 1: Yeah, and do that billions of times a second without
Speaker 1: making a single mistake.
Speaker 2: Too much noise on the line, way too much noise.
Speaker 2: But it's very very easy to distinguish between high voltage
Speaker 2: and low voltage on and off saturation and cut off.
Speaker 1: That's binary.
Speaker 2: That's binary. It's the only way to build reliable circuits
Speaker 2: to the scale we operate on today.
Speaker 1: Okay, so we're stuck with binary because of physics. But
Speaker 1: and I think I speak for all humans here, binary
Speaker 1: is just terrible to read. If I have to debug
Speaker 1: a memory dump and it's just pages of eleven hundred
Speaker 1: under zero ten, I'm going to quit.
Speaker 2: My job, which is exactly why we have hexadismal. A
Speaker 2: lot of new programmers think hex is just some kind
Speaker 2: of computer nerd numbers, but it serves a very specific
Speaker 2: structural purpose. It bridges the gap between our brains in
Speaker 2: the binary circuits. How so it's all about the nibble.
Speaker 2: A nibble is a group of four bits. If you
Speaker 2: look at all the possible combinations of four bits from
Speaker 2: zero zero, zero, zero, zero eleven, how many possibilities is that?
Speaker 1: That would be sixty.
Speaker 2: Sixteen possibilities and exodecimal is base sixteen. It has digits
Speaker 2: zero through nine and then A through f. That's sixteen digits.
Speaker 2: So one single hex digit represents exactly four bits of binary.
Speaker 1: Ah, so it's a perfect one to one mapping, a
Speaker 1: perfect mapping. So it's not just a random choice. It's
Speaker 1: more like a compression algorithm for our eyes. Instead of
Speaker 1: writing eleven eleven, I can.
Speaker 2: Just write f exactly. It lets us chunk binary into
Speaker 2: readable pieces. That's why you see it everywhere in low
Speaker 2: level debugging.
Speaker 1: Now, speaking of debugging and performance, there was a section
Speaker 1: in the book that honestly surprised me. We've talked about
Speaker 1: how the machine sees numbers, but we often have to
Speaker 1: get numbers into the machine from a user. Right, the
Speaker 1: user types one, two, three on their keyboard, and Hyde
Speaker 1: points out this hidden costs that I think most of
Speaker 1: us just ignore.
Speaker 2: The ioconversion cost. Oh yeah, this is a classic hidden bottleneck.
Speaker 1: We see a line like sini in C plus plus
Speaker 1: or input and Python, and we think, okay, the user
Speaker 1: types of number, the computer gets the number.
Speaker 2: That is the illusion. The computer doesn't get a number,
Speaker 2: It gets a keystroke, It gets an ASSI character code.
Speaker 1: Right, so if I type one, two, three, the computer
Speaker 1: receives three separate characters one, two, and three.
Speaker 2: And converting those characters into a single binary integer that
Speaker 2: the CPU can actually do math with that is shockingly expensive.
Speaker 1: Walk us through that. Why is it so heavy?
Speaker 2: Okay? Well, think about the algorithm. You take the character
Speaker 2: one first, you have to subtract the as key offset
Speaker 2: usually forty eight to get the actual numeric value of one.
Speaker 2: Then you look the next character two. First, to merge them,
Speaker 2: you have to take your current total, which is one,
Speaker 2: and multiply it by ten.
Speaker 1: And multiplication is not a cheat instruction for the CPU,
Speaker 1: not at all.
Speaker 2: It's heavy. So you multiply by ten, then you add
Speaker 2: the new digit. Now you have twelve. Then you get three,
Speaker 2: foot you have to multiply that whole previous total by
Speaker 2: ten again and add the three.
Speaker 1: So if you're reading a million lines of data from
Speaker 1: a CSV file, you're running that multiplication loop millions.
Speaker 2: Of times millions. And that's just for input out. Putting
Speaker 2: it back to the screen can be even worse. Why
Speaker 2: worse because that requires division by ten to separate the digits,
Speaker 2: and division is often the slowest math operation a modern
Speaker 2: CPU can perform. Hyde points out that this text the
Speaker 2: number conversion, is often the single biggest bottleneck in a program.
Speaker 1: And developers just ignore it because the function call looks
Speaker 1: so simple they do. That is a great takeaway. Don't
Speaker 1: just print variables for fun inside a tight loop. You're
Speaker 1: torturing the CPU. Okay, let's move on to the anatomy
Speaker 1: of this data. We threw around the word nibble earlier,
Speaker 1: which is cute, but we need to talk about the
Speaker 1: heavy headers, right.
Speaker 2: The container sizes. A nibble is four bits, a bite
Speaker 2: is eight bits, and it's crucial to remember the bite
Speaker 2: is usually the smallest addressable unit of.
Speaker 1: Memory, meaning you can't just ask the CPO.
Speaker 2: For bit three, No, you grab the whole bite, and
Speaker 2: then you have to find bit three yourself.
Speaker 1: And then we scale up in the context of this book,
Speaker 1: which is vary by eighty six focused. A word is
Speaker 1: sixteen bits.
Speaker 2: Correct, and a double word or do word is three
Speaker 2: two bits. A quad word is sixty four bits.
Speaker 1: The scale of these things is just wild. A bite
Speaker 1: gets you what, two hundred and fifty six values, that's it.
Speaker 2: But a thirty two bit doer gets you over four billion.
Speaker 1: It's that exponential growth two dollars.
Speaker 2: A little exactly. And within these containers, bit numbering is standardized.
Speaker 2: Bit zero is your low order bit, the least significant.
Speaker 2: The highest number is your high order bit.
Speaker 1: And if you mix those up, you're in for a
Speaker 1: world of pain.
Speaker 2: A very bad time. Interpreting your data.
Speaker 1: Speaking of having a bad time, let's talk about negative numbers.
Speaker 1: This is one of those things where I just assume
Speaker 1: the computer knows the numbers negative, but it's just zeros
Speaker 1: and ones. There's no minus sign in memory.
Speaker 2: This is one of the most elegant hacks in computer science.
Speaker 2: If you are designing a computer from scratch, you might think, okay,
Speaker 2: let's use the first bit as a sign zero for positive,
Speaker 2: one for negative.
Speaker 1: That seems logical.
Speaker 2: It is logical, but hardware hate special cases. If you
Speaker 2: did that, you'd need separate circuits for addition and subtraction.
Speaker 2: You'd need special logic to handle positive zero and negative zero.
Speaker 2: It's a mess.
Speaker 1: So instead we use two's complement.
Speaker 2: Right, and two's compliment is pure genius because it turns
Speaker 2: subtraction into additions.
Speaker 1: Okay, I looked at the recipe in the book invert
Speaker 1: all bits and add one. It sounds like a sorcery spell.
Speaker 1: Why does adding one make it work?
Speaker 2: Okay? Imagine a mechanical o doometer in an old car.
Speaker 2: It's set at zero zero zero zero. If you roll
Speaker 2: it backward one mile, what does it show?
Speaker 1: It rolls over to nine nine exactly.
Speaker 2: The system wraps around in the computer's binary world. If
Speaker 2: you are at zero, zero, zero, and you subtract one,
Speaker 2: you roll backwards to eleven eleven all ones. In a
Speaker 2: sign system, we decide to interpret that all one state
Speaker 2: not as a huge number, but as negative one.
Speaker 1: So the invert and add one rule is just a
Speaker 1: mathematical shortcut to find that specific bit pattern.
Speaker 2: Precisely, it calculates the rollover value. It aligns the negative
Speaker 2: numbers so that if you add five and negative five,
Speaker 2: the binary actually adds up to zero. It essentially rolls
Speaker 2: the odometer back to all zero's. Naturally, the CPU doesn't
Speaker 2: even know it's doing subtraction. Just adding that is.
Speaker 1: Elegant, But Hyde warns us about the edge case from hell,
Speaker 1: the number that cannot be negated.
Speaker 2: Ah, yes, the minimum negative number. In a sixteen bit system,
Speaker 2: your range goes from plus thirty two thousand, seven hundred
Speaker 2: and sixty seven down to negative thirty two thousand, seven
Speaker 2: hundred and sixty eight.
Speaker 1: Wait, those don't match.
Speaker 2: They don't because zero takes up one of the positive
Speaker 2: slots effectively, so the range is lopsided. You have one
Speaker 2: more negative number than positive numbers.
Speaker 1: So if I try to negate negative thirty two thousand,
Speaker 1: seven hundred and sixty eight.
Speaker 2: There is no plus thirty two thousand, seven hundred and
Speaker 2: sixty eight to turn it into. It doesn't exist in
Speaker 2: sixteen bits, so the operation overflows, and in two's compliment,
Speaker 2: due to the math, it actually wraps right back round
Speaker 2: to negative thirty two thousand, seven hundred and sixty eight.
Speaker 1: That is terrifying. So x mclix could return the same.
Speaker 2: Number only for that one specific value. And if your
Speaker 2: code relies on using absolute values to sanitize inputs, say
Speaker 2: you're calculating distance and you assume it must be positive
Speaker 2: that one number will crash.
Speaker 1: Your system or just create a logic bomb.
Speaker 2: Exactly.
Speaker 1: That is exactly the kind of low level detail that
Speaker 1: high level languages hide until it bites you. Now relate
Speaker 1: to this as sign extension. Let's say I have that
Speaker 1: number metative five and a tiny eight bit byte, and
Speaker 1: I want to move it into a big, spacious sixteen.
Speaker 2: Bit word, very common operation.
Speaker 1: My instinct is to just pad the extra space with zeros, and.
Speaker 2: If you do that, you break the number. Remember, in
Speaker 2: two's complement, negative numbers have a one in the high
Speaker 2: order bit. If you add zeros in front of it.
Speaker 2: That one is no longer the high order bit.
Speaker 1: You've just turned a small negative number into a generic
Speaker 1: positive number.
Speaker 2: Right, so you have to copy the sign.
Speaker 1: Bit smeared across the top.
Speaker 2: Yes, you smear that sign bit across all the new
Speaker 2: upper positions. That preserves the negativity, so to speak. It
Speaker 2: keeps the odometer rolled over correctly in the larger container.
Speaker 1: All right, let's get into the wizardry bitwise operations. This
Speaker 1: is where I feel like the real hacker stuff happens.
Speaker 1: We have logic gates a niro or exo hoto jus.
Speaker 2: These are the fundamental building blocks of the CPU, but
Speaker 2: software developers can use them for some incredible optimizations.
Speaker 1: Let's look at the A and D operation. It compares
Speaker 1: two bits and only returns one if both are one.
Speaker 1: The book mentions a trick for checking if a number
Speaker 1: is odd or even.
Speaker 2: Using this this is a classic. Usually people use the
Speaker 2: modulo operator x to b two. If it's zero, it's even.
Speaker 2: But remember what we said about division.
Speaker 1: The machine hates it.
Speaker 2: It's slow, right, But if you look at binary, any
Speaker 2: odd number ends with the one, any even number ends
Speaker 2: with a zero. It's that simple. Okay, So if you
Speaker 2: simply do xa and d.
Speaker 1: One, you're just isolating that last bit.
Speaker 2: You just check in that last bit instantaneously. If the
Speaker 2: result is one, it's odd, if zero it's even. It's
Speaker 2: massively faster than a division operation.
Speaker 1: That is cool. And there's another trick with modular right.
Speaker 1: If you want to cycle a counter, say from zero
Speaker 1: to thirty one, and then loop back to zero.
Speaker 2: Yes, module encounter. Normally you do x plus one percent
Speaker 2: thirty two. Again, division is expensive, but because thirty two
Speaker 2: is a power of two, we can use a mask.
Speaker 2: Thirty two in binary is a one followed by five zeros.
Speaker 2: The number thirty one is just five ones zero, zero, zero, zero,
Speaker 2: zero one on one one one.
Speaker 1: So if we a counter with thirty one.
Speaker 2: It forces all the upper bits to zero. It effectively
Speaker 2: chops off any value greater than thirty one. So x
Speaker 2: a and d thirty one gives you the exact same
Speaker 2: result as x percent and thirty two, but in terms
Speaker 2: of speed it's a Ferrari versus a bicycle.
Speaker 1: And this leads us right into shifting. We talked about
Speaker 1: how expensive multiplication and division are, but shifting bits left
Speaker 1: or right is practically free for the CPU right.
Speaker 2: If you shift a binary number to the left, moving
Speaker 2: all abyss one slot over and adding a zero at
Speaker 2: the end, you've just multiplied by two.
Speaker 1: Shift it left again, you've multiplied by four, and.
Speaker 2: Shifting right divines by two. Simple enough, with a caveat,
Speaker 2: you have to use the right kind of shift. There's
Speaker 2: a logical shift right which just fills in with zeros.
Speaker 2: That's fine for unsigned numbers. But if you have a
Speaker 2: negative number.
Speaker 1: Oh right, the sign bit. If you fill with zeros,
Speaker 1: you lose the negative sign exactly.
Speaker 2: So you need an arithmetic shift, which preserves the sign bit.
Speaker 2: It copies a sign bit as it shifts. If you
Speaker 2: use the wrong one, your negative number suddenly becomes a
Speaker 2: huge positive number and your math breaks completely.
Speaker 1: Now I want to push back on something. In the
Speaker 1: Packed Data chapter, Hyde talks about squashing a date, month, day,
Speaker 1: year into a single sixteen bit word.
Speaker 2: It's a classic optimization. Four bits for the month, five
Speaker 2: bits for the day, seven bits for the year.
Speaker 1: Sure it's saved space, but RAM is cheap Today. I
Speaker 1: have thirty two gigs on my laptop. Why would I
Speaker 1: burn brain cycles trying to fit a date into two
Speaker 1: bytes when I can just use integers. Why make my
Speaker 1: code unreadable just to save a few bytes?
Speaker 2: That is the billion dollar question. RAM is cheap is
Speaker 2: the mantra of modern development. But here's the counter argument.
Speaker 2: Cash is expensive.
Speaker 1: The CPU cash right.
Speaker 2: The CPU is incredibly fast main memory. Your thirty two
Speaker 2: GB of RAM is incredibly slow. By comparison. It's like
Speaker 2: a library on the other side of town. The cash
Speaker 2: is the bookshelf right next to your desk. If you
Speaker 2: use big, bloated integers for everything, you fill up that
Speaker 2: bookshelf with junk. You get cash misses where the CPU
Speaker 2: has to sit idle, twiddling its thumbs, waiting to fetch
Speaker 2: more data from across town.
Speaker 1: So packing the data isn't just about saving hard drive space.
Speaker 1: It's about keeping more data close to the CPU to
Speaker 1: keep it fed exactly.
Speaker 2: However, and this is the trade off. Hide emphasizes, you
Speaker 2: pay a tax every time you want to read.
Speaker 1: That pack data because the CPU can't read middle bits.
Speaker 2: No, it can't just look at bits five through nine.
Speaker 2: It has to fetch the whole word, load a mask
Speaker 2: to zero out the other bits, and then shift the
Speaker 2: bits over to the right to read the value that's
Speaker 2: three or four instructions just to read the day.
Speaker 1: Variable, so it's a balance. Pack data uses fewer cash
Speaker 1: lines but requires more instructions to unpack, which.
Speaker 2: Means if you are just moving data around like a
Speaker 2: network router moving at pack at night, save the bandwidth.
Speaker 2: But if you're doing heavy calculations on that data, maybe
Speaker 2: keep it unpacked so the CPU doesn't have to fight
Speaker 2: to read it every single time.
Speaker 1: That is a crucial insight. It's not about one right way,
Speaker 1: it's about the right way for the constraints.
Speaker 2: You have, and that applies to everything we've discussed, whether
Speaker 2: it's choosing a data type using a bit wise trick,
Speaker 2: or deciding between binary and decimal represent.
Speaker 1: We've covered a massive amount of ground, from the physical
Speaker 1: voltage to the odometer of two's complement. But I want
Speaker 1: to leave our listeners with one final thought from the
Speaker 1: book regarding scaled numerics.
Speaker 2: This is the one that changes how you look at
Speaker 2: a bank statement.
Speaker 1: We rely so heavily on float and double types for
Speaker 1: decimal numbers, but hide challenges us do we really need them.
Speaker 2: Floating point is inherently imprecise. It's an approximation. You add
Speaker 2: point one point two in a computer and often you
Speaker 2: get point three zero zero zero zero zero zero zero.
Speaker 1: Zero four, which is a complete nightmare for financial software.
Speaker 1: You can't just lose pennies in the rounding errors.
Speaker 2: So the provocative thought is this, can you switch your
Speaker 2: mindset to integers. Instead of storing a dollar a fifty cent,
Speaker 2: you store one hundred and fifty pennies.
Speaker 1: You essentially move the decimal point yourself.
Speaker 2: You manage the decimal point in your head, or rather
Speaker 2: in your CODs logic. But the machine does pure, fast,
Speaker 2: precise integer math. It's a technique called fixed point or
Speaker 2: scaled numerics. It forces you to really understand your data's
Speaker 2: range and precision before you write a single line of code.
Speaker 1: And honestly, that feels like the theme of the whole
Speaker 1: book intentionality.
Speaker 2: Intentionality. Don't just let the compiler make the decisions for you.
Speaker 2: You make the.
Speaker 1: Decisions, and that is how you write great code. Thanks
Speaker 1: for diving deep with us today, Happy coding.