Hacker Newsnew | past | comments | ask | show | jobs | submit | LegionMammal978's commentslogin

> The implicit update surface is somewhat limited by the fact that versions in Cargo.toml implicitly assume the `^` operator on versions that don't specify a different operator, so "1.2.3" means "1.2.x, where x >= 3". For reasons that have never been clear to me, people also seem to really like not putting the patch version in though and just putting stuff like "1.2", meaning that anything other than a major version bump will get pulled in.

Not quite: "1.2.3" = "^1.2.3" = ">=1.2.3, <2.0.0" in Cargo [0], and "1.2" = "^1.2.0" = ">=1.2.0, <2.0.0", so you get the "1.x.x" behavior either way. If you actually want the "1.2.x" behavior (e.g., I've sometimes used that behavior for gmp-mpfr-sys), you should write "~1.2.3" = ">=1.2.3, <1.3.0".

[0] https://doc.rust-lang.org/cargo/reference/specifying-depende...


I don't know how I got this wrong because I literally went and looked at that page to try to remind myself, but I somehow misread it, because you're definitely right. This probably isn't the first time I've gotten this wrong either.

From thinking it through more closely, it does actually seem like it might be a little safer to avoid specifying the patch version; it seems like putting 1.2.3 would fail to resolve any valid version in the case that 1.2.2 is the last non-yanked version and 1.2.3 is yanked. I feel like "1.2.3" meaning "~1.2.3" would have been a better default, since it at least provides some useful tradeoff compared to "1.2", but with the way it actually works, it seems like putting a full version with no operator is basically worse than either of the other options, which is disappointing.


Traditionally in x86, only the first byte is the opcode used to select the instruction, and any further bytes contain only operands. Thus, since there exist 256 possible values for the initial byte, there are at most 256 possible opcodes to represent different instructions.

So if you add a 1-byte instruction for each register to zero its value, that consumes 8 of the possible 256 opcodes, since there are 8 registers. Traditional x86 did have several groups of 1-byte instructions for common operations, but most of them were later replaced with multibyte encodings to free up space for other instructions.


Try 81 bytes for an x86-64 executable, or 77 bytes for the same if you run it on a VM with 5-level paging: https://tmpout.sh/3/22.html


Even in a NAT-less world, the common advice is to use a firewall rule that disallows incoming connections by default. (And I'd certainly be worried if typical home routers were configured otherwise.) So either way, you'd need the average person to mess with their router configuration, if they want to allow incoming P2P connections without hole-punching tricks. At best, the lack of NAT might save you an address-discovery step.


> the common advice is to use a firewall rule that disallows incoming connections by default.

That's good advice! But firewall hole punching is also significantly easier (and guaranteed to work) compared to NAT hole punching. Address discovery is part of it, but there are various ways to implement a NAT (some inherently un-hole-punch-able) and only really one sane way to do a firewall.

> you'd need the average person to mess with their router configuration,

At least with IPv6, that firewall is likely to exist in the CPE, which sophisticated users can then ideally open ports in (or which can implement UPnP/NAT-PMP or whatever the current name for the "open this port now!!" protocol of the decade is); for CG-NAT, it's often outright impossible.


UPnP has covered a huge percentage of use cases that actual users care about, and those who it doesn't cover are often able to do their own customization.


upnp should not exist. Any new router default disables it, as it should be.


Care to elaborate? Non-sophisticated users don't deserve IP reachability?


I used to have it enabled long ago. It's insecure. Random cheap devices will open up ports with upnp without the user noticing. It doesn't work that well either, cause hosts will conflict on ports. P2P applications have better ways to establish connectivity.


How can both be true at the same time: It's insecure for random devices to be able to open up ports, and applications don't even need to open up ports for P2P communication?

If a random device/application wants to insecurely communicate with somebody/something, it will find a way, I agree on that.


Hole-punching tricks work fine. They don't work at all of both users are behind IPv4 NAT/CGNAT.


> But the users would have to maintain their own forks then.

I suppose the idea would be, they don't have to maintain it: if it ever starts to rot from whatever environmental changes, then they can just get the LLM to patch it, or at worst, generate it again from scratch.

(And personally, I prefer writing code so that it isn't coupled so tightly to the environment or other people's fast-moving libraries to begin with, since I don't want to poke at all of my projects every other year just to keep them functional.)


The LLM can a priori test on all possible software and hardware environments, test all possible edge cases for deployment, get feedback from millions of eyes on the project explicitly or implicitly via bug reports and usage, find good general case use features given the massive amounts of data gathered through the community of where the project needs to go next, etc?

Even in a world with pure LLM coding, it's more likely that LLMs maintain an open source place for other LLMs to contribute to.

You're forgetting that code isn't just a technical problem (well, even if it was, that would be a wild claim that goes against all hardness results known to humans given the limits of a priori reasoning...)


> The LLM can a priori test on all possible software and hardware environments, test all possible edge cases for deployment, get feedback from millions of eyes on the project explicitly or implicitly via bug reports and usage, find good general case use features given the massive amounts of data gathered through the community of where the project needs to go next, etc?

Even if that's the ideal (and a very expensive one in terms of time and resources), I really don't think it accurately describes the maintainers of the very long tail of small open-source projects, especially those simple enough for the relevant features to be copied into a few files' worth of code.

Like, sure, projects like Linux, LLVM, Git, or the popular databases may fit that description, but people aren't trying to vendor those via LLMs (or so I hope). And in any case, if the project presently fulfills a user's specific use case, then it "going somewhere next" may well be viewed as a persistent risk.


Yeah, the funny thing that Linux being open-source is absolutely in line with capitalism. Just look at the list of maintainers - they are almost all paid employees of gigacorps.

It is just an optimization that makes sense -- writing an OS that is compatible with all sorts of hardware is hard, let alone one that is performant, checked for vulnerabilities, etc.

Why would each gigacorp waste a bunch of money on developing their own, when they could just spend a tiny bit to improve a specific area they deeply care about, and benefit from all the other changes financed by other companies.


And the GPL makes it all work - as no single gigacorp can just take the whole and legally run with it for their gain, like they could if it was say MIT or BSD licensed.

So you have direct competitors all contributing to a common project in harmony.


Well, GPL is good but I think this setup would still be a local optimum for gigacorps, were it MIT or so. They are using plenty of MIT libraries, e.g. Harfbuzz.

It would just simply not make sense for them to let other companies' improvements go out of the window, unless they can directly monetize it. So it doesn't apply to every project, but especially these low-lying ones would be safe even without any sensible license.


The coefficients given are indeed a near-optimal cubic minimax approximation for (π/2 - arcsin(x))/sqrt(1-x) on [0,1]. But those coefficients aren't actually optimal for approximating arcsin(x) itself.

For reference, the coefficients given are [1.5707288, -0.2121144, 0.0742610, -0.0187293]: if we optimize P(x) = (π/2 - arcsin(x))/sqrt(1-x) ourselves, we can extend them to double precision as [1.5707288189560218, -0.21211524058527342, 0.0742623449400704, -0.018729868776598532]. Increasing the precision reduces the max error, at x = 0, by 0.028%.

Adjusting our error function to optimize the absolute error of arcsin(x) = π/2 - P(x)*sqrt(1-x) on [0,1], we get the coefficients [1.5707583404833712, -0.2128751841625164, 0.07689738736091772, -0.02089203710669022]. The max error is reduced by 44%, from 6.75e-5 to 3.80e-5. If we plot the error function [0], we see that the new max error is achieved at five points, x = 0, 0.105, 0.386, 0.730, 0.967.

(Alternatively, adjusting our error function to optimize the relative error of arcsin(x), we get the coefficients [1.5707963267948966, -0.21441792645252514, 0.08365774237116316, -0.02732304481232744]. The max absolute error is 2.24e-4, but the max relative error is now 0.0181%, even in the vicinity of the root at x = 0. Though we'd almost certainly want to use a different formula to avoid catastrophic cancellation.)

So it goes to show, we can nearly double our accuracy, without modifying the code, just by optimizing for the right error metric.

[0] https://www.desmos.com/calculator/nj3b8rpvbs


Actually, we can improve this a bit further, by also adjusting the "π/2" constant in arcsin(x) = π/2 - P(x)*sqrt(1-x). We take coefficients [1.5707256467180715, -0.21298179775496026, 0.07727939759417458, -0.02132102849918157] for P(x), then take arcsin(x) = 1.570760986756484 - P(x)*sqrt(1-x). This reduces the max error by 6.97%, from 3.80e-5 to 3.53e-5.

Adjusting the "1" upward in sqrt(1-x) does not seem to help at all.


How did you find out that his optimization was done for a different equation, just by trial?


Just looking at the formula in the code (and the book it came from), we see that the approximation is of form arcsin(x) = π/2 - P(x)*sqrt(1-x). It is called a minimax solution in both, and the simplest form of minimax optimization is for polynomials. So we look at P(x) = (π/2 - arcsin(x))/sqrt(1-x): plotting out its error function with the original coefficients, it has the clear equioscillations that you'd expect from an optimized polynomial, i.e., each local peak has the exact same height, which is the max error. But if we look at the error curve in terms of arcsin(x), then its oscillations no longer have the same height, which indicates that the approximation can be improved.


Thank you for elaborating!


Even for boolean logic problems, a minimum-size CNF or DNF will not necessarily be the cheapest solution in terms of gates. As far as I know, hardly anyone has even attempted automatic minimization in terms of general binary operators.


That's true, it'll just be the minimal solution in SOP. I'm willing to call that good enough. My experience is that in practice you're often better off just relying on the fact that modern computers can execute billions of them per second.


Yes, it is all proprietary, but there are still ways to inspect most of the WL-implemented functions since the system does not go to extreme pains to keep them hidden from introspection. It is not unlike Maple in that sense.


Yeah, hiding the recipes for how the math is really done would make the whole system kinda guess-and-hope for serious users.


One reason I've found in practice to use iteration even in cases when recursion makes lots of sense (e.g., divide-and-conquer algorithms): the ability to periodically save the state of the computation to disk, and resume it later. Every time I write a big recursive routine and set it running for days, I end up cursing my lack of foresight for not implementing checkpoints. (Of course, there's the brute-force workaround of using CRIU, but it is extremely painful to get all the file descriptors set up just right on restore.)

Given that turning an active call stack into a serializable form is such a rare feature even in functional languages, iteration with an explicit stack ends up as the only practical choice for this use case.

(Also, for this particular problem, we can implement a much simpler iterative routine: create an explicit stack of forests, initially containing the initial forest. While the stack is not empty, pop a forest: if it is not nil, push its tail, then if its head tree is not empty, accumulate the value and push the sub-forest. If you're starting with a tree, then stick it in a synthetic forest. This all comes out to ~15 LOC, with no mutability except for the accumulator and stack.)


In general, I find that minimax approximation is an underappreciated tool, especially the quite simple Remez algorithm to generate an optimal polynomial approximation [0]. With some modifications, you can adapt it to optimize for either absolute or relative error within an interval, or even come up with rational-function approximations. (Though unfortunately, many presentations of the algorithm use overly-simple forms of sample point selection that can break down on nontrivial input curves, especially if they contain small oscillations.)

[0] https://en.wikipedia.org/wiki/Remez_algorithm


They teach a lot of Taylor/Maclaurin series in Math classes (and trig functions are sometimes called "CORDIC" which is an old method too) but these are not used much in actual FPUs and libraries. Maybe we should update the curricula so people know better ways.


Taylor series makes a lot more sense in a math class, right? It is straightforward and (just for example), when you are thinking about whether or not a series converges in the limit, why care about the quality of the approximation after a set number of steps?


Not quite. The point of Taylor’s theorem is that the n-th degree Taylor polynomial around a is the best n-th degree polynomial approximation around a. However, it doesn’t say anything about how good of an approximation it is further away from the point a. In fact, in math, when you use Taylor approximation, you don’t usually care about the infinite Taylor series, only the finite component.


Taylor series have a quite different convergence behavior than a general polynomial approximation. Or polynomial fit for that matter. Many papers were written which confuse this.

For example, 1/(x+2) has a pole at x=-2. The Taylor series around 0 will thus not converge for |x|>2. A polynomial approximation for, say, a range 0<x<L, will for all L.


Not sure I would call Remez "simple"... it's all relative; I prefer Chebyshev approximation which is simpler than Remez.


Perhaps, but at least I find it very simple for the optimality properties it gives: there is no inherent need to say, "I know that better parameters likely exist, but the algorithm to find them would be hopelessly expensive," as is the case in many forms of minimax optimization.


Ideally either one is just a library call to generate the coefficients. Remez can get into trouble near the endpoints of the interval for asin and require a little bit of manual intervention, however.


Consider applying for YC's Summer 2026 batch! Applications are open till May 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: