HNHacker News
TopNewBestAskShowJobs

spyrja

59 karma · joined April 26, 2025

submissionscomments
spyrja··on The future of large files in Git is Git
Would it be incorrect to say that most of the bloat relates to historical revisions? If so, maybe an rsync-like behavior starting with the most current version of the files would be the best starting point. (Which is all most people will need anyhow.)
spyrja··on Software Development at 800 Words per Minute
For anyone wanting to trying this out on YouTube, select your video, open the Web Developer Tools with Ctrl+Shift+I, then type this (followed by a carriage-return):

  document.getElementsByClassName("video-stream html5-main-video")[0].playbackRate = 5; // Or whatever speed you choose
spyrja··on Logical implication is a comparison operator
Another consequence is that exponentiation a^b can be considered the same thing as b -> a.

In the case where a and b are not strictly boolean (supposing they are instead probabilities for example) you could even generalize it somewhat in terms of "pure" mathematical operations.

  double implies(double a, double b) {
    double dif = a - b;
    double abx = sqrt(dif * dif);
    return 1 + pow(0, abx - dif) - pow(1, abx + dif);
  }
Kind of silly, I know, but it does work.
spyrja··on Logical implication is a comparison operator
Such a satisfying result! However the example is confusing. "Because it is cloudy it will rain." Shouldn't that be the other way around (ie. rain implies the presence of clouds)?
spyrja··on Unxus
Looks like a minimalist operating system implementation.
spyrja··on Is gravity just entropy rising? Long-shot idea gets another look
You can safely replace the terms "order" and "disorder" with "unlikely" and "likely". Simply put, entropy is a measure of how closely a system resembles its "most likely configuration". Consider the discrete entropy of a series of coin flips. Three tosses could result in the following 8 states: HHH, HHT, HTH, HTT, THH, THT, TTH, and TTT. From that we can gather that there is a 1/8 chance of getting either zero or three heads and a 3/8 chance of getting one or two heads. That latter two cases are clearly more likely (and hence associated with a higher entropy). In physics of course entropy is generally the continuous kind, not simple set of binary microstates. But the principle is essentially the same.
spyrja··on Show HN: MidWord – A Word-Guessing Game
That's basically what I did. Drilled down in a binary-search fashion, guessing "captive" in 18 tries. (Doing it programmatically would have been even faster, of course, but then what is the fun of that? The manual approach yielded a fairly optimal performance anyway.)
spyrja··on 0.9999 ≊ 1
Another easy way to understand it is to extend the idea of remainders to decimals. When we say N / D = Q r R that obviously means N = D * Q + R. For example 13 / 3 = 4 r 1 because 13 = 3 * 4 + 1. Likewise then 1 / 3 = 0.3(...) = 0.3 r 0.1 because 1 = 3 * 0.3 + 0.1, but also 1 / 3 = 0.3(...) = 0.33 r 0.01 because 1 = 3 * 0.33 + 0.01, etc. Hence 3 * 0.3(...) = 0.9(...) = 1.
spyrja··on Ratatoi is a C libary that wraps stdlib's strtol (as atoi does), but it's evil.
Personally I'd just go the wrapper approach and ignore errno entirely.

  #include <assert.h>
  #include <ctype.h>
  #include <limits.h>
  #include <stdbool.h>
  #include <stdint.h>
  #include <stdlib.h>

  bool copy_str_to_long_long(const char* str, long long* ptr) {
    while (isspace(*str))
      ++str;
    if (*str == 0)
      return false;
    char* parsed = NULL;
    long long value = strtoll(str, &parsed, 0);
    while (isspace(*parsed))
      ++parsed;
    if (*parsed != 0)
      return false;
    *ptr = value;
    return true;
  }

  long long str_to_long_long(const char* str) {
    long long result;
    assert(copy_str_to_long_long(str, &result));
    return result;
  }

  bool copy_str_to_long(const char* str, long* ptr) {
    long long buffer;
    if (!copy_str_to_long_long(str, &buffer) || buffer < LONG_MIN || buffer > LONG_MAX)
      return false;
    *ptr = buffer;
    return true;
  }

  long str_to_long(const char* str) {
    long result;
    assert(copy_str_to_long(str, &result));
    return result;
  }

  /*
    ...define copy_str_to_short, copy_str_to_int32, etc...
  */
spyrja··on Ratatoi is a C libary that wraps stdlib's strtol (as atoi does), but it's evil.
More than a few C library functions do that kind of thing. Like `strstr`, which takes const strings as arguments but returns a readily modifiable pointer to char. Const-correctness just wasn't on the top of the list when they standardized this stuff, I guess. (Heck, back in those days, most PROGRAMS for that matter weren't written with much care for it.)
spyrja··on Failed Soviet Venus lander Kosmos 482 crashes to Earth after 53 years in orbit
I think "simple but rugged" would be a more apt description. Less moving parts than the US equivalent, easier to maintain, and usually fairly sturdy. On the other hand, since cost was a constant concern, Soviet equipment was generally not designed with aesthetics in mind. So "ugly but reliable" might be another way to put it!
spyrja··on Perfect Random Floating-Point Numbers
Yep, that works too. You likely won't ever need a random number that large, of course, but if you did that would be the way to do it.
spyrja··on Matt Godbolt sold me on Rust by showing me C++
What are you trying to say? C++ can be just as safe as Rust. The latter is just easier to use, especially in terms of working as a team of developers insomuch as the safety-nets are by default in place. Once again, C++ is powerful but comes with just as many footguns as C (in fact, more) and so requires a bit more finesse, experience, and skill to work with safely. Otherwise it can encapsulate pretty much any functionality you could possibly imagine, and that includes safety features. Checked memory accesses, managed memory, well-behaved arithmetic, all of that and more can be codified as a set of objects and interfaces. I started out in assembler, worked up to C, then finally on to C++ where I actually found it quite easy to write safe programs by just sticking to a strict set of guidelines. And that works. I have code that has been running almost continuously for over a decade without a hitch, and I credit that to good programming practices.
spyrja··on Perfect Random Floating-Point Numbers
Actually, the problem is that the algorithm is having to calculate DBL_MAX+DBL_MAX, which of course is going to exceed the maximum value for a double-precision number (by definition). That isn't a very realistic use-case either, but in any case you could just clamp the inputs like so:

  double random_real(double low, double high, int (*random_bit)(void)) {
    if (high < low)
      return random_real(high, low, random_bit);
    const double max = DBL_MAX / 2;
    if (high > max)
      high = max;
    const double min = -max;
    if (low < min)
      low = min;
    double halfway, previous = low;
    while (1) {
      halfway = low + (high - low) / 2;
        if (halfway == previous)
      break;
      if (random_bit() & 1)
        low = halfway;
      else
        high = halfway;
      previous = halfway;
    }
    return halfway;
  }
spyrja··on Matt Godbolt sold me on Rust by showing me C++
Overall it does indeed seem to be the case for roughly 25% of Rust crates:

https://rustfoundation.org/media/unsafe-rust-in-the-wild-not...

https://thenewstack.io/unsafe-rust-in-the-wild/

https://github.com/sslab-gatech/Rudra/blob/master/rudra-sosp...

https://internals.rust-lang.org/t/canvas-unsafe-code-in-the-...

Again, not a criticism of the language itself per se, and certainly not trying to imply that all Rust programmers go around writing unsafe code all day long. It's just kind of funny to me that it is touted as being so much more safe than C++ when the reality is that at least some of those features are in fact disabled for one reason or another in real-world programs.

spyrja··on Matt Godbolt sold me on Rust by showing me C++
Well, precisely. Such is the price of "general-purpose safety". The biggest irony IMO is that the security features of Rust are so often skirted in the name of speed, efficiency, etc, with the end result being a program which isn't much more secure than its C++ equivalent. ¯\_(ツ)_/¯
spyrja··on Perfect Random Floating-Point Numbers
The issues that the GP is grappling with are largely due to the fact that they are trying to "construct" real numbers from a stream of bits. That is always going to lead to bias issues. On the other hand, with this particular algorithm (assuming a truly random source) the resulting number should be more or less completely uniform. It works because we are partitioning the search space itself in such a way that all numbers are as likely as any other. In fact, that the algorithm terminates rather predictably essentially proves just that. After one million invocations, for example, the average number of iterations was something like 57 (with the minimum being 55 and the maximum outlier 74). Which is to say you could pick any number whatsoever and expect to see it no more than once per ~2^57 invocations.
spyrja··on Social AI companions pose unacceptable risks to teens and children under 18
Can't say I see this trend declining any time soon. People seem to find affirmation (and in some sense validation) interacting with these LLM's. Provided the AI in question is well-aligned that shouldn't be much of a concern. Not much different from talking to a friend/therapist for emotional support, is it?
spyrja··on Perfect Random Floating-Point Numbers
Seems to me that the simplest solution would be to repeatedly divide the range of numbers into two halves, then randomly selecting either one until it converges onto a single value. In C this might look something like this:

  double random_real(double low, double high, int (*random_bit)(void)) {
    if (high < low)
      return random_real(high, low, random_bit);
    double halfway, previous = low;
    while (true) {
      halfway = low + (high - low) / 2;
      if (halfway == previous)
        break;
      if (random_bit() & 1)
        low = halfway;
      else
        high = halfway;
      previous = halfway;
    }
    return halfway;
  }
That should theoretically produce a uniformally-distributed value. (Although perhaps I've missed some finer point?)
spyrja··on Matt Godbolt sold me on Rust by showing me C++
To be fair, this sort of thing doesn't have to be so much worse in C++ (yes, it would have been nice if it had been built into the language itself to begin with). You just need a function to do a back-and-forth conversion which then double-check the results, ie:

  #include <exception>  
  #include <sstream>

  template <typename From, typename To>
  void convert_safely_helper_(From const& value, To& result) {
    std::stringstream sst;
    sst << value;
    sst >> result;
  }

  // Doesn't throw, just fails
  template <typename From, typename To>
  bool convert_safely(From const& value, To* result) {
    From check;
    convert_safely_helper_(value, *result);
    convert_safely_helper_(*result, check);
    if (check != value) {
      *result = To();
      return false;
    }
    return true;
  }

  // Throws on error
  template <typename To, typename From>
  To convert_safely(From const& value) {
    To result;
    if (!convert_safely(value, &result))
      throw std::logic_error("invalid conversion");
    return result;
  }

  #include <iostream>

  template <typename Buy, typename Quantity, typename Price>
  void sendOrder(const char* symbol, Buy buy, Quantity quantity, Price price) {
    std::cout << symbol << " " << convert_safely<bool>(buy) << " "
            << convert_safely<unsigned>(quantity) << " " << convert_safely<double>(price)
            << std::endl;
  }

  #define DISPLAY(expression)         \
    std::cout << #expression << ": "; \
    expression

  template <typename Function>
  void test(Function attempt) {
    try {
      attempt();
    } catch (const std::exception& error) {
      std::cout << "[Error: " << error.what() << "]" << std::endl;
    }
  }

  int main(void) {
    test([&] { DISPLAY(sendOrder("GOOG", true, 100, 1000.0)); });
    test([&] { DISPLAY(sendOrder("GOOG", true, 100.0, 1000)); });
    test([&] { DISPLAY(sendOrder("GOOG", true, -100, 1000)); });
    test([&] { DISPLAY(sendOrder("GOOG", true, 100.5, 1000)); });
    test([&] { DISPLAY(sendOrder("GOOG", 2, 100, 1000)); });
  }

Output:

  sendOrder("GOOG", true, 100, 1000.0): GOOG 1 100 1000
  sendOrder("GOOG", true, 100.0, 1000): GOOG 1 100 1000
  sendOrder("GOOG", true, -100, 1000): GOOG 1 [Error: invalid conversion]
  sendOrder("GOOG", true, 100.5, 1000): GOOG 1 [Error: invalid conversion]
  sendOrder("GOOG", 2, 100, 1000): GOOG [Error: invalid conversion]

Rust of course leaves "less footguns laying around", but I still prefer to use C++ if I have my druthers.
spyrja··on PATH should be a system call
For what it is worth, I did actually try to tackle the problem after reading this. So thank you!

https://news.ycombinator.com/item?id=43808428

spyrja··on PATH should be a system call
Challenge accepted!

https://news.ycombinator.com/item?id=43808428

spyrja··on Path is a utility for working with paths
Well I ran a bunch of tests and it turns out that the performance wasn't actually impacted very much after all. So the changes are official. I also made some other adjustments to the default behaviour; if no pattern is specified then it just matches everything. In other words, "path -f" prints every regular file in the filesystem (starting in the current one). Anyway, thanks for the suggestion, otherwise I may not gone down that (decidedly satisfying) rabbit-hole!
spyrja··on Path is a utility for working with paths
I did actually consider that at one point, but eventually decided against it because I felt would have meant a sacrifice in performance; first you'd do the local search, then start at the very top and recurse back down, checking every single entry against the local path to be sure that you don't do the local traversal all over again. Fortunately the code base is very clean and straight-forward, so it would be a fairly trivial excercise to just fork the repo and make those changes yourself to get that kind of behaviour.
spyrja··on Path is a utility for working with paths
Kind of, but also kind of not. I mean if someone can smuggle a file into some random directory, chances are they have enough access to write directly to the "correct" folder to begin with. Personally I wouldn't execute or otherwise load any sort of executable content from a non-root directory (although certainly there are many people who wouldn't even think twice before doing such a thing). So it really just boils down to having a sane security-policy. Restrict searches with something like "path -d /usr *" and you are guaranteed not to scoop-up something that was world-writable in the first place. In fact in the example given in the README, that is precisely how that would have worked. Both /lib32 and /lib64 are owned by "root" and hence not a concern.
spyrja··on Path is a utility for working with paths
I don't see why it necessarily couldn't, my only question would be if there are really many actual use cases for such things? As far as symlinks go, I suppose being able to expand them (but not following them!) might be somewhat useful. But converting to DOS paths and vice-versa? That just doesn't seem very useful. Nevermind converting to-and-fro relative and absolute paths, I can't even imagine what the point of that would be. But perhaps I'm just not seeing the forest for the trees, as they say.
spyrja··on Path is a utility for working with paths
Looks like it has a pretty good interface as well. It does however seem a just a bit too top-heavy (lot's of dependencies) not to mention a few more bugs than I particularly care for. But sheesh, 37K stars, it must be good for something!
spyrja··on Path is a utility for working with paths
I would say the default behaviour just isn't very ergonomic. Suppressing warnings for example requires piping to /dev/null (whereas `path` supresses permission warnings by default), if you want to limit the number of results you have to pipe the output to another command, getting xargs-like behaviour (obviously), or putting quotes around lines with embedded spaces, there are simply more hoops to jump through. It's much easier to type "path -sf .jpg .jpeg .png" than whatever would be required to get the `find` utility to do the same. (Or, say, finding all node_modules folders with "path -z n_m", it's just so much more satisfying.) But yes, these are mostly just syntactic-sugar kinds of issues. Aside from that (and perhaps the lack of cross-platform compatibility), I would say there is nothing inherently deficient about the `find` command. It's a work-horse which probably has more features than `path` does. But the latter really is growing on me. It is actually quite fun to use, if I may say so myself!
← PreviousPage 2 of 2