<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Hacker News: purplesyringa</title><link>https://news.ycombinator.com/user?id=purplesyringa</link><description>Hacker News RSS</description><docs>https://hnrss.org/</docs><generator>hnrss v2.1.1</generator><lastBuildDate>Sun, 16 Aug 2026 18:19:41 +0000</lastBuildDate><atom:link href="https://hnrss.org/user?id=purplesyringa" rel="self" type="application/rss+xml"></atom:link><item><title><![CDATA[New comment by purplesyringa in "Log is non-monotonic in PHP and Lua"]]></title><description><![CDATA[
<p>To expand on this, the imprecision arises the moment you write 0.1 (decimal) in source code and the compiler converts it to binary; the arithmetic itself is (mostly) exact. So as long as you use numbers that look "good enough" in base-2, floats behave very reasonably. The constantly made assumption that floats are imprecise is, in this sense, a user error -- the user shouldn't have used decimals.</p>
]]></description><pubDate>Wed, 29 Jul 2026 12:42:16 +0000</pubDate><link>https://news.ycombinator.com/item?id=49096752</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=49096752</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49096752</guid></item><item><title><![CDATA[New comment by purplesyringa in "Log is non-monotonic in PHP and Lua"]]></title><description><![CDATA[
<p>`log_a x < log_b x` is the mathematically correct result. Due to inherent rounding from floats being a finite representation of real numbers, `log_a x <= log_b x` would be expected from any correct implementation using floats. So either `true true false` or `true false true` would be reasonable. (I made a mistake in the previous comment, LuaJIT returns `<` for me, just like in your comment, not `=`.)<p>The topic of the post is that in PHP and Lua (without LuaJIT), sometimes this inequality doesn't hold, and instead we get `log_a x > log_b x`, which is very incorrect and cannot be explained away by rounding.<p>Does that make more sense?</p>
]]></description><pubDate>Wed, 29 Jul 2026 12:37:59 +0000</pubDate><link>https://news.ycombinator.com/item?id=49096710</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=49096710</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49096710</guid></item><item><title><![CDATA[New comment by purplesyringa in "Log is non-monotonic in PHP and Lua"]]></title><description><![CDATA[
<p>I sort of needed to do that. I needed to compress data with an approximately geometric distribution, and as part of that process I needed to invert its CDF, which is `CDF = 1 - (1 - p)^x`. That translates to `x = log_(1 - p) (1 - CDF)`, which is variable over both the argument and the base. At that point I wondered how consistent the implementation of double-argument `log` is, since the encoder and the decoder need to agree about it, which led to this article.</p>
]]></description><pubDate>Wed, 29 Jul 2026 10:07:28 +0000</pubDate><link>https://news.ycombinator.com/item?id=49095419</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=49095419</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49095419</guid></item><item><title><![CDATA[New comment by purplesyringa in "Log is non-monotonic in PHP and Lua"]]></title><description><![CDATA[
<p>That's interesting! On my PC it reproduces under Lua 5.5 (`log_a x > log_b x`), but not under LuaJIT (`log_a x = log_b x`). I took a look at LuaJIT's implementation (<a href="https://github.com/LuaJIT/LuaJIT/blob/faaf663340347a78b22ed94c63c24fe090bd9784/src/lib_math.c#L58" rel="nofollow">https://github.com/LuaJIT/LuaJIT/blob/faaf663340347a78b22ed9...</a>) and noticed that it always uses the `ln x / ln a` formula -- or, rather, `log_2 x / log_2 a`, which is just as correct I guess. Have you perhaps misinterpreted the results? (I do think it's valuable to add that this doesn't apply to LuaJIT though.)</p>
]]></description><pubDate>Wed, 29 Jul 2026 09:57:34 +0000</pubDate><link>https://news.ycombinator.com/item?id=49095346</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=49095346</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49095346</guid></item><item><title><![CDATA[New comment by purplesyringa in "Log is non-monotonic in PHP and Lua"]]></title><description><![CDATA[
<p>Thank you! This point has been driving me mad for the last couple of years.<p>While answering a sibling comment, I found a paper analyzing various libms for their precision: <a href="https://homepages.loria.fr/pzimmermann/papers/glibc238-20230921.pdf" rel="nofollow">https://homepages.loria.fr/pzimmermann/papers/glibc238-20230...</a> (2023). I only skimmed it, but it looks like only LLVM's libm guarantees 0.5 ulp for every supported single-precision operation, and everyone else is wildly off. That's a pretty good result, though -- it means there's at least one reasonably compliant libm :)<p>Another sibling comments says that guaranteeing 0.5 ulp for double-precision operations is nigh impossible (and then another says it is after all). I don't have the knowledge to confirm which is true, but it's possible that this is the best we can get.</p>
]]></description><pubDate>Wed, 29 Jul 2026 09:50:46 +0000</pubDate><link>https://news.ycombinator.com/item?id=49095302</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=49095302</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49095302</guid></item><item><title><![CDATA[New comment by purplesyringa in "Log is non-monotonic in PHP and Lua"]]></title><description><![CDATA[
<p>Thank you! I completely agree with this, floats are treated as a lot more magical than they actually are. I myself often rely on their exact guarantees for fun tricks (e.g. fast precise int<->float conversion: <a href="https://purplesyringa.moe/blog/fast-limited-range-conversion-between-ints-and-floats/" rel="nofollow">https://purplesyringa.moe/blog/fast-limited-range-conversion...</a>). But while IEEE-754 is very precise, some subtleties arise when you add library functions to the mix -- many libm's and userland libraries don't guarantee 0.5 ulp precision for certain operations <i>and</i> don't document the guaranteed precision either, at which point you're left guessing and saying "well, I guess I should treat floats as magic in this case after all". I added "it's not imprecision" to step around this whole question.</p>
]]></description><pubDate>Wed, 29 Jul 2026 09:38:42 +0000</pubDate><link>https://news.ycombinator.com/item?id=49095216</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=49095216</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49095216</guid></item><item><title><![CDATA[New comment by purplesyringa in "Log is non-monotonic in PHP and Lua"]]></title><description><![CDATA[
<p>The log base is a parameter, not a function, so that doesn't typecheck. Multivariate monotonicity isn't really a popular term, as far as I'm aware, so I'm not aware of good terminology for this. Maybe "log is non-monotonic with respect to the base"?</p>
]]></description><pubDate>Fri, 24 Jul 2026 10:17:02 +0000</pubDate><link>https://news.ycombinator.com/item?id=49033426</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=49033426</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49033426</guid></item><item><title><![CDATA[New comment by purplesyringa in "Log is non-monotonic in PHP and Lua"]]></title><description><![CDATA[
<p>Python, PHP, and Rust do indeed use libm. Not LLVM's libm specifically, though, but rather just about anything they can find in runtime, and those libraries can have suboptimal accuracy.<p>Node uses V8, which notably implements a ton of math manually, because it needs the results to be deterministic across devices. It seemingly uses LLVM's libm, which IIRC promises 0.5 ulp for most operations. (<a href="https://github.com/v8/v8/blob/f24c62fbc342d616032734b714116e1fdb891445/src/base/ieee754.cc#L24" rel="nofollow">https://github.com/v8/v8/blob/f24c62fbc342d616032734b714116e...</a>)<p>Go avoids dynamic linking, so they also have their own implementation. (<a href="https://github.com/golang/go/blob/543ead71a8e7acc2bd6f326327a090b64902b6a8/src/math/log.go" rel="nofollow">https://github.com/golang/go/blob/543ead71a8e7acc2bd6f326327...</a>) They only promise 1 ulp, but I guess in this specific case it works out better than approximations used by the default libm on their system by pure chance?</p>
]]></description><pubDate>Thu, 23 Jul 2026 19:16:34 +0000</pubDate><link>https://news.ycombinator.com/item?id=49026680</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=49026680</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49026680</guid></item><item><title><![CDATA[New comment by purplesyringa in "Log is non-monotonic in PHP and Lua"]]></title><description><![CDATA[
<p>> PHP uses a JIT from Lua.<p>Wow, TIL! I was certain this is a mistake, but apparently PHP uses DynAsm (<a href="https://wiki.php.net/rfc/jit" rel="nofollow">https://wiki.php.net/rfc/jit</a>), which was developed for LuaJIT (<a href="https://luajit.org/dynasm.html" rel="nofollow">https://luajit.org/dynasm.html</a>). Cool stuff!<p>To answer your question, probably not. I dated the PHP change that added this "optimization" back to 2014 (<a href="https://github.com/php/php-src/commit/b547e1358d3846fad4cd0c86e2d2e9f5a9039b35" rel="nofollow">https://github.com/php/php-src/commit/b547e1358d3846fad4cd0c...</a>), while DynAsm only started being used around 2019 (<a href="https://wiki.php.net/rfc/jit" rel="nofollow">https://wiki.php.net/rfc/jit</a>). I think this is just convergent evolution.<p>(I'm putting "optimization" in quotes because it changes semantics.)</p>
]]></description><pubDate>Thu, 23 Jul 2026 19:07:11 +0000</pubDate><link>https://news.ycombinator.com/item?id=49026564</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=49026564</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49026564</guid></item><item><title><![CDATA[New comment by purplesyringa in "Log is non-monotonic in PHP and Lua"]]></title><description><![CDATA[
<p>Yeah, I simplified it a little. Though I must say I'm surprised pretty much every library I looked at uses the natural logarithm specifically, and not log2, which would seemingly be easier to compute with floats. Does anyone here know why, by any chance?</p>
]]></description><pubDate>Thu, 23 Jul 2026 19:01:09 +0000</pubDate><link>https://news.ycombinator.com/item?id=49026492</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=49026492</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49026492</guid></item><item><title><![CDATA[New comment by purplesyringa in "Optimizing Lua string literals to save 400 bytes"]]></title><description><![CDATA[
<p>> small source files<p>How small are we talking? We've had great success with bzip-style compression on ~400 KB (about 20% better than gzip), but I don't know if it scales well. It's also relatively fast to decode (something like 2x slower than DEFLATE, IIRC).<p>I was also considering other approaches, specifically GLZA (<a href="https://encode.su/threads/2427-GLZA" rel="nofollow">https://encode.su/threads/2427-GLZA</a>) looks promising. I think it should be well-suited for code due to its design, and it seems to produce better results than bzip on LTCB (<a href="https://mattmahoney.net/dc/text.html" rel="nofollow">https://mattmahoney.net/dc/text.html</a>), and with a faster decompression time.</p>
]]></description><pubDate>Sun, 19 Jul 2026 21:58:57 +0000</pubDate><link>https://news.ycombinator.com/item?id=48972043</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=48972043</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48972043</guid></item><item><title><![CDATA[New comment by purplesyringa in "Optimizing Lua string literals to save 400 bytes"]]></title><description><![CDATA[
<p>We considered it, but that requires knowing the path to the file and being able to open it, which I don't think is possible in general (e.g. if the file is loaded with `loadstring`, or if it's loaded from tmpfs and then deleted, etc.).</p>
]]></description><pubDate>Sun, 19 Jul 2026 21:16:52 +0000</pubDate><link>https://news.ycombinator.com/item?id=48971725</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=48971725</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48971725</guid></item><item><title><![CDATA[New comment by purplesyringa in "Optimizing Lua string literals to save 400 bytes"]]></title><description><![CDATA[
<p>Thanks! I relayed this to Yuki and we'll make sure to fix the issues.<p>> You can include the start delimiter in the string:<p>At first I had no clue why we thought that didn't work, in fact, my prototype had a fun commit specifically about adding opening brackets:<p><pre><code>    -while b"]" + b"=" * level + b"]" in obj:
    +# Somewhat surprisingly, Lua forbids even opening brackets inside brackets.
    +while b"[" + b"=" * level + b"[" in obj or b"]" + b"=" * level + b"]" in obj:
</code></pre>
...but I think I've figured out the problem. It seems like Lua 5.1 specifically forbids level-0 opening brackets within level-0 strings:<p><pre><code>    > print [[ a [[b c ]]
    stdin:1: nesting of [[...]] is deprecated near '['
</code></pre>
...and Cobalt implements this check for compatibility. So that's another edge case to handle, I guess.<p>> Another note that this doesn't cover is ending with a part of the ending terminator.<p>That's very useful to know, thanks!<p>> So you can't just use the bracketed form to encode arbitrary byte sequences, [...] if you care about the exact representation of line breaks<p>That's right, and the post actually covers how we resolved that closer to the end. In a nutshell, we replace CRs with an escape character, and then use a bitset to denote which symbols are supposed to be CRs and which ones are literal characters. It's not quite a string <i>literal</i> per se, but it's rather cheap in runtime and minimizes file size.</p>
]]></description><pubDate>Sun, 19 Jul 2026 21:12:03 +0000</pubDate><link>https://news.ycombinator.com/item?id=48971693</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=48971693</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48971693</guid></item><item><title><![CDATA[New comment by purplesyringa in "Quadrupling code performance with a "useless" if"]]></title><description><![CDATA[
<p>> the fence basically means "resolve all funny business with this variable before proceeding"<p>Thanks, that's a good explanation! I understand it better now.<p>> What I'd actually worry most about is poisoning the prefetchers, and speculation more generally.<p>I didn't know prefetchers rely on address generation instructions, I thought they only tracked accessed memory. Good to know! Do you know any relevant external resources about this, by any chance?</p>
]]></description><pubDate>Tue, 14 Jul 2026 18:27:16 +0000</pubDate><link>https://news.ycombinator.com/item?id=48911088</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=48911088</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48911088</guid></item><item><title><![CDATA[New comment by purplesyringa in "Quadrupling code performance with a "useless" if"]]></title><description><![CDATA[
<p>> A huge part of the problem here is that you're playing with the 8-bit registers. [...]<p>That's an interesting thought, though in this particular case I think it's a little misguided. 8-bit arithmetic (or, god forbid, 16-bit arithmetic) can quickly cause unexpected stalls, but there is no arithmetic here: the only inherent difference between 8-bit and 32-bit code is the use of `movzx` versus `mov` in a memory load, which to the best of my knowledge are equivalent in performance (modulo store-to-load forwarding and such).<p>It does look like LLVM generates ugly code for address calculation, though. I don't think it's necessarily going to cause a large effect, since `movzx r8d, cl; lea rdx, [rdi + r8]` doesn't depend on the previous iteration, and the critical latency chain here is just `inc rax`, but it might affect throughput a little. It's quite unfortunate that the induction heuristics didn't do their best here.<p>> so there's an `asm volatile` style fence<p>I've seen a couple people suggest the compiler fence, and all of them did the same thing -- they put `asm volatile` <i>after</i> the assignment to `j`, not before. Could you explain your thought process here? I thought putting it before the second load would make more sense, because I can imagine<p><pre><code>    if (j != next_j[i][j]) {
        j = next_j[i][j];
        asm volatile("" : "+r"(j)); 
    }
</code></pre>
being (de)optimized to<p><pre><code>    unsigned char value = next_j[i][j];
    _Bool flag = j != value;
    j = value;
    if (flag) {
        asm volatile("" : "+r"(j)); 
    }
</code></pre>
which in turn could be compiled to `mov reg, [mem]; cmp reg, reg; mov reg, reg; je`, whereas<p><pre><code>    if (j != next_j[i][j]) {
        asm volatile("" : "+r"(j)); 
        j = next_j[i][j];
    }
</code></pre>
doesn't permit such an optimization because it forces a conditional load, which you can't avoid putting behind a branch.<p>I have no doubt that putting the fence after the assignment works in this scenario (clearly it does, since LLVM and GCC recognize it), but I don't intuitively see why.</p>
]]></description><pubDate>Tue, 14 Jul 2026 07:41:11 +0000</pubDate><link>https://news.ycombinator.com/item?id=48903418</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=48903418</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48903418</guid></item><item><title><![CDATA[New comment by purplesyringa in "Quadrupling code performance with a "useless" if"]]></title><description><![CDATA[
<p>The right-shift is the problem. You need to shift right by `j * 8`, which itself requires a shift to compute (`j << 3`), so you have two shifts on the critical path, resulting in a latency of 2 cycles. It's <i>better</i> than a load, but it's still noticeable.</p>
]]></description><pubDate>Mon, 13 Jul 2026 17:07:08 +0000</pubDate><link>https://news.ycombinator.com/item?id=48895665</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=48895665</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48895665</guid></item><item><title><![CDATA[New comment by purplesyringa in "Quadrupling code performance with a "useless" if"]]></title><description><![CDATA[
<p>I knew the loop was latency-bound and I couldn't easily decrease the latency, so I knew I had to somehow avoid the dependency chain at all. I remembered that CPUs predict some properties of memory accesses (e.g. they might predict that a store and then a load from different addresses likely don't intersect), but not addresses, so I thought about another way to force it to predict `j` well. Branch prediction turned out to be the simplest way to do so.<p>Actually, since then I've found out that I could reduce latency by replacing a load on the critical chain with a vector shuffle instruction (`pshufb`, takes just 1 cycle on x86). Ironically, if I realized that sooner, I probably wouldn't have tried to use branch prediction at all!</p>
]]></description><pubDate>Mon, 13 Jul 2026 17:02:37 +0000</pubDate><link>https://news.ycombinator.com/item?id=48895600</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=48895600</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48895600</guid></item><item><title><![CDATA[New comment by purplesyringa in "Quadrupling code performance with a "useless" if"]]></title><description><![CDATA[
<p>The optimization in the post is only advantageous if `next_j[i][j] == j` holds often enough. Without prior knowledge, the compiler can't know if it's going to improve performance, and the worst losses are greater than the best wins (branch misprediction is very expensive), so it decides not to interfere.</p>
]]></description><pubDate>Mon, 13 Jul 2026 12:55:55 +0000</pubDate><link>https://news.ycombinator.com/item?id=48891968</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=48891968</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48891968</guid></item><item><title><![CDATA[New comment by purplesyringa in "Quadrupling code performance with a "useless" if"]]></title><description><![CDATA[
<p>That would be great, if only it worked as intended! From the perspective of an optimizing compiler, `a == b ? a : b` is worse than `b` regardless of the probability you assign to `a == b`.<p>ETA: someone on Lobsters (<a href="https://lobste.rs/s/1an425/quadrupling_code_performance_with#c_4clhdw" rel="nofollow">https://lobste.rs/s/1an425/quadrupling_code_performance_with...</a>) noticed that `[[unlikely]]` actually works on LLVM (not on GCC, and with worse codegen on LLVM, but it's still good to know) -- updated the post.</p>
]]></description><pubDate>Mon, 13 Jul 2026 09:59:54 +0000</pubDate><link>https://news.ycombinator.com/item?id=48890282</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=48890282</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48890282</guid></item><item><title><![CDATA[New comment by purplesyringa in "Recovering garbled Bitcoin addresses (2024)"]]></title><description><![CDATA[
<p>Thanks!</p>
]]></description><pubDate>Sun, 05 Jul 2026 13:40:22 +0000</pubDate><link>https://news.ycombinator.com/item?id=48794265</link><dc:creator>purplesyringa</dc:creator><comments>https://news.ycombinator.com/item?id=48794265</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48794265</guid></item></channel></rss>