← Back to context

Comment by dmccarty

2 years ago

Processors are inherently awesome at branching, adding, adding, shifting, etc. And shifting to get powers of 2 (i.e., KB vs. GB) is a superpower of its own. They're a little less awesome when it comes to math.pow(), math.log(), and math.log() / math.log().

Why 300K+ people copied this in the first place shows some basic level of ignorance about what's happening under the hood.[1]

As someone who's been at this for decades now and knows my own failings better than ever, it also shows how developers can be too attracted by shiny things (ooh look, you can solve it with logs instead, how clever!) at the expense of readable, maintainable code.

[1] But hey, maybe that's why we were all on StackOverflow in the first place

> Processors are inherently awesome at branching, adding, adding, shifting, etc. And shifting to get powers of 2 (i.e., KB vs. GB) is a superpower of its own. They're a little less awesome when it comes to math.pow(), math.log(), and math.log() / math.log().

And here's something to consider -- if you're converting a number to human readable format it's more likely than not your about to do I/O with the resulting string, which is probably going to be an order of magnitude more expensive than the little function here.

  • Great point, I wish I'd mentioned it. The expense of the printf dwarfs the log / log (double divided by a double then cast to an int), which itself is greater than some repeated comparisons in a for loop.

    It's key to be able to recognize this when thinking about performant code.

    In other words, the entire exercise is silliness because the eventual printf is going to blow away any nanoseconds of savings by a smarter/shorter routine.

[flagged]

  • It's not that we think it's arcane or that we are in our own "bubbles of thought", it's that we aren't doing math. We're programming a computer. And a competent programmer would know, or at least suspect, that doing it with logarithms will be slower and more complicated for a computer. The author even points out that even he wouldn't use his solution.

    P.S. Please look up the word literally.

    • I'm having a hard time imagining a situation where "printing out the number in a human readable format" is more time consuming than "figuring out what the number is".

      I think a competent programmer might also ask themselves "am I prematurely optimizing?" if their first instinct is to pick the method that only works on a computer. I've operated in this space long enough that bit shifting is synonymous with doing the logarithm in my mind, but if I had to explain how my code works, I would use the logarithm explanation. I would be sure to point out that the computer does log (base 2) of a number much much MUCH faster than any other base.

      Its probably excessive to say that literally every one is taught logarithms as the ideal solution to this problem, but logarithms are almost universally introduced by explaining that the log (base 10) of a number is always greater than or equal to the number of digits in that base 10 number. So if you completed a high school education in the United States, you have almost certainly heard that much at least.

      edit: printing out the number is almost always gonna be faster than figuring out the value of the number, if the speed of the operation matters. My original post implied the opposite. Part of being a competent programmer is recognizing that optimizing is sometimes bikeshedding.

    • The author's final suggested solution at the bottom of the article still relies on logarithms.

      > doing it with logarithms will be slower and more complicated for a computer

      This is a fascinating point of view and while it isn't wrong in certain "low-level optimization golf" viewpoints is in part based on old wrong assumptions from early chipsets that haven't been true in decades. Most FPUs in modern computers will do basic logarithms in nearly as many cycles as any other floating point math. It is marvelous technology. That many languages wrap these CPU features in what look like library function calls like Math.log() instead of having some sort of "log operator" is as much an historic accident of mathematical notation and that logarithms were extremely slow for a human.

      Logarithms used to be the domain of lookup books (you might have one or more volumes, if not a shelf-full) and was one of the keys to the existence of slide rules and why an Engineer would actually have a set of slide rules in different logarithmic bases. Mathematicians would spend lifetimes doing the complex calculations to fill a lookup book of logarithmic data.

      Today's computers excel at it. Early CPU designs saved transistors and made logarithms a domain of application/language design. Some of the most famous game designs did interesting hacks of pre-computing logarithm tables for a specific set of needs and embedding them in ROM in useful memory versus CPU time trade-offs. Today's CPU designs have plenty of transistors and logarithm support in hardware is just about guaranteed. (That's just CPU designs even; GPU designs can be logarithmic monsters in how many and how fast they can do.)

      Yesterday's mathematicians envy the speed at which a modern computer can calculate logarithms.

      In 2023 if you are trying to optimize an algorithm away from logarithms to some other mix of arithmetic you are either writing retro games for a classic chipset like the MOS 6502, stuck by your bosses in a history-challenged backwards language such as COBOL, or massively prematurely optimizing what the CPU can already better optimize for you. I wish that was something any competent programmer would know or at least suspect. It's 2023, it's okay to learn to use logarithms like a mathematician, because you aren't going to need that "optimization" of bit shifts and addition/subtraction/multiplication/division that obscures what your actual high-level algorithmic need and complexity is.

  • > what are you people even programming that you need to know so absolutely little about how anything else in the entire world works

    Feoren, your comment takes an incredibly superior attitude and accuses its reader, every reader, of being stupid.

    When taking the log of a number, the value in general require an infinite number of digits to represent. Computing log(100) / log(10) should return 2.0 exactly, but since log(100) returns a fixed number of digits and log(10) returns a fixed number of digits, are you 100% confident that the ratio will be exactly 2.0?

    Maybe you test it and it does return exactly 2.0 (to the degree floating point can be exactly any value). Are you confident that such a calculation will also work for any power of 10? Maybe they all work on this intel machine -- does it work on every Arm CPU? Every RISCV CPU? Etc. I wouldn't be, but if I wrote dumb "for" loop I'd be far more confident that I'd get the right result in every case.

    • > your comment takes an incredibly superior attitude and accuses its reader, every reader, of being stupid.

      It's also an incredibly superior attitude to think that the discipline of software development is so uniquely special that other subjects, even basic math, have nothing to offer it, and that one could be an effective and productive software developer without having to besmirch your perfect code with concepts from other schools of thought.

      And "stupid" would mean "incapable of understanding basic math". This is more like "unwilling to even try". Mere stupidity would be fine: stupid people need jobs too. But a statement that the operation everyone else in the world would use is "unmaintainable" because the programmer is unwilling to refresh themselves on how logarithms work with a quick scan of its Wikipedia article, that's not stupidity. That's bordering on malpractice.

      > When taking the log of a number, the value in general require an infinite number of digits to represent.

      So does taking a third of a number. So? Do you consider the code "x / 3.0" unmaintainable?

      > Computing log(100) / log(10) should return 2.0 exactly, but since log(100) returns a fixed number of digits and log(10) returns a fixed number of digits, are you 100% confident that the ratio will be exactly 2.0?

      Exactness was never a requirement. Do you really never use floating point? The reality is that showing "1000 kB" 1% of the time that you should have shown "1.0 MB" is actually fine -- nobody cares, everyone understands what it means -- which applies almost all floating point imprecision. It's important to know when it does matter, but it usually doesn't. It's important for a professional to know when to not care. How much of your client's money are you going to spend on worrying about tiny details that they don't care about?

      > Are you confident that such a calculation will also work for any power of 10? Maybe they all work on this intel machine -- does it work on every Arm CPU? Every RISCV CPU? Etc. I wouldn't be, but if I wrote dumb "for" loop I'd be far more confident that I'd get the right result in every case.

      Except a 0.00001% imprecision doesn't matter for most cases, but an off-by-one error does. For loops are much more common sources of error than logarithms are.

  • > You're all literally writing CRUD React front-end javascript by copy-pasting "for" loops from StackOverflow?

    To an approximation, yes.

    The underlying calculations at my bank were probably written once in 1970 in COBOL and haven't changed meaningfully since. But the front-end UI to access it has gone from teletypes and punch cards to glass terminals to networked DOS to Win32 to ActiveX to Web 2.0 to React and mobile apps. Lots and lots of churn and work on the CRUD part, zero churn and work on the "need to remember logarithms" part.

    AI? You have core teams building ChatGPT, Midjourney, etc. Then huge numbers of people accessing those via API, building CRUD sites to aggregate midjourney results and prompts, etc etc. Even Apple has made a drag-and-drop UI to train an AI object classifier, the ratio of people who had to know the math to make that vs the people using it is probably way above 1:100,000

    Is this that surprising?

  • Well, maybe not exactly unmaintainable but I think most of us have learned that floating point operations are not to be trusted, especially if it needs to run on different processors. Furthermore, calling such math operations is an overkill most of the time. I would definitely never consider it for such a simple operation. I actually agree with you that it might look cleaner and easier to understand, but in my mind it would be such a heavy weight overkill I would never use it.