diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest/ExtendedCalculatorParserTests.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest/ExtendedCalculatorParserTests.cs index b5bee39a8a..6073278a36 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest/ExtendedCalculatorParserTests.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest/ExtendedCalculatorParserTests.cs @@ -245,5 +245,30 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests Assert.IsNotNull(result); Assert.AreEqual(expectedResult, result.Result); } + + private static IEnumerable Interpret_TestScientificNotation_WhenCalled_Data => + new[] + { + new object[] { "0.2E1", "en-US", 2M }, + new object[] { "0,2E1", "pt-PT", 2M }, + }; + + [DataTestMethod] + [DynamicData(nameof(Interpret_TestScientificNotation_WhenCalled_Data))] + public void Interpret_TestScientificNotation_WhenCalled(string input, string sourceCultureName, decimal expectedResult) + { + // Arrange + var translator = NumberTranslator.Create(new CultureInfo(sourceCultureName, false), new CultureInfo("en-US", false)); + var engine = new CalculateEngine(); + + // Act + // Using en-us culture to have a fixed number style + var translatedInput = translator.Translate(input); + var result = engine.Interpret(translatedInput, new CultureInfo("en-US", false), out _); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedResult, result.Result); + } } } diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest/QueryTests.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest/QueryTests.cs index a12744b684..92bc417947 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest/QueryTests.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest/QueryTests.cs @@ -145,6 +145,15 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests [DataRow("pipipie", "pi * pi * pi * e")] [DataRow("(1+1)(3+2)(1+1)(1+1)", "(1+1) * (3+2) * (1+1) * (1+1)")] [DataRow("(1+1) (3+2) (1+1)(1+1)", "(1+1) * (3+2) * (1+1) * (1+1)")] + [DataRow("1.0E2", "(1.0 * 10^(2))")] + [DataRow("-1.0E-2", "(-1.0 * 10^(-2))")] + [DataRow("1.2E2", "(1.2 * 10^(2))")] + [DataRow("5/1.0E2", "5/(1.0 * 10^(2))")] + [DataRow("0.1E2", "(0.1 * 10^(2))")] + [DataRow(".1E2", "(.1 * 10^(2))")] + [DataRow(".1E2", "(.1 * 10^(2))")] + [DataRow("5/5E3", "5/(5 * 10^(3))")] + [DataRow("1.E2", "(1. * 10^(2))")] public void RightHumanMultiplicationExpressionTransformation(string typedString, string expectedQuery) { // Setup @@ -200,6 +209,12 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests [DataRow("pilog(100)", 6.2831853072)] [DataRow("3log(100)", 6)] [DataRow("2e", 5.4365636569)] + [DataRow("2E2", 200)] + [DataRow("2E-2", 0.02)] + [DataRow("1.2E2", 120)] + [DataRow("1.2E-1", 0.12)] + [DataRow("5/5E3", 0.001)] + [DataRow("-5/5E3", -0.001)] [DataRow("(1+1)(3+2)", 10)] [DataRow("(1+1)cos(pi)", -2)] [DataRow("log(100)cos(pi)", -2)] diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateHelper.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateHelper.cs index 50b94b1e54..f63be439e8 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateHelper.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateHelper.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; +using System.Globalization; using System.Text.RegularExpressions; namespace Microsoft.PowerToys.Run.Plugin.Calculator @@ -18,6 +20,7 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator @"sinh\s*\(|cosh\s*\(|tanh\s*\(|arsinh\s*\(|arcosh\s*\(|artanh\s*\(|" + @"pi|" + @"==|~=|&&|\|\||" + + @"((-?(\d+(\.\d*)?)|-?(\.\d+))[E](-?\d+))|" + /* expression from CheckScientificNotation between parenthesis */ @"e|[0-9]|0x[0-9a-fA-F]+|0b[01]+|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]]" + @")+$", RegexOptions.Compiled); @@ -51,7 +54,8 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator public static string FixHumanMultiplicationExpressions(string input) { - var output = CheckNumberOrConstantThenParenthesisExpr(input); + var output = CheckScientificNotation(input); + output = CheckNumberOrConstantThenParenthesisExpr(output); output = CheckNumberOrConstantThenFunc(output); output = CheckParenthesisExprThenFunc(output); output = CheckParenthesisExprThenParenthesisExpr(output); @@ -60,6 +64,22 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator return output; } + private static string CheckScientificNotation(string input) + { + /** + * NOTE: By the time the expression gets to us, it's already in English format. + * + * Regex explanation: + * (-?(\d+({0}\d*)?)|-?({0}\d+)): Used to capture one of two types: + * -?(\d+({0}\d*)?): Captures a decimal number starting with a number (e.g. "-1.23") + * -?({0}\d+): Captures a decimal number without leading number (e.g. ".23") + * E: Captures capital 'E' + * (-?\d+): Captures an integer number (e.g. "-1" or "23") + */ + var p = @"(-?(\d+(\.\d*)?)|-?(\.\d+))E(-?\d+)"; + return Regex.Replace(input, p, "($1 * 10^($5))"); + } + /* * num (exp) * const (exp) diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/NumberTranslator.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/NumberTranslator.cs index 9278f10912..ce27a728bc 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/NumberTranslator.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/NumberTranslator.cs @@ -66,35 +66,47 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator private static string Translate(string input, CultureInfo cultureFrom, CultureInfo cultureTo, Regex splitRegex) { var outputBuilder = new StringBuilder(); + var hexRegex = new Regex(@"(?:(0x[\da-fA-F]+))"); - string[] tokens = splitRegex.Split(input); - foreach (string token in tokens) + string[] hexTokens = hexRegex.Split(input); + + foreach (string hexToken in hexTokens) { - int leadingZeroCount = 0; - - // Count leading zero characters. - foreach (char c in token) + if (hexToken.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) { - if (c != '0') + outputBuilder.Append(hexToken); + continue; + } + + string[] tokens = splitRegex.Split(hexToken); + foreach (string token in tokens) + { + int leadingZeroCount = 0; + + // Count leading zero characters. + foreach (char c in token) { - break; + if (c != '0') + { + break; + } + + leadingZeroCount++; } - leadingZeroCount++; + // number is all zero characters. no need to add zero characters at the end. + if (token.Length == leadingZeroCount) + { + leadingZeroCount = 0; + } + + decimal number; + + outputBuilder.Append( + decimal.TryParse(token, NumberStyles.Number, cultureFrom, out number) + ? (new string('0', leadingZeroCount) + number.ToString(cultureTo)) + : token); } - - // number is all zero characters. no need to add zero characters at the end. - if (token.Length == leadingZeroCount) - { - leadingZeroCount = 0; - } - - decimal number; - - outputBuilder.Append( - decimal.TryParse(token, NumberStyles.Number, cultureFrom, out number) - ? (new string('0', leadingZeroCount) + number.ToString(cultureTo)) - : token); } return outputBuilder.ToString(); @@ -102,7 +114,7 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator private static Regex GetSplitRegex(CultureInfo culture) { - var splitPattern = $"((?:\\d|[a-fA-F]|{Regex.Escape(culture.NumberFormat.NumberDecimalSeparator)}"; + var splitPattern = $"((?:\\d|{Regex.Escape(culture.NumberFormat.NumberDecimalSeparator)}"; if (!string.IsNullOrEmpty(culture.NumberFormat.NumberGroupSeparator)) { splitPattern += $"|{Regex.Escape(culture.NumberFormat.NumberGroupSeparator)}";