Log In  

Since P8 has a 32k limit on numbers, what's the best way (or a way) to have numbers in the 100 thousands or millions and so on?

I mean, I guess I could super fake it by just printing out zeros after a low score integer but is that really the best way to deal with it?

P#22603 2016-06-10 09:43 ( Edited 2016-08-15 09:52)

Well, you've got two ways...

  1. Fake it!
  1. Use a combination of 2 variables that can preferably calculate to 9999.
P#22604 2016-06-10 09:48 ( Edited 2016-06-10 13:48)

This is from my Pico Racer game (though it actually uses tens of thousands):

function addscore(points_to_add)
    ones += points_to_add
    while ones >= 1000 do
        ones -= 1000
        thousands += 1

It will of course overflow if you have e.g. 999 in ones and try to add 32000 points. If you want to avoid the overflow with huge scores you need to deal with the thousands as well when adding.

Then you have to print the thousands and ones so that the ones are zero padded (000, 001, 002 etc.). Didn't test the following but it should work:

local score_string = "00"..ones
print(thousands..sub(score_string, #score_string - 2,#score_string))

EDIT: fixed a stupid typo in addscore()

P#22606 2016-06-10 10:27 ( Edited 2016-06-10 16:11)

In combo pool, I tried to simply store the score divided by 100, so using the fractionnal part to have more values. It's not totaly accurate but it was working good enough. I also added a 0 to make the score more impressive. Here is the code to display it, but there is probably a better way :

mulint = 100
-- when adding score :
score += addscore/mulint

function getscoretext(value)
    local fracscore = flr((value%1)*mulint+0.5)
    local textscore = (fracscore%mulint)..""
    local floorscore = flr(value) + flr(fracscore/mulint)
    if floorscore > 0 then
        if(#textscore<2) textscore = "0"..textscore
        textscore = floorscore..textscore
    return textscore

-- then displaying the score :

In theory you could divide by 2^16 I think, but retrieving the number to display it may become hard, if someone as a simple code for that, it would be great.

P#22607 2016-06-10 10:57 ( Edited 2016-06-10 15:05)

Ok, I did some more tests with only one value to store the score, and I have something working prety well until about 300 millions. I check accuracy as well as I could. You must never add more than 32k to the score at a time obviously.
Here is the code :

mulint = 2^14

-- to add an integer value to the score:
score += addscore/mulint

-- convert a number to a string, padding with 0 to reach size n:
function tostr(v,n)
    local s = ""..v
    local t = #s
    for i=1,n-t do
    return s

-- get the text from a score value:
function getscoretext(val)
    local low = (val * mulint)%mulint
    local high = flr(val)
    local begin = flr(high*1.6384)
    local last = flr(((high * 1.6384)%1)*10000+0.5)
    local nlow = low+last
    local rest = nlow%10000
    local nhigh = begin+flr(nlow/10000)

    return tostr(nhigh,5)..tostr(rest,4)

-- print a score:

Tell me if you find any errors

P#22612 2016-06-10 13:37 ( Edited 2016-06-10 17:37)

@kometbomb: that's a really simple yet elegant solution, I like it! Might actually have to steal that one for my games :P

P#22613 2016-06-10 13:42 ( Edited 2016-06-10 17:42)

@NuSan: Have you tried something like just printing the number but skipping the decimal separator? E.g. 35300.3523 -> 353003523

@qbicfeet: Please do. :) (Not that I invented anything there, it's just carrying the number)

P#22614 2016-06-10 14:04 ( Edited 2016-06-10 18:04)

@kometbomb I didnt try that, but the precision displayed by a print of a fractionnal is not great. And asking a decimal version of a fixed point encoded value will always give an approximation.

I also quickly made a small "library" following your method, and it maybe easyer to use. Here it is :

function newscore()
    local s = {u=0,k=0}
    s.add = function (this,p)
            this.u += p%10000
            this.k += flr(p/10000)
            while this.u>=10000 do
                this.u -= 10000
                this.k += 1
            while this.u<0 do
                this.u += 10000
                this.k -= 1
    s.text = function (this)
            local vunit = this.u
            local kilo = ""..this.k
            if this.k<0 then
                kilo = "-"..(abs(this.k)-1)
                vunit = abs(((this.u)-10000))
            local units = "000"..vunit

            return ""..kilo..sub(units, #units-3,#units)
    return s

-- here some sample use :
score = newscore()

P#22617 2016-06-10 14:44 ( Edited 2016-06-10 18:44)

@NuSan: Yeah, I did some testing and adding 0.01 for one point gets inaccurate really quick.

I was just thinking over the morning coffee, remembering programming in 6502 assembler that there could be a function that updates a number and returns the carry like this:

function adc(number,add)
  number += add
  local carry = 0

  -- could probably get the carry 
  -- with flr(number/1000) but I 
  -- am not 100 % sure so let's
  -- be naive

  while number >= 1000 do
    carry += 1
    number -= 1000

  return carry

And now you can cascade it. E.g.:

ones = 0
thousands = 0
millions = 0
billions = 0

adc(billions, adc(millions, adc(thousands, adc(ones, p))))

And you can similarly come up with a function that prints with zero padding (it's the same for the ones, thousands, millions etc.). This wastes a lot of space compared to your magic, might be relevant when saving e.g. a TOP 50 list in the cart memory.

P#22659 2016-06-11 03:19 ( Edited 2016-06-11 07:19)

Edit: Left for education/posterity, but use this one further below, it's better.

Just use all 32 bits of the S15.16 fixed-point numbers. Under the hood, they're just integers with an implied decimal point between bits 15 and 16.

If you enjoy retro programming, you should definitely learn more about fixed-point numbers. I'll give a quick overview, but I'm a terrible teacher and I strongly suggest learning about fixed-point math on your own time.

I'll have a TL;DR at the end for those who just want to know how to solve this particular problem for now.

PICO Lua holds values where the integer portion is shifted left 16 so it can rest in the upper 16 bits of a native 32-bit integer on the real cpu. Let me give you an example...

Let's say you want to assign 10 to x:

x = 10

You're actually saying something like this to the Lua interpreter:

x.internal = 10 << 16

Or, if we write it directly in hex:

x.internal = 0x000a0000

The lower 16 bits are the fraction. For integer values in lua, it's always going to come out 0x????0000. For non-integers, you simply multiply the real fraction portion by 65536 (0x10000) and those are the bits to put in the lower 16.

For instance, if you want to encode 10.5, the fractional portion is .5, and .5 * 65536 is 32768, or 0x8000. So...

x = 10.5


x.internal = 0x000a8000

As it happens, multiplying by 65536 IS shifting left 16. You're not really doing two operations, one for the integer and one for the fraction. It's all just one shift by 16.

Helpfully, Lua lets us write non-integers with hexadecimal. So this:

x = 10.5

Could actually be written like this:

x = 0x000a.8000

If we simply take those bits and shift them left 16 bits, which is the same thing as shifting the decimal point right 16 bits, we can see how simple the underlying operation really is:

x.internal = 0x000a.8000 << 16 = 0x000a8000

So if you want to store something in those bottom bits, you just have to do as a fraction. Hex fractions are easiest to deal with for literals, so I suggest you use them. If you don't know hex... well, you should learn hex. :)


We need to store integer values in the bottom 16 bits, in the fraction. We can do this by (conceptually) shifting the integer portion right 16:

  • Think: need to add 100 to massive score

  • Do:
    score += 100 >> 16

    --or, using a hex literal directly
    score += 0x0000.0064


  • Think: need to print score

  • Do:
    local s = ""
    local v = score
    while (v!=0) do
        -- modulo by the underlying version of 10
        local r = v % 0x0000.000a
        -- bring it up where lua will stringify it correctly
        r <<= 16
        -- prefix the new digit onto the score string
        s = r..s
        -- move to the next digit
        v /= 10

This math shouldn't lose any precision because it's all integers under the hood, no underflows or anything.

You guys were having issues with using tens and hundreds and thousands because they aren't powers of two, but shifting is all about powers of two and it can always be done cleanly. The only time we use tens is to convert the value to a human-readable decimal string.

P#22666 2016-06-11 05:49 ( Edited 2020-10-12 02:03)

Addendum: If you tried that code before I wrote this post, I missed the SHL() in the printing code, sorry it didn't work without that. It's fixed now.

P#22667 2016-06-11 05:57 ( Edited 2016-06-11 09:57)

Nice one Felice! I was just in the middle of writing up the same approach, but struggling over how to do the conversion back to string, and you nailed it!

P#22668 2016-06-11 06:22 ( Edited 2016-06-11 10:23)

@kometbomb : that's a prety simple way to extend the number as high as you want, wich can be usefull.

Felice : thanks, I knew there was a clean way to extract the string. Once you think about extracting each digits separatly, it become way easyer.

I tried changing it to handle negative score, tell me if (or when ^^) you see a cleaner version :

function getscoretext(val)
  local s = ""
  local v = val
  while (v!=0) do
    -- modulo by the underlying version of 10
    local r = v % 0x0000.000a
    -- bring it up where lua will stringify it correctly
    r = shl(r, 16)
    -- prefix the new digit onto the score string
    if val<0 then
            s = ((10-r)%10)..s
        s = r..s
    -- move to the next digit
    v /= 10
  if val<0 then
    s = "-"..s
  return s 
P#22675 2016-06-11 07:11 ( Edited 2016-06-11 11:11)


Edit: Use this one further below, it's better.

This is the smallest I can make it, for the sake of tokens+mem:

 function getscoretext(val)
   local s = ""
   local v = abs(val)
   while (v!=0) do
     s = (v % 0x0.000a << 16)..s
     v /= 10
   if (val<0)  s = "-"..s
   return s 

Note that you could obviously just draw your score to screen during the loop, to avoid making half a dozen temporary strings, since Lua annoyingly insists strings must be immutable and constantly makes new copies when you manipulate them.

P#22677 2016-06-11 07:30 ( Edited 2020-10-12 02:01)

@Felice : the code is so short and clean now, thanks. I think I will reuse that each time I need to store score in a game now.

P#22680 2016-06-11 10:38 ( Edited 2016-06-11 14:38)

My mind is blown. Amazing stuff guys

P#22715 2016-06-11 20:42 ( Edited 2016-06-12 00:42)

So I just got around to trying all of this out and I think it's working...thanks to everyone that contributed. Lord knows I would have never come up with such a elegant solution. All that math and Lua knowledge is beyond me.

Here's how I'm using it.

I use the shr() function to add to my score. So here, adding 15,000 points.

score += shr(15000,16)

Then I use Felice's function to display it

print(getscoretext(score), 0,0, 7)

The only change I made to the function was so that it would display a zero score. I just changed the val check to be val<=0 and instead of the result being the dash, it's just a 0 character. Seems to work fine.

The only thing I did notice is that if you add a number greater than 32,000 it gets wonky...which makes sense. As long as I don't give any single thing a point value greater than 32k, it's all good.

Thanks again, everyone. I know this will be the defacto thread for anyone looking to have nice, big, fat scores.

P#22805 2016-06-12 22:24 ( Edited 2016-06-13 02:24)

val<0 is for negative scores.

Felice function needs a special case for val==

Actually I think they should be v not val?

P#22806 2016-06-12 22:32 ( Edited 2016-06-13 09:14)

You're right that it needs a 0 condition. However, the loop ends when v==0, so you can't use v to check for 0 or sign. That's why it refers back to the original val to find the sign to prefix. It should also use val for the 0 condition.

P#22808 2016-06-13 00:13 ( Edited 2016-06-13 04:17)

Edit 2020-10-13: Updated to my most recent versions.

The fix would be to use a repeat..until loop instead of while..do. E.g.:

function s32_tostr(v)
    local s,t="",abs(v)
    until t==0
    return v<0 and "-"..s or s

(39 tokens)

That way it always does one iteration and that covers the 0 case.

Caution: this s32 version fails on the single so-called "wrap", "outpost", or "weird", value: 0x8000.0000, but that's not something most people need to worry about. If you do need to handle that value, you could simply add if(val==0x8000) return "-2147483648" to the start of the function. Alternatively, you could spend about 15 tokens more and use the u32+s32 combo code in the final section of this post, which does not have this issue.

Here's a u32 version. It does its divide-by-10 in two operations, rather than one. It starts with a logical shift right by 1, which is effectively an unsigned divide by 2. This ensures the top bit is 0 and the number is now unquestionably positive. That means we can safely divide by 5 using the normal (signed) divide operator., producing a number we can safely assume is positive. Second, it divides by 5 using the normal (signed) divide operator, which , effectively producing an unsigned divide by 10:

function u32_tostr(v)
    local s=""
        local t=v>>>1
    until v==0
    return s

(41 tokens)

And finally, for those who need both, around 20 tokens can be saved by having the s32 version rely on the u32 version:

function u32_tostr(v)
    local s=""
        local t=v>>>1
    until v==0
    return s

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

(61 tokens total)

As a bonus, this s32 version doesn't suffer from the 0x8000.0000 wrap-point problem.
keywords: unsigned 32-bit int unsigned int unsigned 32-bit integer unsigned integer signed 32-bit int signed int signed 32-bit integer signed integer u32 s32 i32 int32 uint32 int32_t uint32_t dword long

P#22809 2016-06-13 00:14 ( Edited 2020-10-13 22:42)

I've just realized that I basically failed to implement itoa() correctly due to missing an obvious edge case. I have brought shame on my CS104 professor.

Speaking of edge cases, this function will also fail if your player has -2147483648 points. So be aware of that. Just in case it happens.

P#22810 2016-06-13 00:25 ( Edited 2016-06-13 04:25)

Nice fix!

I've removed my untested, bad code. :)

P#22819 2016-06-13 05:15 ( Edited 2016-06-13 09:15)
P#26789 2016-08-12 08:26 ( Edited 2016-08-12 12:26)
P#26807 2016-08-12 14:32 ( Edited 2016-08-12 18:33)

@moechofe care to talk us through your approach?

P#26812 2016-08-12 17:44 ( Edited 2016-08-12 21:44)

Sure, I'm using an array to store the value of each digit [0-9], and an array to store the sprite index (it's mandatory).


Next, i'm using a function that add a value to the score. It increment each digit until it reach (1)0, then increase the next digit, from the lowest to the greatest (right to left):

function add_score(h)
  local s,i,d=sprites,9,digits
  if(h<0)printh("ERR: add_score >32787")return
  while h>0 do
    local a=h%10
    h=flr(h/10) i-=1

Take care, if the added value is too big, it will be a negative value.

Hope it helps.

P#26829 2016-08-13 03:56 ( Edited 2016-08-13 07:56)

nice approach!

P#26933 2016-08-15 05:52 ( Edited 2016-08-15 09:52)

Based on the digits idea, I made a score control where you can add large numbers:

function _init()    
    score_ctrl = score_control() 

    -- ===================================================
    -- scores can be added as a number if they are <=32767    
    -- ===================================================

    -- =====================
    -- otherwise use strings    
    -- =====================

    print(score_ctrl.score)  -- result 1315647

function score_control()
    local digits={} 


        local str = tostr(points)         
        if (#digits<#str) then
            for i=1,#str-#digits do
        while (#str<#digits) do 
        for i=1,#str do
        local score,overflow="",0
        for i=#digits,1,-1 do
            local c,h=digits[i]%10,digits[i]\10
            if (i>1) then digits[i-1]+=h else overflow=h end
        if (overflow>0) then
        sc.score = score

    return sc
P#136488 2023-10-27 13:21

we have a much easier way now! https://www.lexaloffle.com/dl/docs/pico-8_manual.html#TOSTR


-- smallest number possible, represents 1 point
-- 100 points for boss defeat!

-- show big number
P#136497 2023-10-27 14:32 ( Edited 2023-10-27 14:34)

After seeing threads like this, I knew I needed to put this post up.


I think you can add the number of digits up to the limit of the strings that can be combined.

?strdsum('900109','123456') -- 1023565
P#136523 2023-10-28 06:05 ( Edited 2023-10-28 06:26)

[Please log in to post a comment]