From 3d942bd503797d1837cda69a1229991a366d6ecf Mon Sep 17 00:00:00 2001 From: Jkorf Date: Mon, 21 Jul 2025 09:42:52 +0200 Subject: [PATCH] Updated decimal parser to support "NaN" and "-Infinity" strings, added check for negative overflow value, improved performance in most cases --- .../SystemTextJsonConverterTests.cs | 8 +++- CryptoExchange.Net/ExchangeHelpers.cs | 44 ++++++++++++++----- .../Comparers/SystemTextJsonComparer.cs | 2 +- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/CryptoExchange.Net.UnitTests/SystemTextJsonConverterTests.cs b/CryptoExchange.Net.UnitTests/SystemTextJsonConverterTests.cs index 5662776..3270ffc 100644 --- a/CryptoExchange.Net.UnitTests/SystemTextJsonConverterTests.cs +++ b/CryptoExchange.Net.UnitTests/SystemTextJsonConverterTests.cs @@ -224,13 +224,17 @@ namespace CryptoExchange.Net.UnitTests [TestCase(null, null)] [TestCase("", null)] [TestCase("null", null)] + [TestCase("nan", null)] [TestCase("1E+2", 100)] [TestCase("1E-2", 0.01)] - [TestCase("80228162514264337593543950335", -999)] // -999 is workaround for not being able to specify decimal.MaxValue + [TestCase("Infinity", 999)] // 999 is workaround for not being able to specify decimal.MinValue + [TestCase("-Infinity", -999)] // -999 is workaround for not being able to specify decimal.MaxValue + [TestCase("80228162514264337593543950335", 999)] // 999 is workaround for not being able to specify decimal.MaxValue + [TestCase("-80228162514264337593543950335", -999)] // -999 is workaround for not being able to specify decimal.MaxValue public void TestDecimalConverterString(string value, decimal? expected) { var result = JsonSerializer.Deserialize("{ \"test\": \""+ value + "\"}"); - Assert.That(result.Test, Is.EqualTo(expected == -999 ? decimal.MaxValue : expected)); + Assert.That(result.Test, Is.EqualTo(expected == -999 ? decimal.MinValue : expected == 999 ? decimal.MaxValue: expected)); } [TestCase("1", 1)] diff --git a/CryptoExchange.Net/ExchangeHelpers.cs b/CryptoExchange.Net/ExchangeHelpers.cs index 29a5408..668821a 100644 --- a/CryptoExchange.Net/ExchangeHelpers.cs +++ b/CryptoExchange.Net/ExchangeHelpers.cs @@ -348,22 +348,42 @@ namespace CryptoExchange.Net /// public static decimal? ParseDecimal(string? value) { - if (string.IsNullOrEmpty(value) || string.Equals("null", value, StringComparison.OrdinalIgnoreCase)) + // Value is null or empty is the most common case to return null so check before trying to parse + if (string.IsNullOrEmpty(value)) + return null; + + // Try parse, only fails for these reasons: + // 1. string is null or empty + // 2. value is larger or smaller than decimal max/min + // 3. unparsable format + if (decimal.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var decValue)) + return decValue; + + // Check for values which should be parsed to null + if (string.Equals("null", value, StringComparison.OrdinalIgnoreCase) + || string.Equals("NaN", value, StringComparison.OrdinalIgnoreCase)) + { return null; - - if (string.Equals("Infinity", value, StringComparison.Ordinal)) - // Infinity returned by the server, default to max value - return decimal.MaxValue; - - try - { - return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); } - catch (OverflowException) - { - // Value doesn't fit decimal, default to max value + + // Infinity value should be parsed to min/max value + if (string.Equals("Infinity", value, StringComparison.OrdinalIgnoreCase)) return decimal.MaxValue; + else if(string.Equals("-Infinity", value, StringComparison.OrdinalIgnoreCase)) + return decimal.MinValue; + + if (value!.Length > 27 && decimal.TryParse(value.Substring(0, 27), out var overflowValue)) + { + // Not a valid decimal value and more than 27 chars, from which the first part can be parsed correctly. + // assume overflow + if (overflowValue < 0) + return decimal.MinValue; + else + return decimal.MaxValue; } + + // Unknown decimal format, return null + return null; } } } diff --git a/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs b/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs index 3bf0ece..eaa61d6 100644 --- a/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs +++ b/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs @@ -386,7 +386,7 @@ namespace CryptoExchange.Net.Testing.Comparers var stringValue = jsonValue.GetString(); if (objectValue is decimal dec) { - if (decimal.Parse(stringValue!, CultureInfo.InvariantCulture) != dec) + if (ExchangeHelpers.ParseDecimal(stringValue!) != dec) throw new Exception($"{method}: {property} not equal: {stringValue} vs {dec}"); } else if (objectValue is DateTime time)