diff --git a/src/builtins/README.md b/src/builtins/README.md
index 3433f3422a492de874615c0bc2cd60b023e8d632..88931bf705b14f74ead46ff711a6aec89e9a892f 100644
--- a/src/builtins/README.md
+++ b/src/builtins/README.md
@@ -33,12 +33,31 @@ let git_branch = $(git rev-parse --abbrev-ref HEAD 2> /dev/null)
 
 If the command is executed without any arguments, it will simply list all available variables.
 
+#### Dropping variables
+
 To drop a value from the shell, the `drop` keyword may be used:
 
-```sh
+```ion
 drop git_branch
 ```
 
+#### Arithmetic
+
+The `let` command also supports basic arithmetic.
+
+```ion
+let a = 1
+echo $a
+let a += 4
+echo $a
+let a *= 10
+echo $a
+let a /= 2
+echo $a
+let a -= 5
+echo $a
+```
+
 ### Export
 
 The `export` command works similarly to the `let` command, but instead of defining a local variable, it defines a global variable that other processes can access.
@@ -46,3 +65,20 @@ The `export` command works similarly to the `let` command, but instead of defini
 ```sh
 export PATH = "~/.cargo/bin:${PATH}"
 ```
+
+#### Arithmetic
+
+The `export` command also supports basic arithmetic.
+
+```ion
+export a = 1
+echo $a
+export a += 4
+echo $a
+export a *= 10
+echo $a
+export a /= 2
+echo $a
+export a -= 5
+echo $a
+```
diff --git a/src/builtins/variables.rs b/src/builtins/variables.rs
index f22e16b61867c1d91d1d0a04c67af98a35c32989..1efb06f4801b18872203145e7c44dd8018be7602 100644
--- a/src/builtins/variables.rs
+++ b/src/builtins/variables.rs
@@ -22,6 +22,15 @@ enum Binding {
     ListEntries,
     KeyOnly(String),
     KeyValue(String, String),
+    Math(String, Operator, f32),
+    MathInvalid(String)
+}
+
+enum Operator {
+    Plus,
+    Minus,
+    Divide,
+    Multiply
 }
 
 /// Parses let bindings, `let VAR = KEY`, returning the result as a `(key, value)` tuple.
@@ -37,6 +46,7 @@ fn parse_assignment<I: IntoIterator>(args: I)
     // Find the key and advance the iterator until the equals operator is found.
     let mut key = "".to_owned();
     let mut found_key = false;
+    let mut operator = None;
 
     // Scans through characters until the key is found, then continues to scan until
     // the equals operator is found.
@@ -44,6 +54,34 @@ fn parse_assignment<I: IntoIterator>(args: I)
         match character {
             ' ' if key.is_empty() => (),
             ' ' => found_key = true,
+            '+' => {
+                if char_iter.next() == Some('=') {
+                    operator = Some(Operator::Plus);
+                    found_key = true;
+                }
+                break
+            },
+            '-' => {
+                if char_iter.next() == Some('=') {
+                    operator = Some(Operator::Minus);
+                    found_key = true;
+                }
+                break
+            },
+            '*' => {
+                if char_iter.next() == Some('=') {
+                    operator = Some(Operator::Multiply);
+                    found_key = true;
+                }
+                break
+            },
+            '/' => {
+                if char_iter.next() == Some('=') {
+                    operator = Some(Operator::Divide);
+                    found_key = true;
+                }
+                break
+            },
             '=' => {
                 found_key = true;
                 break
@@ -62,7 +100,15 @@ fn parse_assignment<I: IntoIterator>(args: I)
         } else if !Variables::is_valid_variable_name(&key) {
             Binding::InvalidKey(key)
         } else {
-            Binding::KeyValue(key, value)
+            match operator {
+                Some(operator) => {
+                    match value.parse::<f32>() {
+                        Ok(value) => Binding::Math(key, operator, value),
+                        Err(_)    => Binding::MathInvalid(value)
+                    }
+                },
+                None => Binding::KeyValue(key, value)
+            }
         }
     }
 }
@@ -84,6 +130,11 @@ pub fn alias<I: IntoIterator>(vars: &mut Variables, args: I) -> i32
             let stderr = io::stderr();
             let _ = writeln!(&mut stderr.lock(), "ion: please provide value for alias '{}'", key);
             return FAILURE;
+        },
+        _ => {
+            let stderr = io::stderr();
+            let _ = writeln!(&mut stderr.lock(), "ion: invalid alias syntax");
+            return FAILURE;
         }
     }
     SUCCESS
@@ -127,6 +178,29 @@ pub fn let_<I: IntoIterator>(vars: &mut Variables, args: I) -> i32
             let stderr = io::stderr();
             let _ = writeln!(&mut stderr.lock(), "ion: please provide value for variable '{}'", key);
             return FAILURE;
+        },
+        Binding::Math(key, operator, increment) => {
+            let value = vars.get_var_or_empty(&key);
+            let _ = match value.parse::<f32>() {
+                Ok(old_value) => match operator {
+                    Operator::Plus     => vars.variables.insert(key, (old_value + increment).to_string()),
+                    Operator::Minus    => vars.variables.insert(key, (old_value - increment).to_string()),
+                    Operator::Multiply => vars.variables.insert(key, (old_value * increment).to_string()),
+                    Operator::Divide   => vars.variables.insert(key, (old_value / increment).to_string()),
+                },
+                Err(_) => {
+                    let stderr = io::stderr();
+                    let mut stderr = stderr.lock();
+                    let _ = writeln!(stderr, "ion: original value, {}, is not a number", value);
+                    return FAILURE;
+                }
+            };
+        },
+        Binding::MathInvalid(value) => {
+            let stderr = io::stderr();
+            let mut stderr = stderr.lock();
+            let _ = writeln!(stderr, "ion: supplied value, {}, is not a number", value);
+            return FAILURE;
         }
     }
     SUCCESS
@@ -174,6 +248,29 @@ pub fn export_variable<I: IntoIterator>(vars: &mut Variables, args: I) -> i32
                 return FAILURE;
             }
         },
+        Binding::Math(key, operator, increment) => {
+            let value = vars.get_var(&key).unwrap_or_else(|| "".to_owned());
+            match value.parse::<f32>() {
+                Ok(old_value) => match operator {
+                    Operator::Plus     => env::set_var(key, (old_value + increment).to_string()),
+                    Operator::Minus    => env::set_var(key, (old_value - increment).to_string()),
+                    Operator::Multiply => env::set_var(key, (old_value * increment).to_string()),
+                    Operator::Divide   => env::set_var(key, (old_value / increment).to_string()),
+                },
+                Err(_) => {
+                    let stderr = io::stderr();
+                    let mut stderr = stderr.lock();
+                    let _ = writeln!(stderr, "ion: original value, {}, is not a number", value);
+                    return FAILURE;
+                }
+            }
+        },
+        Binding::MathInvalid(value) => {
+            let stderr = io::stderr();
+            let mut stderr = stderr.lock();
+            let _ = writeln!(stderr, "ion: supplied value, {}, is not a number", value);
+            return FAILURE;
+        },
         _ => {
             let stderr = io::stderr();
             let _ = writeln!(&mut stderr.lock(), "ion usage: export KEY=VALUE");