diff --git a/src/stdlib/src/lib.rs b/src/stdlib/src/lib.rs
index d60ffc8a5abdcaf8abe5d019440646fd8265cac7..9a9cfe8e6ec7c587bb33f685e5d04db2b519acbf 100644
--- a/src/stdlib/src/lib.rs
+++ b/src/stdlib/src/lib.rs
@@ -635,19 +635,55 @@ pub extern "C" fn srandom(seed: c_uint) {
 }
 
 #[no_mangle]
-pub unsafe extern "C" fn strtod(s: *const c_char, endptr: *mut *mut c_char) -> c_double {
-    // TODO: endptr
+pub unsafe extern "C" fn strtod(mut s: *const c_char, endptr: *mut *mut c_char) -> c_double {
+    while ctype::isspace(*s as c_int) != 0 {
+        s = s.offset(1);
+    }
 
-    use core::str::FromStr;
+    let mut result = 0.0;
+    let mut radix = 10;
 
-    let s_str = str::from_utf8_unchecked(platform::c_str(s));
-    match f64::from_str(s_str) {
-        Ok(ok) => ok as c_double,
-        Err(_err) => {
-            platform::errno = EINVAL;
-            0.0
+    let negative = match *s as u8 {
+        b'-' => { s = s.offset(1); true },
+        b'+' => { s = s.offset(1); false },
+        _ => false
+    };
+
+    if *s as u8 == b'0' && *s.offset(1) as u8 == b'x' {
+        s = s.offset(2);
+        radix = 16;
+    }
+
+    while let Some(digit) = (*s as u8 as char).to_digit(radix) {
+        result *= radix as c_double;
+        result += digit as c_double;
+        s = s.offset(1);
+    }
+
+    if *s as u8 == b'.' {
+        s = s.offset(1);
+
+        let mut i = 1.0;
+        while let Some(digit) = (*s as u8 as char).to_digit(radix) {
+            i *= radix as c_double;
+            result += digit as c_double / i;
+            s = s.offset(1);
         }
     }
+
+    if !endptr.is_null() {
+        // This is stupid, but apparently strto* functions want
+        // const input but mut output, yet the man page says
+        // "stores the address of the first invalid character in *endptr"
+        // so obviously it doesn't want us to clone it.
+        *endptr = s as *mut _;
+    }
+
+    if negative {
+        -result
+    } else {
+        result
+    }
 }
 
 pub fn is_positive(ch: c_char) -> Option<(bool, isize)> {
diff --git a/tests/Makefile b/tests/Makefile
index b6eacd637ff745326b09cbe5ea7a000bc0ca86f7..33fd0564db5393ad5dc877cb7956197a75f699cb 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -28,6 +28,7 @@ EXPECT_BINS=\
 	stdlib/env \
 	stdlib/mkostemps \
 	stdlib/rand \
+	stdlib/strtod \
 	stdlib/strtol \
 	stdlib/strtoul \
 	stdlib/system \
diff --git a/tests/expected/stdio/all.stdout b/tests/expected/stdio/all.stdout
index 23bfb625a6ed770d2f7568d54b8badd69c0a2807..ebda98d01a2738e3b1f5ea75d3163815195255e3 100644
--- a/tests/expected/stdio/all.stdout
+++ b/tests/expected/stdio/all.stdout
@@ -1,3 +1,4 @@
 H
 Hello World!
 
+Hello
diff --git a/tests/expected/stdio/setvbuf.stderr b/tests/expected/stdio/setvbuf.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/expected/stdio/setvbuf.stdout b/tests/expected/stdio/setvbuf.stdout
new file mode 100644
index 0000000000000000000000000000000000000000..7f04b787f7dc9dab3d29ca26572878533525395d
--- /dev/null
+++ b/tests/expected/stdio/setvbuf.stdout
@@ -0,0 +1,6 @@
+H
+ello World!
+
+Line 2
+
+Hello
diff --git a/tests/expected/stdlib/strtod.stderr b/tests/expected/stdlib/strtod.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/expected/stdlib/strtod.stdout b/tests/expected/stdlib/strtod.stdout
new file mode 100644
index 0000000000000000000000000000000000000000..c3013b8585a9901a0536ca1d18f270e6ba203fb4
--- /dev/null
+++ b/tests/expected/stdlib/strtod.stdout
@@ -0,0 +1,9 @@
+d: 0 Endptr: "a 1 hello"
+d: 1 Endptr: " hello"
+d: 1 Endptr: " hello 2"
+d: 10.123 Endptr: ""
+d: 10.123 Endptr: ""
+d: -5.3 Endptr: ""
+d: 16.071044921875 Endptr: ""
+d: 1.13671875 Endptr: ""
+d: 3.12890625 Endptr: ""
diff --git a/tests/stdlib/strtod.c b/tests/stdlib/strtod.c
new file mode 100644
index 0000000000000000000000000000000000000000..21a728050d30ed9fa99c4ddbdfe664c08f0e1689
--- /dev/null
+++ b/tests/stdlib/strtod.c
@@ -0,0 +1,17 @@
+#include <stdlib.h>
+#include <stdio.h>
+
+int main() {
+    char* endptr = 0;
+    double d;
+
+    char* inputs[] = {
+        "a 1 hello", " 1 hello", "1 hello 2",
+        "10.123", "010.123", "-5.3",
+        "0x10.123", "0x1.23", "0x3.21"
+    };
+    for (int i = 0; i < sizeof(inputs) / sizeof(char*); i += 1) {
+        d = strtod(inputs[i], &endptr);
+        printf("d: %f Endptr: \"%s\"\n", d, endptr);
+    }
+}