Repository: mholt/PapaParse
Pull request #694 (2019-07-26 06:08:22)

Leave big numbers as strings (>2^53 || <-2^53)
Issues like #692 occur when numbers pass the regex for being numerical, but then are too large to have their precision maintained when using `parseFloat`. To avoid needing to use a library like Big to handle numbers of this size (IMO its a lot of overhead) we instead leave them as strings, and instead would encourage users to use a custom dynamicTyping function if they need to handle them as numbers.


Patch:
diff --git a/docs/docs.html b/docs/docs.html
index e4fc228..271a62a 100644
--- a/docs/docs.html
+++ b/docs/docs.html
@@ -508,7 +508,7 @@ var csv = Papa.unparse({
 									<code>dynamicTyping</code>
 								</td>
 								<td>
-									If true, numeric and boolean data will be converted to their type instead of remaining strings. Numeric data must conform to the definition of a decimal literal. European-formatted numbers must have commas and dots swapped. If also accepts an object or a function. If object it's values should be a boolean to indicate if dynamic typing should be applied for each column number (or header name if using headers). If it's a function, it should return a boolean value for each field number (or name if using headers) which will be passed as first argument.
+									If true, numeric and boolean data will be converted to their type instead of remaining strings. Numeric data must conform to the definition of a decimal literal. Numerical values greater than <code>2^53</code> or less than <code>-2^53</code> will not be converted to numbers to preserve precision. European-formatted numbers must have commas and dots swapped. If also accepts an object or a function. If object it's values should be a boolean to indicate if dynamic typing should be applied for each column number (or header name if using headers). If it's a function, it should return a boolean value for each field number (or name if using headers) which will be passed as first argument.
 								</td>
 							</tr>
 							<tr>
diff --git a/papaparse.js b/papaparse.js
index d6d83fd..acf4c95 100755
--- a/papaparse.js
+++ b/papaparse.js
@@ -996,6 +996,8 @@ License: MIT
 	function ParserHandle(_config)
 	{
 		// One goal is to minimize the use of regular expressions...
+		var MAX_FLOAT = Math.pow(2, 53);
+		var MIN_FLOAT = -MAX_FLOAT;
 		var FLOAT = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i;
 		var ISO_DATE = /(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/;
 		var self = this;
@@ -1123,6 +1125,16 @@ License: MIT
 			return _config.skipEmptyLines === 'greedy' ? s.join('').trim() === '' : s.length === 1 && s[0].length === 0;
 		}
 
+		function testFloat(s) {
+			if (FLOAT.test(s)) {
+				var floatValue = parseFloat(s);
+				if (floatValue > MIN_FLOAT && floatValue < MAX_FLOAT) {
+					return true;
+				}
+			}
+			return false;
+		}
+
 		function processResults()
 		{
 			if (_results && _delimiterError)
@@ -1190,7 +1202,7 @@ License: MIT
 					return true;
 				else if (value === 'false' || value === 'FALSE')
 					return false;
-				else if (FLOAT.test(value))
+				else if (testFloat(value))
 					return parseFloat(value);
 				else if (ISO_DATE.test(value))
 					return new Date(value);


Test patch:
diff --git a/tests/test-cases.js b/tests/test-cases.js
index 9d5be3a..81a2dce 100644
--- a/tests/test-cases.js
+++ b/tests/test-cases.js
@@ -815,11 +815,11 @@ var PARSE_TESTS = [
 		}
 	},
 	{
-		description: "Dynamic typing converts numeric literals",
-		input: '1,2.2,1e3\r\n-4,-4.5,-4e-5\r\n-,5a,5-2',
+		description: "Dynamic typing converts numeric literals and maintains precision",
+		input: '1,2.2,1e3\r\n-4,-4.5,-4e-5\r\n-,5a,5-2\r\n16142028098527942586,9007199254740991,-9007199254740992',
 		config: { dynamicTyping: true },
 		expected: {
-			data: [[1, 2.2, 1000], [-4, -4.5, -0.00004], ["-", "5a", "5-2"]],
+			data: [[1, 2.2, 1000], [-4, -4.5, -0.00004], ["-", "5a", "5-2"], ["16142028098527942586", 9007199254740991, "-9007199254740992"]],
 			errors: []
 		}
 	},