Try the following code, in which a negative number is repeatedly multiplied by a fractional value:

k=-1 for i=1,33 do k*=0.5 end print(k.." or "..tostr(k,true)) |

As of 0.1.11d, you'll end up with "-0 or 0xFFFF.FFFF" as your output, or negative zero.

There are arguments for the concept of negative zero, particularly when you're working with floating-point numbers—but I'm not sure it makes sense within the context of Pico-8's fixed-point values. Unlike unsigned zero, negative zero is unstable in a fixed-point representation. If you multiply 1000 by 0, you get zero. But if you multiply 1000 by negative zero (0xFFFF.FFFF), you get -0.0153.

In other words, unless you handle negative zero as a special case (e.g. compare to an epsilon value), it's possible you'll introduce numeric drift.

It also leads to other weird behavior. Try this:

k=-1 for i=1,33 do k=shr(k,1) end print(k.." or "..tostr(k,true)) |

The output will also be negative zero, which makes very little sense to me. Bit-shifting past the range of the number should yield 0; if I were using k as a bit field, e.g., I wouldn't necessarily want a shift-right to mean "multiply by 1/2."

SHORTER: it seems like negative zero in Pico-8 is either a bug or just a bad idea. But maybe someone can explain why it should be there?

It's not actually -0, but -0.0000152587890625 (-2^-16). You can see the same problem with positive numbers (there's at least one "positive 0" that's not actually zero):

k=1 for i=1,16 do k=shr(k,1) end print(k.." or "..tostr(k,true)) print(k*1000) |

These aren't == 0, they just show up as zero when you convert them to a string. The issue is with the number formatting code in PICO-8, which should probably output something like "-0.000015" for your example (and "0.000015" for mine).

For the example you cite, in which i loops from 1 to 16: yes, you'll end up with the smallest possible positive non-zero value that Pico-8 can represent, since there are 16-bits of fixed-point precision in the decimal place. (And yes, you're right that the number formatting code does not print the value accurately.)

But make i loop from 1 to 17 and then k will converge to 0, which makes sense when k starts as 1. What I'm pointing out is that when k starts as -1, it will never converge to 0 no matter how long the loop is. Instead it converges to 0xFFFF.FFFF, which prints as "-0" but is actually a very small non-zero negative value.

In other words, regardless of how the value is printed, positive and negative numbers do not behave in a symmetrical manner in Pico-8.

I see. The reason this happens is that shr is an "arithmetic shift", which keeps the top bit in place as it shifts. From the Wikipedia article:

*However, arithmetic right shifts are major traps for the unwary, specifically in treating rounding of negative integers. For example, in the usual two's complement representation of negative integers, −1 is represented as all 1's. For an 8-bit signed integer this is 1111 1111. An arithmetic right-shift by 1 (or 2, 3, …, 7) yields 1111 1111 again, which is still −1. This corresponds to rounding down (towards negative infinity), but is not the usual convention for division.*

Ahhhh--an arithmetic shift as opposed to a logical one. Thanks, that would explain a lot. The Pico-8 manual says something contradictory and should probably be corrected:

shl x y shr x y // shifts are logicial shifts (the sign bit is not shifted) |

"I see. The reason this happens is that shr is an "arithmetic shift", which keeps the top bit in place as it shifts."

Oh this explains something I was confused about the other day when I was trying to implement a linear feedback shift register and it wasn't working for numbers that used the full 16 bits but was for smaller numbers.

Yeah, the manual is just plain wrong. Probably a quarter of the carts in existence would break if SHR() suddenly became logical.

It'd be nice if @zep added an LSR(), or some similar name, that did logical shifts, rather than forcing you to BAND() away the top bit(s) after SHR().

...but it'd also be nice if there were a CEI() function, rather than having to do -FLR(-x), but we can't seem to get any traction on that simple request, so I'm not holding my breath for an LSR().

yep, zep could just add LSL, LSR, ROL, ROR to the API and just leave SHR/SHL as is...

(edit) and ceil and tan.

Don't even need LSL(), since shifting left is sign-agnostic.

But yeah. It's hard to believe that a 4x4 dither pattern in hardware is legit for the platform, but it'd be too advanced and expands the feature set too much to have CEI() and LSR().

C'mon, zep...

(Hell, there's a ton of stuff that's very simple to write, so simple that even newbies can work it, out, and which the majority of carts use. All it is is wasted tokens and time. The handful of functions we've listed aren't nearly all of them. Even C64 BASIC had stuff not in PICO-8's API. I'd list off stuff but I know it's gonna be met with silence and I hate wasting my time.)

[Please log in to post a comment]