14
\$\begingroup\$

Time for you to implement my new stack based language! It's called StackyMath. This will be a stack based language with 8 operations on the stack and ways to add numbers to the stack.

List of operations:

  • /: Division. Performed on the top 2 numbers of the stack. Pushes the result back on the stack.
  • *: Multiplication. Performed on the top 2 numbers of the stack. Pushes the result back on the stack
  • -: Subtraction. Performed on the top 2 numbers of the stack. Pushes the result back on the stack
  • +: Addition. Performed on the top 2 numbers of the stack. Pushes the result back on the stack
  • ^: Exponentiation. Performed on the top 2 numbers of the stack. Pushes the result back on the stack
  • %: Modulo. Performed on the top 2 numbers of the stack. Pushes the result back on the stack
  • !: Factorial. Performed on the top number on the stack. Pushes the result back on the stack
  • D: Duplicate the top number on the stack

Operations defined in pseudo code:

  • /: push(pop divided by pop)
  • *: push(pop times pop)
  • -: push(pop minus pop)
  • +: push(pop plus pop)
  • ^: push(pop to the pop)
  • %: push(pop mod pop)
  • !: push(factorial pop)
  • D: t = pop; push(t); push(t)

How to push numbers to the stack:

Adding numbers to the stack is easy, just put the raw number in your program where you need it. If you need to put multiple numbers on the stack you can separate them with a comma (,). Your program will not need to process - numbers in the input, If the user wants one they should push the number they want negated, zero, and -. Numbers in the input of the program are also constrained to positive integers.

Input:

Your program should take the input on the command line, or from std in. Input will only consist of numbers (no scientific notation or decimals) delimited by , as needed, and the operations defined above.

Output:

Your program should print the number on the top of the stack.

Error cases:

  • If the program tries to over-pop the stack, you should print StackUnderflowException!!! .
  • If you have division by zero, print DivisionByZeroException!!!
  • If a number that exceeds 64-bits, either while executing the program or processing a number in the input, print NumberOverflowException!!!
  • If somehow you get a negative number on the top of the stack and you need to do a factorial, print NegativeFactorialException!!!
  • If you have a floating point number on the tops of the stack and the next operation is factorial, print FloatingFactorialException!!!
  • If no numbers are on the stack when the program exits (i.e. the program was empty) print EmptyProgram!!!

Notes:

  • All error output to should got yo std err or closest equivalent.
  • All numbers are constrained to 64-bit floating point.

Example programs:

50,47*                 -> 2350
50,47/                 -> 0.94
100,8!                 -> 40320  
100D*                  -> 10000
!                      -> StackUnderflowException!!!
5,2/!                  -> FloatingFactorialException!!!  
4,3!2*/                -> 3 
654,489,48,43/5*7D+-*% -> 77.68749999999909
                       -> EmptyProgram!!!

(I can add more if needed)

\$\endgroup\$
12
  • 3
    \$\begingroup\$ If it weren't for the Error cases, Vitsy could do this naturally (except converting ! to F). \$\endgroup\$ Commented Dec 2, 2015 at 20:36
  • \$\begingroup\$ I figured, that's partly why I included them. \$\endgroup\$
    – J Atkin
    Commented Dec 2, 2015 at 20:37
  • 3
    \$\begingroup\$ Yours is somewhat broader in scope, though it may be arguable that its a duplicate: codegolf.stackexchange.com/questions/221/… \$\endgroup\$ Commented Dec 2, 2015 at 22:29
  • \$\begingroup\$ Wow, I forgot about that one. But I don't think they are dupes since you have to process errors and more operators are defined in mine. \$\endgroup\$
    – J Atkin
    Commented Dec 2, 2015 at 23:07
  • \$\begingroup\$ 654,489,48,43/5*7D+-*% should return 77.6875. (43/48*5-(7+7) should be (7+7)-43/48*5) \$\endgroup\$
    – user81655
    Commented Dec 3, 2015 at 1:32

4 Answers 4

5
\$\begingroup\$

Ruby, 412 410 404 392 380 377 characters

def e m,x='Exception';warn m+x+?!*3;exit;end
def o;e'StackUnderflow'if$*==[];$*.pop;end
u=->n{e'DivisionByZero'if n.infinite?;e'NumberOverflow'if n>2**64;$*<<n}
f=->n{e'NegativeFactorial'if n<0;e'FloatingFactorial'if n.to_i<n;n<2?1:f[n-1]*n}
gets.gsub(/(\d+)|([+*\/%^-])|(!)|D/){$1?u[$1.to_f]:$2?u[eval"o#{$2>?A?:**:$2}o"]:$3?u[f[o]]:u[x=o]+u[x]}
e'EmptyProgram',''if$*==[]
p o

This is regular precision version using Float. The result precision is as in the sample code, but numeric overflow detection is not exact.

Sample run:

bash-4.3$ ruby StackyMath.rb <<< '654,489,48,43/5*7D+-*%'
77.68749999999909

Ruby, 378 377 characters

def e m,x='Exception';warn m+x+?!*3;exit;end
def o;e'StackUnderflow'if$*==[];$*.pop;end
u=->n{e'NumberOverflow'if n>2**64;$*<<n}
f=->n{e'NegativeFactorial'if n<0;e'FloatingFactorial'if n.to_i<n;n<2?1:f[n-1]*n}
gets.gsub(/(\d+)|([+*\/%^-])|(!)|D/){$1?u[Rational$1]:$2?u[eval"o#{$2>?A?:**:$2}o"]:$3?u[f[o]]:u[x=o]+u[x]}rescue e'DivisionByZero'
e'EmptyProgram',''if$*==[]
p o.to_f

This is high precision version using Rational. The result precision is not always the same as in the sample code, but numeric overflow detection is exact.

Sample run:

bash-4.3$ ruby StackyMath-hi.rb <<< '654,489,48,43/5*7D+-*%'
77.6875
\$\endgroup\$
4
\$\begingroup\$

JavaScript (ES6), 430 bytes

422 bytes with ES7 by changing Math.pow(2,2) to 2**2

e=m=>{throw alert(m)};u=prompt();u?alert(eval('u.match(/\\d+|[^,]/g).map(o=>s.push(t=o=="/"?(b=p(a=2))?a/b:e`DivisionByZero43*"?2*23-"?2-23+"?2+23%"?2%23^"?Math.pow(2,2)3D"?s.push(r=2)&&r3!"?eval("for(r=i=2;i<0?e`Negative54:i%1?e`Floating54:--i;)r*=i;r"):+o)&&t==Infinity&&e`NumberOverflow4,s=[],p=_=>s.length?s.pop():e`StackUnderflow4);t'.replace(/[2-5]/g,x=>[,,'p()',':o=="','Exception!!!`','Factorial'][x]))):e`EmptyProgram!!!`

Explanation

Uses eval to replace certain common phrases. Ungolfed and without the eval it looks like this:

e=m=>{throw alert(m)};                           // e = throw error, alert displays
                                                 //     message, throw stops execution
u=prompt();                                      // u = received input
u?alert(                                         // display the result
  u.match(/\d+|[^,]/g)                           // get array of numbers and operators
    .map(o=>                                     // iterate over operators
      s.push(t=                                  // t = last pushed value

        // Execute operator
        o=="/"?(b=p(a=p()))?a/b:                 // make sure argument B is not 0
          e`DivisionByZeroException!!!`:
        o=="*"?p()*p():
        o=="-"?p()-p():
        o=="+"?p()+p():
        o=="%"?p()%p():
        o=="^"?Math.pow(p(),p()):
        o=="D"?s.push(r=p())&&r:
        o=="!"?eval("                            // eval to enable for loop in ternary
          for(                                   // no factorial in JS so do this manually
            r=i=p();
            i<0?e`NegativeFactorialException!!!` // check for errors
              :i%1?
                e`FloatingFactorialException!!!`
                :--i;
          )
            r*=i;
          r"):                                   // return r
        +o                                       // if not an operator cast as a number
      )&&t==Infinity&&                           // JS turns anything over 64 bit float
        e`NumberOverflowException!!!`,           //     max value into Infinity
      s=[],                                      // s = stack array
      p=_=>s.length?s.pop():                     // p = check stack then pop
        e`StackUnderflowException!!!`
    )&&t                                         // return top stack element
  ):e`EmptyProgram!!!`                           // error if input length is 0
\$\endgroup\$
2
  • \$\begingroup\$ If you want to upgrade to ES7, you could use the ES7 exponentiation operator to replace Math.pow(p(),p()) with p()**p(). \$\endgroup\$
    – rgajrawala
    Commented Dec 3, 2015 at 4:54
  • 1
    \$\begingroup\$ @usandfriends I was thinking about it, but it meant that it would not work in my browser so I left it out. :P I'll add a note saying that. \$\endgroup\$
    – user81655
    Commented Dec 3, 2015 at 5:07
1
\$\begingroup\$

Groovy, 718 bytes. Fore!

May as well post my impl golfed. Meet my big wall of code:

g='Exception!!!'
a={System.err.print(it);System.exit(1)}
b=new Stack()
c={b.push(it)}
v=2d**64d
d={b.pop()}
e={if(b.size()<it)a('StackUnderflow'+g)}
f={a('NumberOverflow'+g)}
h={e(2)
c(Eval.xy(d(),d(),"x$it y"))
if(b.peek()>v)f()}
k={i->if(i<0)a('NegativeFactorial'+g)
if(Math.abs(i-(i as long))>1E-6)a('FloatingFactorial'+g)
(2..i).inject{x,y->(v/x<y)?f():x*y}}
l=['/':{e(2)
m=d()
n=d()
if(n==0)a('DivisionByZero'+g)
c(m/n)},'!':{e(1)
c(k(d()))},'D':{e(1)
c(b.peek())}]
System.in.newReader().readLine().findAll(~/\d+|[^,]/).each{x->if(x.matches(/\d+/))try{c(x as double)}catch(Exception e){f()}
else if("+-*%^".contains(x))h(x.replace('^','**'))
else l[x]()}
if(b){o=d()
if(Double.isInfinite(o))f()
println o}else a('EmptyProgram!!!')

Ungolfed:

error = {System.err.print(it);System.exit(1)}

stack = new Stack()
maxVal = 2d**64d

push = {stack.push(it)}
pop = {stack.pop()}

checkAtLeast = {if (stack.size() < it) error('StackUnderflow'+exception)}
numberOverflow = {error('NumberOverflow'+exception)}

exception = 'Exception!!!'

def dArgOp(i) {
    checkAtLeast(2)
    push(Eval.xy(pop(), pop(), "x$i y"))
    if(stack.peek() > maxVal) numberOverflow
}

factorial = {i->
    if (i < 0)
        error('NegativeFactorial'+exception)
    if (Math.abs(i - (i as long)) > 1E-6)
        error('FloatingFactorial'+exception)
    (2..i).inject {acc, it ->
        (maxVal/acc < it)?numberOverflow():acc*it
    }
}

ops = [
'/' : {
    checkAtLeast(2)
    first = pop()
    second = pop()
    if (second == 0)
        error('DivisionByZero'+exception)
    push(first / second)
},
'!' : {
    checkAtLeast(1)
    push(factorial(pop()))
},
'D' : {
    checkAtLeast(1)
    push(stack.peek())
}]

input = System.in.newReader().readLine()
tokens = input.findAll(~/\d+|[^,]/)

tokens.each {
    print "current token: $it  \t stack before eval: $stack "
    if (it.matches(/\d+/))
        try {
            push(it as double)
        } catch (Exception e) {
            numberOverflow()
        }

    else if ("+-*%^".contains(it))
        dArgOp(it.replace('^','**'))
    else
        ops[it]()
    println "result: ${stack.peek()}"
}

if (stack) {
    top = pop()
    if (Double.isInfinite(top))
        numberOverflow()
    println top
} else
    error('EmptyProgram!!!')

Edit 1: save ~15 bytes thanks to @Doorknob
Edit 2: drop ~130 bytes with a few more tricks

\$\endgroup\$
19
  • \$\begingroup\$ I don't know Groovy, but you seem to have lots of unnecessary whitespace. For example, around operators, after for/if, etc \$\endgroup\$
    – Doorknob
    Commented Dec 2, 2015 at 21:13
  • \$\begingroup\$ Whoops, just noticed many more places to remove whitespace. Thanks for the tip. \$\endgroup\$
    – J Atkin
    Commented Dec 2, 2015 at 21:22
  • \$\begingroup\$ You can use System.in.text instead of System.in.newReader().readLine(). \$\endgroup\$ Commented Dec 3, 2015 at 0:47
  • \$\begingroup\$ Not really. .text is greedy and as long as data in in the reader, it won't return. \$\endgroup\$
    – J Atkin
    Commented Dec 3, 2015 at 0:52
  • 1
    \$\begingroup\$ Right, but this is code-golf. It's not a big deal if people have to type Control-D after their input. \$\endgroup\$ Commented Dec 3, 2015 at 1:50
1
\$\begingroup\$

Candy, 298 348 392 bytes

Although Candy is stack based, I'm not sure that really helped...

&{|"EmptyProgram!!!\n"(;).}(=bYZay0=zx4k"!%*+,-/D^"(iYe{Z=x})aYb=z=ya=X{Y{cZ0=yza}b212202221(i=ZXe{y})a0=zcY0j{XjZ{|D1b#64R(=c2*)c>{b"NumberOverFlow"(;)i}}|i}aZ{(=)"Exception!!!\n"(;).}0=yz|A#48-Z#10*+=z1=y})c?(=).@0&<{1|b"StackUnderflow"(;)c0}.@1~ALe{A0<{b"Negative"(;)i|1bAR(=cA*)}|b"Floating"(;)i}Z{b"Factorial"(;)}.@2W%.@3*.@4+@5.@6W-.@7WD{/|b"DivisionByZero"(;)i}.@8~A.@9=xK=y=1bA_(cX*).

Formatted a bit reveals a bit of structure:

&{|"EmptyProgram!!!\n"(;).}
(=bYZay0=zx4k
  "!%*+,-/D^"
  (iYe{Z=x})
  aYb=z=ya=X
  {
    Y{cZ0=yza}b
    212202221(i=ZXe{y})
    a0=zcY0j
    {XjZ{|D1b#64R(=c2*)c>{b"NumberOverFlow"(;)i}}|i}
    aZ{(=)"Exception!!!\n"(;).}
    0=yz|A#48-Z#10*+=z1=y
  }
)c?(=).
@0&<{1|b"StackUnderflow"(;)c0}.
@1~ALe{A0<{b"Negative"(;)i|1bAR(=cA*)}|b"Floating"(;)i}Z{"Factorial"(;)}.
@2W%.@3*.@4+@5.@6W-.@7WD{/|"DivisionByZero"(;)i}.@8~A.@9=xK=y=1bA_(cX*).

The actual math occurs on the last two lines. It's driven there by a jump table on the third line.

\$\endgroup\$
5
  • \$\begingroup\$ Dang, I see that I missed DivisionByZero, NegativeFactorial, and Overflow. I was just looking at the test cases! \$\endgroup\$ Commented Dec 4, 2015 at 12:14
  • \$\begingroup\$ Wow, this is cool. I just might need to look up candy. \$\endgroup\$
    – J Atkin
    Commented Dec 4, 2015 at 14:11
  • \$\begingroup\$ I'm still working on how exactly to define overflow. \$\endgroup\$ Commented Dec 4, 2015 at 19:20
  • \$\begingroup\$ Actually I had the same problem in my answer. See the comments below my answer. \$\endgroup\$
    – J Atkin
    Commented Dec 4, 2015 at 19:22
  • \$\begingroup\$ Fixed the Overflow now. I used the same approach as the Ruby, which is just to compare to 2^64 at the end of each operation. \$\endgroup\$ Commented Dec 4, 2015 at 20:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.