diff --git a/src/main/java/com/typesafe/config/impl/ConfigDouble.java b/src/main/java/com/typesafe/config/impl/ConfigDouble.java
index 105811f6..db810177 100644
--- a/src/main/java/com/typesafe/config/impl/ConfigDouble.java
+++ b/src/main/java/com/typesafe/config/impl/ConfigDouble.java
@@ -3,12 +3,12 @@ package com.typesafe.config.impl;
 import com.typesafe.config.ConfigOrigin;
 import com.typesafe.config.ConfigValueType;
 
-final class ConfigDouble extends AbstractConfigValue {
+final class ConfigDouble extends ConfigNumber {
 
     final private double value;
 
-    ConfigDouble(ConfigOrigin origin, double value) {
-        super(origin);
+    ConfigDouble(ConfigOrigin origin, double value, String originalText) {
+        super(origin, originalText);
         this.value = value;
     }
 
@@ -24,6 +24,10 @@ final class ConfigDouble extends AbstractConfigValue {
 
     @Override
     String transformToString() {
-        return Double.toString(value);
+        String s = super.transformToString();
+        if (s == null)
+            return Double.toString(value);
+        else
+            return s;
     }
 }
diff --git a/src/main/java/com/typesafe/config/impl/ConfigInt.java b/src/main/java/com/typesafe/config/impl/ConfigInt.java
index 63699363..ce505950 100644
--- a/src/main/java/com/typesafe/config/impl/ConfigInt.java
+++ b/src/main/java/com/typesafe/config/impl/ConfigInt.java
@@ -3,12 +3,12 @@ package com.typesafe.config.impl;
 import com.typesafe.config.ConfigOrigin;
 import com.typesafe.config.ConfigValueType;
 
-final class ConfigInt extends AbstractConfigValue {
+final class ConfigInt extends ConfigNumber {
 
     final private int value;
 
-    ConfigInt(ConfigOrigin origin, int value) {
-        super(origin);
+    ConfigInt(ConfigOrigin origin, int value, String originalText) {
+        super(origin, originalText);
         this.value = value;
     }
 
@@ -48,6 +48,10 @@ final class ConfigInt extends AbstractConfigValue {
 
     @Override
     String transformToString() {
-        return Integer.toString(value);
+        String s = super.transformToString();
+        if (s == null)
+            return Integer.toString(value);
+        else
+            return s;
     }
 }
diff --git a/src/main/java/com/typesafe/config/impl/ConfigLong.java b/src/main/java/com/typesafe/config/impl/ConfigLong.java
index 593b9b60..b3a82c81 100644
--- a/src/main/java/com/typesafe/config/impl/ConfigLong.java
+++ b/src/main/java/com/typesafe/config/impl/ConfigLong.java
@@ -3,12 +3,12 @@ package com.typesafe.config.impl;
 import com.typesafe.config.ConfigOrigin;
 import com.typesafe.config.ConfigValueType;
 
-final class ConfigLong extends AbstractConfigValue {
+final class ConfigLong extends ConfigNumber {
 
     final private long value;
 
-    ConfigLong(ConfigOrigin origin, long value) {
-        super(origin);
+    ConfigLong(ConfigOrigin origin, long value, String originalText) {
+        super(origin, originalText);
         this.value = value;
     }
 
@@ -53,6 +53,10 @@ final class ConfigLong extends AbstractConfigValue {
 
     @Override
     String transformToString() {
-        return Long.toString(value);
+        String s = super.transformToString();
+        if (s == null)
+            return Long.toString(value);
+        else
+            return s;
     }
 }
diff --git a/src/main/java/com/typesafe/config/impl/ConfigNumber.java b/src/main/java/com/typesafe/config/impl/ConfigNumber.java
new file mode 100644
index 00000000..f150245f
--- /dev/null
+++ b/src/main/java/com/typesafe/config/impl/ConfigNumber.java
@@ -0,0 +1,21 @@
+package com.typesafe.config.impl;
+
+import com.typesafe.config.ConfigOrigin;
+
+abstract class ConfigNumber extends AbstractConfigValue {
+    // This is so when we concatenate a number into a string (say it appears in
+    // a sentence) we always have it exactly as the person typed it into the
+    // config file. It's purely cosmetic; equals/hashCode don't consider this
+    // for example.
+    final private String originalText;
+
+    protected ConfigNumber(ConfigOrigin origin, String originalText) {
+        super(origin);
+        this.originalText = originalText;
+    }
+
+    @Override
+    String transformToString() {
+        return originalText;
+    }
+}
diff --git a/src/main/java/com/typesafe/config/impl/DefaultTransformer.java b/src/main/java/com/typesafe/config/impl/DefaultTransformer.java
index c7bc1815..db04f6e4 100644
--- a/src/main/java/com/typesafe/config/impl/DefaultTransformer.java
+++ b/src/main/java/com/typesafe/config/impl/DefaultTransformer.java
@@ -16,13 +16,13 @@ final class DefaultTransformer implements ConfigTransformer {
             case NUMBER:
                 try {
                     Long v = Long.parseLong(s);
-                    return new ConfigLong(value.origin(), v);
+                    return new ConfigLong(value.origin(), v, s);
                 } catch (NumberFormatException e) {
                     // try Double
                 }
                 try {
                     Double v = Double.parseDouble(s);
-                    return new ConfigDouble(value.origin(), v);
+                    return new ConfigDouble(value.origin(), v, s);
                 } catch (NumberFormatException e) {
                     // oh well.
                 }
diff --git a/src/main/java/com/typesafe/config/impl/Tokenizer.java b/src/main/java/com/typesafe/config/impl/Tokenizer.java
index d7895090..b0b28c7e 100644
--- a/src/main/java/com/typesafe/config/impl/Tokenizer.java
+++ b/src/main/java/com/typesafe/config/impl/Tokenizer.java
@@ -271,11 +271,11 @@ final class Tokenizer {
             try {
                 if (containedDecimalOrE) {
                     // force floating point representation
-                    return Tokens
-                            .newDouble(lineOrigin(), Double.parseDouble(s));
+                    return Tokens.newDouble(lineOrigin(),
+                            Double.parseDouble(s), s);
                 } else {
                     // this should throw if the integer is too large for Long
-                    return Tokens.newLong(lineOrigin(), Long.parseLong(s));
+                    return Tokens.newLong(lineOrigin(), Long.parseLong(s), s);
                 }
             } catch (NumberFormatException e) {
                 throw parseError("Invalid number: '" + s
diff --git a/src/main/java/com/typesafe/config/impl/Tokens.java b/src/main/java/com/typesafe/config/impl/Tokens.java
index a2315cd2..a478f9f6 100644
--- a/src/main/java/com/typesafe/config/impl/Tokens.java
+++ b/src/main/java/com/typesafe/config/impl/Tokens.java
@@ -264,16 +264,17 @@ final class Tokens {
         return newValue(new ConfigString(origin, value));
     }
 
-    static Token newInt(ConfigOrigin origin, int value) {
-        return newValue(new ConfigInt(origin, value));
+    static Token newInt(ConfigOrigin origin, int value, String originalText) {
+        return newValue(new ConfigInt(origin, value, originalText));
     }
 
-    static Token newDouble(ConfigOrigin origin, double value) {
-        return newValue(new ConfigDouble(origin, value));
+    static Token newDouble(ConfigOrigin origin, double value,
+            String originalText) {
+        return newValue(new ConfigDouble(origin, value, originalText));
     }
 
-    static Token newLong(ConfigOrigin origin, long value) {
-        return newValue(new ConfigLong(origin, value));
+    static Token newLong(ConfigOrigin origin, long value, String originalText) {
+        return newValue(new ConfigLong(origin, value, originalText));
     }
 
     static Token newNull(ConfigOrigin origin) {
diff --git a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala
index fd347705..82781f36 100644
--- a/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala
+++ b/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala
@@ -14,9 +14,9 @@ class ConfigValueTest extends TestUtils {
 
     @Test
     def configIntEquality() {
-        val a = new ConfigInt(fakeOrigin(), 42)
-        val sameAsA = new ConfigInt(fakeOrigin(), 42)
-        val b = new ConfigInt(fakeOrigin(), 43)
+        val a = intValue(42)
+        val sameAsA = intValue(42)
+        val b = intValue(43)
 
         checkEqualObjects(a, a)
         checkEqualObjects(a, sameAsA)
@@ -25,9 +25,9 @@ class ConfigValueTest extends TestUtils {
 
     @Test
     def configLongEquality() {
-        val a = new ConfigLong(fakeOrigin(), Integer.MAX_VALUE + 42L)
-        val sameAsA = new ConfigLong(fakeOrigin(), Integer.MAX_VALUE + 42L)
-        val b = new ConfigLong(fakeOrigin(), Integer.MAX_VALUE + 43L)
+        val a = longValue(Integer.MAX_VALUE + 42L)
+        val sameAsA = longValue(Integer.MAX_VALUE + 42L)
+        val b = longValue(Integer.MAX_VALUE + 43L)
 
         checkEqualObjects(a, a)
         checkEqualObjects(a, sameAsA)
@@ -36,21 +36,21 @@ class ConfigValueTest extends TestUtils {
 
     @Test
     def configIntAndLongEquality() {
-        val longValue = new ConfigLong(fakeOrigin(), 42L)
-        val intValue = new ConfigLong(fakeOrigin(), 42)
-        val longValueB = new ConfigLong(fakeOrigin(), 43L)
-        val intValueB = new ConfigLong(fakeOrigin(), 43)
+        val longVal = longValue(42L)
+        val intValue = longValue(42)
+        val longValueB = longValue(43L)
+        val intValueB = longValue(43)
 
-        checkEqualObjects(intValue, longValue)
+        checkEqualObjects(intValue, longVal)
         checkEqualObjects(intValueB, longValueB)
         checkNotEqualObjects(intValue, longValueB)
-        checkNotEqualObjects(intValueB, longValue)
+        checkNotEqualObjects(intValueB, longVal)
     }
 
     private def configMap(pairs: (String, Int)*): java.util.Map[String, AbstractConfigValue] = {
         val m = new java.util.HashMap[String, AbstractConfigValue]()
         for (p <- pairs) {
-            m.put(p._1, new ConfigInt(fakeOrigin(), p._2))
+            m.put(p._1, intValue(p._2))
         }
         m
     }
@@ -309,4 +309,25 @@ class ConfigValueTest extends TestUtils {
         unresolved { dmo.values() }
         unresolved { dmo.getInt("foo") }
     }
+
+    @Test
+    def roundTripNumbersThroughString() {
+        // formats rounded off with E notation
+        val a = "132454454354353245.3254652656454808909932874873298473298472"
+        // formats as 100000.0
+        val b = "1e6"
+        // formats as 5.0E-5
+        val c = "0.00005"
+        // formats as 1E100 (capital E)
+        val d = "1e100"
+
+        val obj = parseObject("{ a : " + a + ", b : " + b + ", c : " + c + ", d : " + d + "}")
+        assertEquals(Seq(a, b, c, d),
+            Seq("a", "b", "c", "d") map { obj.getString(_) })
+
+        // make sure it still works if we're doing concatenation
+        val obj2 = parseObject("{ a : xx " + a + " yy, b : xx " + b + " yy, c : xx " + c + " yy, d : xx " + d + " yy}")
+        assertEquals(Seq(a, b, c, d) map { "xx " + _ + " yy" },
+            Seq("a", "b", "c", "d") map { obj2.getString(_) })
+    }
 }
diff --git a/src/test/scala/com/typesafe/config/impl/JsonTest.scala b/src/test/scala/com/typesafe/config/impl/JsonTest.scala
index b013aaf1..c85d22ee 100644
--- a/src/test/scala/com/typesafe/config/impl/JsonTest.scala
+++ b/src/test/scala/com/typesafe/config/impl/JsonTest.scala
@@ -55,11 +55,11 @@ class JsonTest extends TestUtils {
             case lift.JField(name, value) =>
                 throw new IllegalStateException("either JField was a toplevel from lift-json or this function is buggy")
             case lift.JInt(i) =>
-                if (i.isValidInt) new ConfigInt(fakeOrigin(), i.intValue) else new ConfigLong(fakeOrigin(), i.longValue)
+                if (i.isValidInt) intValue(i.intValue) else longValue(i.longValue)
             case lift.JBool(b) =>
                 new ConfigBoolean(fakeOrigin(), b)
             case lift.JDouble(d) =>
-                new ConfigDouble(fakeOrigin(), d)
+                doubleValue(d)
             case lift.JString(s) =>
                 new ConfigString(fakeOrigin(), s)
             case lift.JNull =>
diff --git a/src/test/scala/com/typesafe/config/impl/TestUtils.scala b/src/test/scala/com/typesafe/config/impl/TestUtils.scala
index 1d63221a..ad5d8ce4 100644
--- a/src/test/scala/com/typesafe/config/impl/TestUtils.scala
+++ b/src/test/scala/com/typesafe/config/impl/TestUtils.scala
@@ -311,12 +311,12 @@ abstract trait TestUtils {
         }
     }
 
-    protected def intValue(i: Int) = new ConfigInt(fakeOrigin(), i)
-    protected def longValue(l: Long) = new ConfigLong(fakeOrigin(), l)
+    protected def intValue(i: Int) = new ConfigInt(fakeOrigin(), i, null)
+    protected def longValue(l: Long) = new ConfigLong(fakeOrigin(), l, null)
     protected def boolValue(b: Boolean) = new ConfigBoolean(fakeOrigin(), b)
     protected def nullValue() = new ConfigNull(fakeOrigin())
     protected def stringValue(s: String) = new ConfigString(fakeOrigin(), s)
-    protected def doubleValue(d: Double) = new ConfigDouble(fakeOrigin(), d)
+    protected def doubleValue(d: Double) = new ConfigDouble(fakeOrigin(), d, null)
 
     protected def parseObject(s: String) = {
         Parser.parse(SyntaxFlavor.CONF, new SimpleConfigOrigin("test string"), s, includer()).asInstanceOf[AbstractConfigObject]
@@ -338,9 +338,9 @@ abstract trait TestUtils {
     def tokenNull = Tokens.newNull(fakeOrigin())
     def tokenUnquoted(s: String) = Tokens.newUnquotedText(fakeOrigin(), s)
     def tokenString(s: String) = Tokens.newString(fakeOrigin(), s)
-    def tokenDouble(d: Double) = Tokens.newDouble(fakeOrigin(), d)
-    def tokenInt(i: Int) = Tokens.newInt(fakeOrigin(), i)
-    def tokenLong(l: Long) = Tokens.newLong(fakeOrigin(), l)
+    def tokenDouble(d: Double) = Tokens.newDouble(fakeOrigin(), d, null)
+    def tokenInt(i: Int) = Tokens.newInt(fakeOrigin(), i, null)
+    def tokenLong(l: Long) = Tokens.newLong(fakeOrigin(), l, null)
 
     def tokenSubstitution(expression: Token*) = {
         val l = new java.util.ArrayList[Token]