A few days back @dw817 posted a thread about the number of possible combinations in 256 bytes in which they asked:
> Is there a way to calculate 256^256 ?
And knowing there are all sorts of tools and languages which can handle such huge numbers I cheekily posted the answer and caused a bit of a derail.
Anyway, I had some time on my hands so I figured I'd implement large number support for Pico-8.
Edit: Fixed a bug in divide()
Is this likely to be of use to anybody? Probably not. Is it major overkill? You betcha! Though in fairness, at 706 tokens it came out smaller than I was expecting.
Features/Functions
-
bignum(n)
: Create a 'bignum' with valuen
which is just a regular Pico-8 number. Most operations require both numbers to be bignums. Sobignum(16000)*bignum(1254)
but notbignum(16000)*1254
.bignum()
is equivalent tobignum(0)
-
divide(bn)
: You can use the division and mod operators (/, %) on bignums if you only need one or the other. Thedivide
function returns both the quotient and remainder at once.q, r = divide(bignum(3)^16, bignum(2)^15)
show(bn)
: Converts a bignum into its decimal representation as a string.factorial(n)
: Calculates the factorial ofn
and returns the result as a bignum. Factorials get very big, very fast so the input tofactorial
is just a regular number.+ - / % ^
: Arithmetic operators behave how you would expect. All require both arguments to be bignums with the exception of^
where only the first argument is a bignum and the second is a regular number. Division is integer division only sobignum(3)/bignum(4)==bignum(0)
.> >= <= < ==
: Comparisons similarly require both arguments to be bignums.
Issues
- The arithmetic operators are all fairly efficent but probably not optimally so.
- I wasn't taking negative numbers into account at all so I make no promises about what will happen if you try to use them. Maybe it will work! (It probably won't work.)
show
is extremely inefficient. I'm sure there are better ways to convert very long binary strings to decimal but I'm not familiar with them so this is what I've got. Large numbers (like 52!) may take a bit of time and very large numbers will crash the program as it runs out of memory. (bignum(256)^256 does this, sadly. It calculates the number fine but crashes when you try to convert it.)
Hi @jasondelaat:
This is quite good ! Gold star.
And ... thank you for the shout out. :)
To be fair I wrote my own cobbled versions of this years ago, not doing exponents but addition, subtraction, multiplication, and division.
https://www.lexaloffle.com/bbs/?tid=35680
https://www.lexaloffle.com/bbs/?tid=35715
https://www.lexaloffle.com/bbs/?pid=101378
Do you think you could write out a function that just and merely handles a string ?
And this string could have something like:
calcintdigits=64 -- allow 64-digits to left of decimal calcfloatdigits=64 -- allow 64-digits to right of decimal calcfalse=0 -- for use in logic statements below calctrue=1 -- for use in logic statements below calcna="nil" -- for use in not assigned variables ?calc("1+2") -- simple math ?calc("pi") -- return 64-digit floating point result ?calc("2+3*4") -- take into account order of operations, answer is 14. ?calc("(2+3)*4") -- parentheses, answer is 20 ?calc("2+(3*4)") -- parentheses, answer is 14. ?calc("1+2*3/4") -- return fractional, answer 2.5 ?calc("rem(82/9)") -- return remainder, answer 1 as 81/9 is 9 and 82/9 = 9.1111. ?calc("2^3") -- 2 to the power of 3 ?calc("sqrt(81)") -- square root of 81 ?calc("a=2+3*4") -- assign virtual variable a this value ?calc("a") -- return value of a ?calc("a+1") -- return value of a with math ?calc("basic("1+2*3/4") -- ignores operation order, goes from left to right, = 2.25 ?calc("revbasic("1+2*3/4") -- ignores operation order, goes from right to left = 3.6667 ?calc("1>0") -- logical statement. Returns value TRUE as listed above. ?calc("not(1>0)") -- would be the reverse. Returns value FALSE as listed above. ?calc("!a") -- for NOT a which is assigned a value would return FALSE. ?calc("b") -- if unassigned will return value NIL as listed above. |
Standard flr(), rnd(), sin(), cos(), atn(), etc. are also available.
When an error is found, value divided by 0 for instance, return "div by zero."
If you try to calculate bigger than the value ranges you have set for calcintdigits and calcfloatdigits, return "rangeint" or "rangefloat" from string, depending upon which rule is broken.
When an error is found, two pluses beside each other for instance, return "syntax" from string.
If a parenthesis is in error in a calculation return "short" if there are too few of them and "long" if there are too many of them.
Assigning a variable returns the calculation, for instance the a=2+3*4
above would return the value 14 in addition to assigning virtual variable A this value. If it is an error follow the rules above.
Virtual variables can be any length but must start with a letter and then a letter or digit. Examples, a, b, c, apple, bear, pos873, d0, e1, etc.
In ignoring operation order, parentheses are also ignored.
And yes, as you have it you cannot add or subtract numbers direct, it must always be a string.
a="1+2" b="3/4" c=calc(a) d=calc(b) e=calc(c.."*"..d) ?e |
2.25
(It wouldn't do anything if embedded on the BBS so you can just save the png if you want to play with it.)
This isn't exactly what you're asking for and doesn't have all the features you mention because I just threw it together. I went with a lisp-like s-expression syntax for two reasons: Partly because writing an s-expression parser is relatively easy but mostly because I'd already written one for another project.
If you're not familiar with s-expression they're pretty easy. Operations and their arguments are enclosed in parentheses separated by spaces. The first argument is the operation to perform, the rest are the arguments to perform it on. (AKA prefix notation rather than infix notation.)
So to add some numbers:
calc('(+ 123 456)') -- 123 + 456 |
This has the advantage that, for operations which permit it, you can just string a whole bunch of values together. This will just add up all the value, for instance:
calc('(+ 123 456 789 10)') -- 123 + 456 + 789 + 10 |
As I've written it the operations which allow it are +, *, and -.
Arguments can be nested expressions:
calc('(+ 123 (* 456 789))') -- 123 + (456 * 789) |
Since the whole thing is just wrapping the bignum operations I added an integer parsing function so you can enter large numbers directly.
calc('(+ 123456 987654)') |
And I'm abusing _ENV so, as long as your variables are global, you can just reference variables in the string and it should work like so:
a = calc('(+ 123 765)') print(calc_d('(* a a)')) |
There are two functions which do basically the same thing, calc
and calc_d
. The only difference is that calc_d
(for display) converts to a string and returns that while calc
returns the actual bignum object itself. If you're using the variable trick above you want to use calc
on anything you're assigning to a variable but calc_d
is fine for anything you just want to print out.
Hi @jasondelaat:
I downloaded and ran your code. No output.
Tried ?calc("1+2")
and ?calc_d("1+2")
both came back with an error message:
Attempt to index local 'ast' (a nil value)
Yeah, the cart is just function definitions hence posting the png image rather than embedding the cart.
As I said, it's not exactly what you asked for. The expressions need to be formatted like lisp s-expressions meaning even simple expressions need to be enclosed in parentheses and the operation always comes first with the arguments coming after.
So instead of ?calc_d("1+2")
it needs to be calc_d("(+ 1 2)")
Have a look at the examples in my previous comment to see more about how it works.
I did it this way because s-expressions are very easy to parse and I'd already written an s-expression parser so I just took the path of least resistance.
Hi @jasondelaat:
Oh ! I didnd't even notice the formatting you had above.
Hey, this really works ! That's quite interesting. But yep, it will definitely be better if someone could code it for normal 1+2
and (1+2)*3
[Please log in to post a comment]