Log In  

*edit 2: 0.2.3 has a built-in way to do this with tostr

edit: see downthread for a better function

I was thinking about high scores in PICO-8 a while ago and it occurred to me that they'd make more sense as unsigned 32 bit integers than 16b.16b fixed point decimals. The easy part in that case is adding points - simply increment in units of 0x0.0001 instead of units of 1 - but if it's a high score, I'd also like to be able to display it.

Thus:

function tostr_u32(n)
    -- return n as a 32-bit uint
    -- 92 tokens, ~1/780 of a 30 FPS CPU per call
    -- " " as thousands divider

    -- calculate ones
    -- (0x.03e8 = 1000 * 0x0.0001)
    local s=tostr(shl(n%0x.03e8,16))
    if n>0 then
        n/=1000
    else
        -- if not-actually-a-sign-bit is set
        -- have to be a little tricksy

        -- splitting in half
        --  n&0x0.ffff lower
        --  lshr(n,16) upper
        -- upper half unit = 65 536
        -- so within thousands:
        local m=536*lshr(n,16)
        m+=n&0x0.ffff
        -- originally used
--      local m=536*lshr(n,16)+n&0x0.ffff
        -- but that returned wrong results
        s=tostr(shl(m%0x.03e8,16))
        -- and doing the division by
        -- 1000 in two steps:
        n=lshr(n,1)
        n/=500
    end

    while n~=0 do
        while #s%4~=3 do
            -- pad with zeros
            s="0"..s
        end
        s=tostr(shl(n%0x.03e8,16))
            .." "..s
        n/=1000
    end

    return s
end

I'm sure this could be minimized further and/or optimized further and/or made more general, but I'd be willing to use it as is so I figured I'd share.

P#82745 2020-10-09 19:53 ( Edited 2021-09-10 12:40)

1

simpler version (not using a string conversion on top): https://www.lexaloffle.com/bbs/?pid=22677

P#82760 2020-10-10 05:37

This version from downthread looks like it's solving more-or-less the problem I was thinking of, yeah - signed int instead of unsigned int, but yeah

Thanks for the link!

P#82774 2020-10-10 13:46

Unsigned int would definitely be trickier since we don't have access to any unsigned math on PICO-8. Hmm.

I'll have a think about this. It seems like a neat & tidy little puzzle to try to solve!

P#82811 2020-10-11 23:16
1

Edit: for a better u32 version, see the revised one I did further below in this thread after @packbat had a better idea.

Hm, this is my current solution for u32. I nip off the top bit, convert it, and then if the top bit was set, I use longhand addition to add 2147483648 (0x80000000) to the output string.

function u32_tostr(u)
    local s,v="",u&0x7fff.ffff
    repeat
        s=(v%0x0.000a<<16)..s
        v/=10
    until v==0
    if u<0 then
        local a,b="2147483648",sub("000000000"..s,-10)
        s,v="",0
        for i=10,1,-1 do
            v=sub(a,i,i)+sub(b,i,i)+v\10
            s=(v%10)..s
        end
    end
    return s 
end

I'd like to think there's a more elegant solution, but I haven't thought of one.

At 83 tokens and roughly double the work for upper-range numbers, it's definitely heavier than the most current version of my s32 function that you linked above, at 39 tokens.

P#82814 2020-10-12 00:21 ( Edited 2020-10-13 21:08)

I like that method! Thinking about the addition step: how about doing the addition during the first divide-by-ten?

function u32_tostr(u)
    local s,v="",u&0x7fff.ffff
    local w=v%0x.000a
    v/=10
    if u<0 then
        w+=0x.0008
        s=(w%0x.000a<<16)..s
        v+=w/10
        v+=0xccc.cccc
    else
        s=(w<<16)..s
    end
    while v>0 do
        s=(v%0x.000a<<16)..s
        v/=10
    end
    return s 
end

It looks like it cuts tokens to 74 and speeds things up substantially, and it means that the s=[digit]..s lines can be replaced with printing directly to screen if desired.

P#82815 2020-10-12 01:07 ( Edited 2020-10-12 01:10)
1

Oh, nice! I like that better! Saves 9 tokens too.

P#82816 2020-10-12 01:15
1

@packbat

Edit: Improved AGAIN in the next post.

Wait, I have a better one that builds off of your idea! Since 10 is 5x2, we can do a logical shift right as part of the first /10 and then divide by 5, letting us evade the problem with the top bit:

function u32_tostr(u)
    local v=u>>>1
    local s=(v%0x0.0005<<17)+(u<<16&1)
    v/=5
    while v!=0 do
        s=(v%0x0.000a<<16)..s
        v/=10
    end
    return s
end

47 tokens! :D

Also, a lesson for the day: You can concatenate two number variables into a string, even though you can't concatenate two number literals. Weird but true. I wonder if this is a parser bug, since the runtime obviously allows it.

P#82817 2020-10-12 01:35 ( Edited 2020-10-12 02:59)
4

Wait. Now that I know how to do an unsigned /10, I can just put that in the original function and not have any excess!

function u32_tostr(v)
    local s=""
    repeat
        local t=v>>>1
        s=(t%0x0.0005<<17)+(v<<16&1)..s
        v=t/5
    until v==0
    return s
end

41 tokens! :D

Added to my original post in the other thread.


Edit: Just to be comprehensive, so someone who lands on this thread from a search can easily get both versions, here's the signed version also:

function s32_tostr(v)
    local s,t="",abs(v)
    repeat
        s=(t%0x0.000a<<16)..s
        t/=10
    until t==0
    return v<0 and "-"..s or s
end

39 tokens.


Edit 2: And if someone needs both functions, 20 tokens can be saved by using the u32 version to support the s32 version:

function u32_tostr(v)
    local s=""
    repeat
        local t=v>>>1
        s=(t%0x0.0005<<17)+(v<<16&1)..s
        v=t/5
    until v==0
    return s
end

function s32_tostr(v)
    if(v<0) return "-"..u32_tostr(-v)
    return u32_tostr(v)
end

Total 61 tokens.

P#82819 2020-10-12 01:55 ( Edited 2020-10-13 22:58)

Oh heck yea! I like that a lot - no magic numbers, just a little sidestep to ensure the sign bit acts as a numeric bit instead. That's very cool!

P#82822 2020-10-12 02:19
1

Wow - you guys made an awesome pair!
so 4,294,967,295 maximum score now? ;)

P#82827 2020-10-12 06:50
1

@freds72

Yup! This is why people always say to use pair programming when you have a tricky problem. It's all about that word that marketing demons misuse: Synergy!

P#82832 2020-10-12 09:20

@freds72: so 4,294,967,295 maximum score now? ;)

...oh, wow, someone ought to make a clicker game that has a game mechanic that kicks in when you hit 4,294,967,296 and the number of picos or whatever drops back near zero. That would be extremely appropriate.

Also thanks! It was quite delightful and validating to see someone else looking at PICO-8 numbers and going, "yes, reverse-engineering 32-bit unsigned integers out of these is a good idea and I like it". :D

P#82840 2020-10-12 15:25
1

Heads up that if you need both functions in one app, I added a (fairly obvious) version that uses one to support the other in my post above. It saves 20 tokens vs. having both independent functions.

P#82910 2020-10-13 22:59 ( Edited 2020-10-13 23:04)

About this detail:

> You can concatenate two number variables into a string, even though you can't concatenate two number literals

We get 'syntax error malformed number' because a dot is used for fixed-points literals (like '20.1'), so two dots ('20..10') is rejected (which seems a good decision, could be a typo). This can be worked around by adding parens ('(20)..10' → string '"2020"') or a space ('20 ..10' — idea taken from Python, where you can call methods on number literals like this: '20 .bit_length()').

P#82936 2020-10-14 15:36

@merwok

Oo, good to know. And yeah, I should have realized it was because of decimal points in number literals. Mind you, I think the parser ought to be able to detect the difference between a decimal point and a concatenation, same as it can tell the difference between a greater-than operator and a shift-right operator.

P#82962 2020-10-15 14:52

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2024-03-29 11:14:30 | 0.047s | Q:30