diff --git a/release-notes/v0.3.0.md b/release-notes/v0.3.0.md index ad8e03de..79f6dc50 100644 --- a/release-notes/v0.3.0.md +++ b/release-notes/v0.3.0.md @@ -8,15 +8,20 @@ - Upgrade `System.CommandLine` to 2.0.0-beta4.22272.1 [[#350][350]] - Upgrade to .NET 7 RTM [[#351][351]] +#### Data types +- Add support for `float` data type [[#353][353]] + ### Changed * Quote types names in error message emitted when attempting to reassign a variable to an incoercible type. [[#343][343]] ### Fixed ### Tests +- Continued adding more tests for functionality being added to the system. The number of tests increased from 1586 tests in the previous release to 1911 tests in version 0.2.0. This is partly caused by [[#353][353]], which added support for the `float` data type. Each primitive data type supported requires a significant number of tests to be added for the "meta-tests" to complete; these ensure that no data types get added to the system without properly defined semantics for all operator/primitive type combinations. [343]: https://github.com/perlang-org/perlang/pull/343 [344]: https://github.com/perlang-org/perlang/pull/344 [347]: https://github.com/perlang-org/perlang/pull/347 [350]: https://github.com/perlang-org/perlang/pull/350 [351]: https://github.com/perlang-org/perlang/pull/351 +[353]: https://github.com/perlang-org/perlang/pull/353 diff --git a/src/Perlang.Common/Extensions/TypeExtensions.cs b/src/Perlang.Common/Extensions/TypeExtensions.cs index 53de2a68..abdb7281 100644 --- a/src/Perlang.Common/Extensions/TypeExtensions.cs +++ b/src/Perlang.Common/Extensions/TypeExtensions.cs @@ -17,6 +17,7 @@ public static string ToTypeKeyword(this Type type) { } when type == typeof(BigInteger) => "bigint", { } when type == typeof(UInt32) => "uint", { } when type == typeof(UInt64) => "ulong", + { } when type == typeof(Single) => "float", { } when type == typeof(Double) => "double", { } when type == typeof(NullObject) => "null", { } when type == typeof(String) => "string", diff --git a/src/Perlang.Common/NumericToken.cs b/src/Perlang.Common/NumericToken.cs index ebdd5b1c..9264c7b1 100644 --- a/src/Perlang.Common/NumericToken.cs +++ b/src/Perlang.Common/NumericToken.cs @@ -5,13 +5,16 @@ namespace Perlang; public class NumericToken : Token { public bool IsFractional { get; } + public char? Suffix { get; } public Base NumberBase { get; } public NumberStyles NumberStyles { get; } + public bool HasSuffix => Suffix != null; - public NumericToken(string lexeme, int line, string numberCharacters, bool isFractional, Base numberBase, NumberStyles numberStyles) + public NumericToken(string lexeme, int line, string numberCharacters, char? suffix, bool isFractional, Base numberBase, NumberStyles numberStyles) : base(TokenType.NUMBER, lexeme, numberCharacters, line) { IsFractional = isFractional; + Suffix = suffix; NumberBase = numberBase; NumberStyles = numberStyles; } diff --git a/src/Perlang.Interpreter/PerlangInterpreter.cs b/src/Perlang.Interpreter/PerlangInterpreter.cs index e0875124..1878f2a6 100644 --- a/src/Perlang.Interpreter/PerlangInterpreter.cs +++ b/src/Perlang.Interpreter/PerlangInterpreter.cs @@ -1551,35 +1551,70 @@ public VoidObject VisitWhileStmt(Stmt.While stmt) CheckNumberOperands(expr.Operator, left, right); // Bang operator (!) is safe because of the CheckNumberOperands() call above. - if (left is float or double && right is float or double) + if (left is float && right is float) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber - rightNumber; + } + else if (left is float or double && right is float or double) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); return leftNumber - rightNumber; } - else if (left is float or double && right is int or long) + else if (left is float && right is int or long) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + long rightNumber = rightConvertible!.ToInt64(CultureInfo.InvariantCulture); + + return leftNumber - rightNumber; + } + else if (left is double && right is int or long) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); long rightNumber = rightConvertible!.ToInt64(CultureInfo.InvariantCulture); return leftNumber - rightNumber; } - else if (left is float or double && right is uint or ulong) + else if (left is float && right is uint or ulong) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + ulong rightNumber = rightConvertible!.ToUInt64(CultureInfo.InvariantCulture); + + return leftNumber - rightNumber; + } + else if (left is double && right is uint or ulong) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); ulong rightNumber = rightConvertible!.ToUInt64(CultureInfo.InvariantCulture); return leftNumber - rightNumber; } - else if (left is int or long && right is float or double) + else if (left is int or long && right is float) + { + long leftNumber = leftConvertible!.ToInt64(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber - rightNumber; + } + else if (left is int or long && right is double) { long leftNumber = leftConvertible!.ToInt64(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); return leftNumber - rightNumber; } - else if (left is uint or ulong && right is float or double) + else if (left is uint or ulong && right is float) + { + ulong leftNumber = leftConvertible!.ToUInt64(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber - rightNumber; + } + else if (left is uint or ulong && right is double) { ulong leftNumber = leftConvertible!.ToUInt64(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); @@ -1700,35 +1735,70 @@ public VoidObject VisitWhileStmt(Stmt.While stmt) // Regular numeric operand implementation comes after string concatenation. // Bang operator (!) is safe because of the CheckNumberOperands() call above. - if (left is float or double && right is float or double) + if (left is float && right is float) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber + rightNumber; + } + else if (left is float or double && right is float or double) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); return leftNumber + rightNumber; } - else if (left is int or long or uint && right is float or double) + else if (left is int or long && right is float) + { + long leftNumber = leftConvertible!.ToInt64(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber + rightNumber; + } + else if (left is uint or ulong && right is float) + { + ulong leftNumber = leftConvertible!.ToUInt64(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber + rightNumber; + } + else if (left is int or long && right is double) { long leftNumber = leftConvertible!.ToInt64(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); return leftNumber + rightNumber; } - else if (left is uint or ulong && right is float or double) + else if (left is uint or ulong && right is double) { ulong leftNumber = leftConvertible!.ToUInt64(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); return leftNumber + rightNumber; } - else if (left is float or double && right is int or long) + else if (left is float && right is int or long) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + long rightNumber = rightConvertible!.ToInt64(CultureInfo.InvariantCulture); + + return leftNumber + rightNumber; + } + else if (left is double && right is int or long) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); long rightNumber = rightConvertible!.ToInt64(CultureInfo.InvariantCulture); return leftNumber + rightNumber; } - else if (left is float or double && right is uint or ulong) + else if (left is float && right is uint or ulong) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + ulong rightNumber = rightConvertible!.ToUInt64(CultureInfo.InvariantCulture); + + return leftNumber + rightNumber; + } + else if (left is double && right is uint or ulong) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); ulong rightNumber = rightConvertible!.ToUInt64(CultureInfo.InvariantCulture); @@ -1811,7 +1881,16 @@ public VoidObject VisitWhileStmt(Stmt.While stmt) // The method returns the value to be assigned. The assignment takes place elsewhere. // Bang operator (!) is safe because of the CheckNumberOperands() call above. - if (left is float or double && right is float or double) + if (left is float && right is float or double) + { + // Note: `rightNumber` might loose precision here because of conversion from `double` to + // `single`. This is all fine and matches the semantics of other languages perfectly. :-) + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber + rightNumber; + } + else if (left is double && right is float or double) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); @@ -1820,14 +1899,28 @@ public VoidObject VisitWhileStmt(Stmt.While stmt) } /* left is int or long && right is float or double -- deliberately unsupported, since it would lose fractional part */ /* left is uint or ulong && right is float or double -- likewise */ - else if (left is float or double && right is int or long) + else if (left is float && right is int or long) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + long rightNumber = rightConvertible!.ToInt64(CultureInfo.InvariantCulture); + + return leftNumber + rightNumber; + } + else if (left is double && right is int or long) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); long rightNumber = rightConvertible!.ToInt64(CultureInfo.InvariantCulture); return leftNumber + rightNumber; } - else if (left is float or double && right is uint or ulong) + else if (left is float && right is uint or ulong) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + ulong rightNumber = rightConvertible!.ToUInt64(CultureInfo.InvariantCulture); + + return leftNumber + rightNumber; + } + else if (left is double && right is uint or ulong) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); ulong rightNumber = rightConvertible!.ToUInt64(CultureInfo.InvariantCulture); @@ -1909,35 +2002,70 @@ public VoidObject VisitWhileStmt(Stmt.While stmt) CheckNumberOperands(expr.Operator, left, right); // Bang operator (!) is safe because of the CheckNumberOperands() call above. - if (left is float or double && right is float or double) + if (left is float && right is float) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber / rightNumber; + } + else if (left is float or double && right is float or double) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); return leftNumber / rightNumber; } - else if (left is float or double && right is int or long) + else if (left is float && right is int or long) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + long rightNumber = rightConvertible!.ToInt64(CultureInfo.InvariantCulture); + + return leftNumber / rightNumber; + } + else if (left is double && right is int or long) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); long rightNumber = rightConvertible!.ToInt64(CultureInfo.InvariantCulture); return leftNumber / rightNumber; } - else if (left is float or double && right is uint or ulong) + else if (left is float && right is uint or ulong) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + ulong rightNumber = rightConvertible!.ToUInt64(CultureInfo.InvariantCulture); + + return leftNumber / rightNumber; + } + else if (left is double && right is uint or ulong) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); ulong rightNumber = rightConvertible!.ToUInt64(CultureInfo.InvariantCulture); return leftNumber / rightNumber; } - else if (left is int or long && right is float or double) + else if (left is int or long && right is float) + { + long leftNumber = leftConvertible!.ToInt64(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber / rightNumber; + } + else if (left is int or long && right is double) { long leftNumber = leftConvertible!.ToInt64(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); return leftNumber / rightNumber; } - else if (left is uint or ulong && right is float or double) + else if (left is uint or ulong && right is float) + { + ulong leftNumber = leftConvertible!.ToUInt64(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber / rightNumber; + } + else if (left is uint or ulong && right is double) { ulong leftNumber = leftConvertible!.ToUInt64(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); @@ -2019,35 +2147,70 @@ public VoidObject VisitWhileStmt(Stmt.While stmt) CheckNumberOperands(expr.Operator, left, right); // Bang operator (!) is safe because of the CheckNumberOperands() call above. - if (left is float or double && right is float or double) + if (left is float && right is float) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber * rightNumber; + } + else if (left is float or double && right is float or double) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); return leftNumber * rightNumber; } - else if (left is float or double && right is int or long) + else if (left is float && right is int or long) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + long rightNumber = rightConvertible!.ToInt64(CultureInfo.InvariantCulture); + + return leftNumber * rightNumber; + } + else if (left is double && right is int or long) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); long rightNumber = rightConvertible!.ToInt64(CultureInfo.InvariantCulture); return leftNumber * rightNumber; } - else if (left is float or double && right is uint or ulong) + else if (left is float && right is uint or ulong) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + ulong rightNumber = rightConvertible!.ToUInt64(CultureInfo.InvariantCulture); + + return leftNumber * rightNumber; + } + else if (left is double && right is uint or ulong) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); ulong rightNumber = rightConvertible!.ToUInt64(CultureInfo.InvariantCulture); return leftNumber * rightNumber; } - else if (left is int or long && right is float or double) + else if (left is int or long && right is float) + { + long leftNumber = leftConvertible!.ToInt64(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber * rightNumber; + } + else if (left is int or long && right is double) { long leftNumber = leftConvertible!.ToInt64(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); return leftNumber * rightNumber; } - else if (left is uint or ulong && right is float or double) + else if (left is uint or ulong && right is float) + { + ulong leftNumber = leftConvertible!.ToUInt64(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber * rightNumber; + } + else if (left is uint or ulong && right is double) { ulong leftNumber = leftConvertible!.ToUInt64(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); @@ -2186,35 +2349,70 @@ public VoidObject VisitWhileStmt(Stmt.While stmt) CheckNumberOperands(expr.Operator, left, right); // Bang operator (!) is safe because of the CheckNumberOperands() call above. - if (left is float or double && right is float or double) + if (left is float && right is float) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber % rightNumber; + } + else if (left is float or double && right is float or double) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); return leftNumber % rightNumber; } - else if (left is int or long && right is float or double) + else if (left is int or long && right is float) + { + long leftNumber = leftConvertible!.ToInt64(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber % rightNumber; + } + else if (left is int or long && right is double) { long leftNumber = leftConvertible!.ToInt64(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); return leftNumber % rightNumber; } - else if (left is uint or ulong && right is float or double) + else if (left is uint or ulong && right is float) + { + ulong leftNumber = leftConvertible!.ToUInt64(CultureInfo.InvariantCulture); + float rightNumber = rightConvertible!.ToSingle(CultureInfo.InvariantCulture); + + return leftNumber % rightNumber; + } + else if (left is uint or ulong && right is double) { ulong leftNumber = leftConvertible!.ToUInt64(CultureInfo.InvariantCulture); double rightNumber = rightConvertible!.ToDouble(CultureInfo.InvariantCulture); return leftNumber % rightNumber; } - else if (left is float or double && right is int or long) + else if (left is float && right is int or long) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + long rightNumber = rightConvertible!.ToInt64(CultureInfo.InvariantCulture); + + return leftNumber % rightNumber; + } + else if (left is double && right is int or long) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); long rightNumber = rightConvertible!.ToInt64(CultureInfo.InvariantCulture); return leftNumber % rightNumber; } - else if (left is float or double && right is uint or ulong) + else if (left is float && right is uint or ulong) + { + float leftNumber = leftConvertible!.ToSingle(CultureInfo.InvariantCulture); + ulong rightNumber = rightConvertible!.ToUInt64(CultureInfo.InvariantCulture); + + return leftNumber % rightNumber; + } + else if (left is double && right is uint or ulong) { double leftNumber = leftConvertible!.ToDouble(CultureInfo.InvariantCulture); ulong rightNumber = rightConvertible!.ToUInt64(CultureInfo.InvariantCulture); @@ -2472,6 +2670,10 @@ public VoidObject VisitWhileStmt(Stmt.While stmt) { return Convert.ToInt64(value); } + else if (targetTypeReference.ClrType == typeof(Single)) + { + return Convert.ToSingle(value); + } else if (targetTypeReference.ClrType == typeof(Double)) { return Convert.ToDouble(value); diff --git a/src/Perlang.Interpreter/Typing/TypeCoercer.cs b/src/Perlang.Interpreter/Typing/TypeCoercer.cs index 8d29aff9..be9074ec 100644 --- a/src/Perlang.Interpreter/Typing/TypeCoercer.cs +++ b/src/Perlang.Interpreter/Typing/TypeCoercer.cs @@ -36,9 +36,14 @@ public static class TypeCoercer internal static ImmutableDictionary FloatIntegerLengthByType => new Dictionary { - // Double-precision values are 64-bit but can store numbers up to 1.7E +/- 308 (with data loss, i.e. numbers - // larger than +/- 2^53 can not be exactly represented. We presume people working with numbers this large to - // be (or make themselves aware of) this limitation. + // Single-precision values are 32-bit but can store numbers between 1.4E-45 and ~3.40E38 (with data loss, + // i.e. numbers larger or equal than +/- 2^24 can not be exactly represented. We presume people working with + // numbers this large to be (or make themselves aware of) this limitation.) + { typeof(Single), 32 }, + + // Double-precision values are 64-bit but can store numbers between 4.9E-324 and ~1.80E308 (with data loss, + // i.e. numbers larger or equal than +/- 2^53 can not be exactly represented. We presume people working with + // numbers this large to be (or make themselves aware of) this limitation.) { typeof(Double), 64 } }.ToImmutableDictionary(); diff --git a/src/Perlang.Interpreter/Typing/TypeResolver.cs b/src/Perlang.Interpreter/Typing/TypeResolver.cs index fccdc6bd..45f55667 100644 --- a/src/Perlang.Interpreter/Typing/TypeResolver.cs +++ b/src/Perlang.Interpreter/Typing/TypeResolver.cs @@ -139,8 +139,16 @@ public override VoidObject VisitBinaryExpr(Expr.Binary expr) new[] { typeof(int), typeof(long), typeof(uint), typeof(ulong), typeof(float), typeof(double) }.Contains(rightTypeReference.ClrType) && (leftTypeReference.ClrType == typeof(double) || rightTypeReference.ClrType == typeof(double))) { + // Order is important. This branch must come _before_ the float branch, since `float + + // double` and `double + float` is expected to produce a `double`. expr.TypeReference.ClrType = typeof(double); } + else if (new[] { typeof(int), typeof(long), typeof(uint), typeof(ulong), typeof(float), typeof(double) }.Contains(leftTypeReference.ClrType) && + new[] { typeof(int), typeof(long), typeof(uint), typeof(ulong), typeof(float), typeof(double) }.Contains(rightTypeReference.ClrType) && + (leftTypeReference.ClrType == typeof(float) || rightTypeReference.ClrType == typeof(float))) + { + expr.TypeReference.ClrType = typeof(float); + } else if (new[] { typeof(int), typeof(long), typeof(uint), typeof(ulong), typeof(BigInteger) }.Contains(leftTypeReference.ClrType) && new[] { typeof(int), typeof(long), typeof(uint), typeof(ulong), typeof(BigInteger) }.Contains(rightTypeReference.ClrType) && (leftTypeReference.ClrType == typeof(BigInteger) || rightTypeReference.ClrType == typeof(BigInteger))) @@ -176,9 +184,17 @@ public override VoidObject VisitBinaryExpr(Expr.Binary expr) { expr.TypeReference.ClrType = typeof(long); } - else if (new[] { typeof(float), typeof(double) }.Contains(leftTypeReference.ClrType) && - new[] { typeof(int), typeof(long), typeof(uint), typeof(ulong), typeof(float), typeof(double) }.Contains(rightTypeReference.ClrType) && - (leftTypeReference.ClrType == typeof(double) || rightTypeReference.ClrType == typeof(double))) + else if (new[] { typeof(float) }.Contains(leftTypeReference.ClrType) && + new[] { typeof(int), typeof(long), typeof(uint), typeof(ulong), typeof(float) }.Contains(rightTypeReference.ClrType)) + { + // Here it gets interesting: `float` += `double` is legal in Java but *NOT* supported in C# + // (Cannot implicitly convert type 'double' to 'float'). We go with the C# semantics for now + // since it seems like the safer approach. If/when we need to support this, some form of + // explicit casting mechanism would be more suitable. + expr.TypeReference.ClrType = typeof(float); + } + else if (new[] { typeof(double) }.Contains(leftTypeReference.ClrType) && + new[] { typeof(int), typeof(long), typeof(uint), typeof(ulong), typeof(float), typeof(double) }.Contains(rightTypeReference.ClrType)) { expr.TypeReference.ClrType = typeof(double); } @@ -279,8 +295,12 @@ public override VoidObject VisitBinaryExpr(Expr.Binary expr) } else if (new[] { typeof(int), typeof(long), typeof(uint), typeof(ulong), typeof(float), typeof(double) }.Contains(leftTypeReference.ClrType) && new[] { typeof(int), typeof(long), typeof(uint), typeof(ulong), typeof(float), typeof(double) }.Contains(rightTypeReference.ClrType) && - (leftTypeReference.ClrType == typeof(double) || rightTypeReference.ClrType == typeof(double))) + (new[] { typeof(float), typeof(double) }.Contains(leftTypeReference.ClrType) || new[] { typeof(float), typeof(double) }.Contains(rightTypeReference.ClrType))) { + // The above check to ensure that either of the operands is `float` or `double` is important + // here, since e.g. `ulong` and `int` cannot be compared to each other (because of + // not-so-hard-to-understand limitations in the C# language; I'm not even sure this would be + // possible to implement with the existing x64 math instructions) expr.TypeReference.ClrType = typeof(bool); } else if (new[] { typeof(int), typeof(long), typeof(uint), typeof(ulong), typeof(BigInteger) }.Contains(leftTypeReference.ClrType) && @@ -728,6 +748,12 @@ private static void ResolveExplicitTypes(ITypeReference typeReference) typeReference.ClrType = typeof(ulong); break; + // "Float" is called "Single" in C#/.NET, but Java uses `float` and `Float`. In this case, I think it + // makes little sense to make them inconsistent. + case "float" or "Float": + typeReference.ClrType = typeof(float); + break; + case "double" or "Double": typeReference.ClrType = typeof(double); break; diff --git a/src/Perlang.Parser/FloatingPointLiteral.cs b/src/Perlang.Parser/FloatingPointLiteral.cs index aaf47784..cbb7f99a 100644 --- a/src/Perlang.Parser/FloatingPointLiteral.cs +++ b/src/Perlang.Parser/FloatingPointLiteral.cs @@ -21,12 +21,14 @@ public FloatingPointLiteral(T value) BitsUsed = value switch { + float floatValue => (int)Math.Ceiling(Math.Log2(floatValue)), double doubleValue => (int)Math.Ceiling(Math.Log2(doubleValue)), _ => throw new ArgumentException($"Unsupported numeric type encountered: {value.GetType().Name}") }; IsPositive = value switch { + float floatValue => floatValue >= 0, double doubleValue => doubleValue >= 0, _ => throw new ArgumentException($"Unsupported numeric type encountered: {value.GetType().Name}") }; diff --git a/src/Perlang.Parser/NumberParser.cs b/src/Perlang.Parser/NumberParser.cs index 872736ea..0cff2e16 100644 --- a/src/Perlang.Parser/NumberParser.cs +++ b/src/Perlang.Parser/NumberParser.cs @@ -14,13 +14,36 @@ public static INumericLiteral Parse(NumericToken numericToken) if (numericToken.IsFractional) { - // TODO: This is a mess. We currently treat all floating point values as _double_, which is insane. We - // TODO: should probably have a "use smallest possible type" logic as below for integers, for floating point - // TODO: values as well. We could also consider supporting `decimal` while we're at it. + if (numericToken.HasSuffix) + { + switch (numericToken.Suffix) + { + case 'f': + { + // The explicit IFormatProvider is required to ensure we use 123.45 format, regardless of host OS + // language/region settings. See #263 for more details. + float value = Single.Parse(numberCharacters, CultureInfo.InvariantCulture); + return new FloatingPointLiteral(value); + } + + case 'd': + { + // The explicit IFormatProvider is required to ensure we use 123.45 format, regardless of host OS + // language/region settings. See #263 for more details. + double value = Double.Parse(numberCharacters, CultureInfo.InvariantCulture); + return new FloatingPointLiteral(value); + } - // The explicit IFormatProvider is required to ensure we use 123.45 format, regardless of host OS - // language/region settings. See #263 for more details. - return new FloatingPointLiteral(Double.Parse(numberCharacters, CultureInfo.InvariantCulture)); + default: + throw new InvalidOperationException($"Numeric literal suffix {numericToken.Suffix} is not supported"); + } + } + else + { + // No suffix provided => use `double` precision by default, just like C# + double value = Double.Parse(numberCharacters, CultureInfo.InvariantCulture); + return new FloatingPointLiteral(value); + } } else { @@ -85,7 +108,11 @@ public static object MakeNegative(object value) { if (value is INumericLiteral numericLiteral) { - if (numericLiteral.Value is double doubleValue) + if (numericLiteral.Value is float floatValue) + { + return new FloatingPointLiteral(-floatValue); + } + else if (numericLiteral.Value is double doubleValue) { return new FloatingPointLiteral(-doubleValue); } diff --git a/src/Perlang.Parser/Scanner.cs b/src/Perlang.Parser/Scanner.cs index b81316ad..128711d9 100644 --- a/src/Perlang.Parser/Scanner.cs +++ b/src/Perlang.Parser/Scanner.cs @@ -62,7 +62,6 @@ public class Scanner { "sbyte", RESERVED_WORD }, { "short", RESERVED_WORD }, { "ushort", RESERVED_WORD }, - { "float", RESERVED_WORD }, { "decimal", RESERVED_WORD }, { "char", RESERVED_WORD }, @@ -120,6 +119,7 @@ public class Scanner "uint", "ulong", "bigint", + "float", "double", "string" }.ToImmutableHashSet(); @@ -455,12 +455,18 @@ private void Number() } string numberCharacters = RemoveUnderscores(source[(start + startOffset)..current]); + char? suffix = null; + + if (IsAlpha(Peek())) + { + suffix = Advance(); + } // Note that numbers are not parsed at this stage. We deliberately postpone it to the parsing stage, to be // able to conjoin MINUS and NUMBER tokens together for negative numbers. The previous approach (inherited // from Lox) worked poorly with our idea of "narrowing down" constants to smallest possible integer. See // #302 for some more details. - AddToken(new NumericToken(source[start..current], line, numberCharacters, isFractional, numberBase, numberStyles)); + AddToken(new NumericToken(source[start..current], line, numberCharacters, suffix, isFractional, numberBase, numberStyles)); } private static string RemoveUnderscores(string s) @@ -578,6 +584,10 @@ private static bool IsDigit(char c, NumericToken.Base @base) => private bool IsAtEnd() => current >= source.Length; + /// + /// Moves the cursor one step forward and returns the element which was previously current. + /// + /// The current element, before advancing the cursor. private char Advance() { current++; diff --git a/src/Perlang.Tests.Integration/Number/NumberTests.cs b/src/Perlang.Tests.Integration/Number/NumberTests.cs index d02798e6..1cc5ea1c 100644 --- a/src/Perlang.Tests.Integration/Number/NumberTests.cs +++ b/src/Perlang.Tests.Integration/Number/NumberTests.cs @@ -1,6 +1,7 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; +using FluentAssertions; using Perlang.Tests.Integration.Typing; using Xunit; using static Perlang.Tests.Integration.EvalHelper; @@ -133,12 +134,29 @@ public async Task literal_float(CultureInfo cultureInfo) CultureInfo.CurrentCulture = cultureInfo; string source = @" - 123.456 + 123.456f + "; + + object result = Eval(source); + + result.Should() + .Be(123.456f); + } + + [Theory] + [ClassData(typeof(TestCultures))] + public async Task literal_float_has_expected_type(CultureInfo cultureInfo) + { + CultureInfo.CurrentCulture = cultureInfo; + + string source = @" + 123.456f "; object result = Eval(source); - Assert.Equal(123.456, result); + result.Should() + .BeOfType(); } [Theory] @@ -148,12 +166,13 @@ public async Task literal_negative_float(CultureInfo cultureInfo) CultureInfo.CurrentCulture = cultureInfo; string source = @" - -0.001 + -0.001f "; object result = Eval(source); - Assert.Equal(-0.001, result); + result.Should() + .Be(-0.001f); } [Theory] @@ -163,12 +182,13 @@ public async Task literal_float_with_underscore_in_integer_part(CultureInfo cult CultureInfo.CurrentCulture = cultureInfo; string source = @" - 123_45.678 + 123_45.678f "; object result = Eval(source); - Assert.Equal(12345.678, result); + result.Should() + .Be(12345.678f); } [Theory] @@ -178,12 +198,77 @@ public async Task literal_float_with_underscore_in_fractional_part(CultureInfo c CultureInfo.CurrentCulture = cultureInfo; string source = @" - 123.45_678 + 123.45_678f + "; + + object result = Eval(source); + + result.Should() + .Be(123.45678f); + } + + [Theory] + [ClassData(typeof(TestCultures))] + public async Task literal_double_with_suffix(CultureInfo cultureInfo) + { + CultureInfo.CurrentCulture = cultureInfo; + + string source = @" + 123.456d + "; + + object result = Eval(source); + + result.Should() + .Be(123.456d); + } + + [Theory] + [ClassData(typeof(TestCultures))] + public async Task literal_double_with_suffix_has_expected_type(CultureInfo cultureInfo) + { + CultureInfo.CurrentCulture = cultureInfo; + + string source = @" + 123.456d + "; + + object result = Eval(source); + + result.Should() + .BeOfType(); + } + + [Theory] + [ClassData(typeof(TestCultures))] + public async Task literal_double_with_implicit_suffix(CultureInfo cultureInfo) + { + CultureInfo.CurrentCulture = cultureInfo; + + string source = @" + 123.456 + "; + + object result = Eval(source); + + result.Should() + .Be(123.456d); + } + + [Theory] + [ClassData(typeof(TestCultures))] + public async Task literal_double_with_implicit_suffix_has_expected_type(CultureInfo cultureInfo) + { + CultureInfo.CurrentCulture = cultureInfo; + + string source = @" + 123.456 "; object result = Eval(source); - Assert.Equal(123.45678, result); + result.Should() + .BeOfType(); } [Fact] diff --git a/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorData.cs b/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorData.cs index cb581d07..afbb0e69 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorData.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorData.cs @@ -24,38 +24,49 @@ public static class BinaryOperatorData new List { new object[] { "-12", "34.0", "False" }, - new object[] { "2", "4294967295", "False" }, - new object[] { "2", "18446744073709551616", "False" }, - new object[] { "2", "9223372036854775807", "False" }, + new object[] { "2147483647", "4294967295", "False" }, + new object[] { "2147483647", "18446744073709551616", "False" }, + new object[] { "2147483647", "9223372036854775807", "False" }, new object[] { "2147483646", "2147483647", "False" }, new object[] { "2147483647", "2147483646", "True" }, new object[] { "2147483647", "2147483647", "False" }, + new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "False" }, + new object[] { "2147483647", "12.0", "True" }, new object[] { "4294967296", "9223372036854775807", "False" }, - new object[] { "2147483647", "33.0", "True" }, new object[] { "4294967295", "33", "True" }, new object[] { "4294967295", "4294967295", "False" }, new object[] { "4294967295", "9223372036854775807", "False" }, new object[] { "4294967295", "18446744073709551615", "False" }, new object[] { "4294967295", "18446744073709551616", "False" }, new object[] { "4294967295", "12.0", "True" }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "False" }, new object[] { "9223372036854775807", "2", "True" }, new object[] { "9223372036854775807", "4294967295", "True" }, new object[] { "9223372036854775807", "9223372036854775807", "False" }, new object[] { "9223372036854775807", "18446744073709551616", "False" }, + new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "False" }, new object[] { "9223372036854775807", "12.0", "True" }, new object[] { "18446744073709551615", "4294967295", "True" }, new object[] { "18446744073709551615", "18446744073709551615", "False" }, new object[] { "18446744073709551615", "18446744073709551616", "False" }, + new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "False" }, new object[] { "18446744073709551615", "12.0", "True" }, new object[] { "18446744073709551616", "2", "True" }, new object[] { "18446744073709551616", "4294967295", "True" }, new object[] { "18446744073709551616", "9223372036854775807", "True" }, new object[] { "18446744073709551616", "18446744073709551615", "True" }, new object[] { "18446744073709551616", "18446744073709551616", "False" }, - new object[] { "12.0", "34", "False" }, - new object[] { "-12.0", "4294967295", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "12.0", "True" }, + new object[] { "12.0", "2147483647", "False" }, + new object[] { "12.0", "4294967295", "False" }, new object[] { "12.0", "9223372036854775807", "False" }, new object[] { "12.0", "18446744073709551615", "False" }, + new object[] { "12.0", "340282349999999991754788743781432688640.0f", "False" }, new object[] { "12.0", "34.0", "False" }, new object[] { "34.0", "33.0", "True" }, }; @@ -67,47 +78,60 @@ public static class BinaryOperatorData new object[] { "9223372036854775807", "18446744073709551615", "Unsupported > operand types: 'long' and 'ulong'" }, new object[] { "18446744073709551615", "2", "Unsupported > operand types: 'ulong' and 'int'" }, new object[] { "18446744073709551615", "9223372036854775807", "Unsupported > operand types: 'ulong' and 'long'" }, + new object[] { "18446744073709551616", "340282349999999991754788743781432688640.0f", "Unsupported > operand types: 'bigint' and 'float'" }, new object[] { "18446744073709551616", "12.0", "Unsupported > operand types: 'bigint' and 'double'" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551616", "Unsupported > operand types: 'float' and 'bigint'" }, new object[] { "-12.0", "18446744073709551616", "Unsupported > operand types: 'double' and 'bigint'" }, }; public static IEnumerable GreaterEqual => new List { - new object[] { "2", "4294967295", "False" }, - new object[] { "2", "18446744073709551616", "False" }, - new object[] { "2", "9223372036854775807", "False" }, - new object[] { "-12", "34.0", "False" }, + new object[] { "2147483647", "4294967295", "False" }, + new object[] { "2147483647", "18446744073709551616", "False" }, + new object[] { "2147483647", "9223372036854775807", "False" }, + new object[] { "-2147483648", "34.0", "False" }, new object[] { "2147483646", "2147483647", "False" }, new object[] { "2147483647", "2147483647", "True" }, new object[] { "2147483647", "2147483646", "True" }, + new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "False" }, + new object[] { "2147483647", "33.0", "True" }, new object[] { "4294967295", "33", "True" }, new object[] { "4294967295", "4294967295", "True" }, new object[] { "4294967295", "9223372036854775807", "False" }, new object[] { "4294967295", "18446744073709551615", "False" }, new object[] { "4294967295", "18446744073709551616", "False" }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "False" }, new object[] { "4294967295", "12.0", "True" }, new object[] { "9223372036854775807", "2", "True" }, new object[] { "9223372036854775807", "4294967295", "True" }, new object[] { "9223372036854775807", "9223372036854775807", "True" }, new object[] { "9223372036854775807", "18446744073709551616", "False" }, + new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "False" }, new object[] { "9223372036854775807", "12.0", "True" }, new object[] { "18446744073709551615", "4294967295", "True" }, new object[] { "18446744073709551615", "18446744073709551615", "True" }, new object[] { "18446744073709551615", "18446744073709551616", "False" }, + new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "False" }, new object[] { "18446744073709551615", "12.0", "True" }, new object[] { "18446744073709551616", "2", "True" }, new object[] { "18446744073709551616", "4294967295", "True" }, new object[] { "18446744073709551616", "9223372036854775807", "True" }, new object[] { "18446744073709551616", "18446744073709551615", "True" }, new object[] { "18446744073709551616", "18446744073709551616", "True" }, - new object[] { "2147483647", "33.0", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "12.0", "True" }, new object[] { "12.0", "34", "False" }, new object[] { "12.0", "4294967295", "False" }, new object[] { "12.0", "9223372036854775807", "False" }, new object[] { "12.0", "18446744073709551615", "False" }, new object[] { "12.0", "34.0", "False" }, new object[] { "34.0", "33.0", "True" }, + new object[] { "12.0", "340282349999999991754788743781432688640.0f", "False" }, }; public static IEnumerable GreaterEqual_unsupported_types => @@ -117,7 +141,9 @@ public static class BinaryOperatorData new object[] { "9223372036854775807", "18446744073709551615", "Unsupported >= operand types: 'long' and 'ulong'" }, new object[] { "18446744073709551615", "2", "Unsupported >= operand types: 'ulong' and 'int'" }, new object[] { "18446744073709551615", "9223372036854775807", "Unsupported >= operand types: 'ulong' and 'long'" }, + new object[] { "18446744073709551616", "340282349999999991754788743781432688640.0f", "Unsupported >= operand types: 'bigint' and 'float'" }, new object[] { "18446744073709551616", "12.0", "Unsupported >= operand types: 'bigint' and 'double'" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551616", "Unsupported >= operand types: 'float' and 'bigint'" }, new object[] { "-12.0", "18446744073709551616", "Unsupported >= operand types: 'double' and 'bigint'" }, }; @@ -131,37 +157,49 @@ public static class BinaryOperatorData new object[] { "2", "-34", "False" }, new object[] { "2", "34.0", "True" }, new object[] { "-12", "34", "True" }, - new object[] { "-12.0", "-34", "False" }, - new object[] { "-12.0", "9223372036854775807", "True" }, new object[] { "-12", "-34", "False" }, new object[] { "-12", "-34.0", "False" }, new object[] { "2147483647", "33.0", "False" }, + new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "True" }, new object[] { "4294967295", "33", "False" }, new object[] { "4294967295", "4294967295", "False" }, new object[] { "4294967295", "9223372036854775807", "True" }, new object[] { "4294967295", "18446744073709551615", "True" }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "True" }, new object[] { "4294967295", "18446744073709551616", "True" }, new object[] { "4294967295", "12.0", "False" }, new object[] { "9223372036854775807", "2", "False" }, new object[] { "9223372036854775807", "4294967295", "False" }, new object[] { "9223372036854775807", "9223372036854775807", "False" }, new object[] { "9223372036854775807", "18446744073709551616", "True" }, + new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "True" }, new object[] { "9223372036854775807", "12.0", "False" }, new object[] { "18446744073709551615", "4294967295", "False" }, new object[] { "18446744073709551615", "18446744073709551615", "False" }, new object[] { "18446744073709551615", "18446744073709551616", "True" }, + new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "True" }, new object[] { "18446744073709551615", "12.0", "False" }, new object[] { "18446744073709551616", "2", "False" }, new object[] { "18446744073709551616", "4294967295", "False" }, new object[] { "18446744073709551616", "9223372036854775807", "False" }, new object[] { "18446744073709551616", "18446744073709551615", "False" }, + new object[] { "18446744073709551616", "18446744073709551616", "False" }, new object[] { "18446744073709551616", "18446744073709551617", "True" }, new object[] { "12.0", "-34", "False" }, + new object[] { "-12.0", "-34", "False" }, new object[] { "12.0", "4294967295", "True" }, new object[] { "12.0", "9223372036854775807", "True" }, + new object[] { "-12.0", "9223372036854775807", "True" }, new object[] { "12.0", "18446744073709551615", "True" }, - new object[] { "12.0", "34.0", "True" }, + new object[] { "12.0", "340282349999999991754788743781432688640.0f", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "12.0", "False" }, new object[] { "34.0", "33.0", "False" }, + new object[] { "12.0", "34.0", "True" }, }; public static IEnumerable Less_unsupported_types => @@ -171,7 +209,9 @@ public static class BinaryOperatorData new object[] { "9223372036854775807", "18446744073709551615", "Unsupported < operand types: 'long' and 'ulong'" }, new object[] { "18446744073709551615", "2", "Unsupported < operand types: 'ulong' and 'int'" }, new object[] { "18446744073709551615", "9223372036854775807", "Unsupported < operand types: 'ulong' and 'long'" }, + new object[] { "18446744073709551616", "340282349999999991754788743781432688640.0f", "Unsupported < operand types: 'bigint' and 'float'" }, new object[] { "18446744073709551616", "12.0", "Unsupported < operand types: 'bigint' and 'double'" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551616", "Unsupported < operand types: 'float' and 'bigint'" }, new object[] { "12.0", "18446744073709551616", "Unsupported < operand types: 'double' and 'bigint'" }, }; @@ -179,34 +219,43 @@ public static class BinaryOperatorData new List { new object[] { "-2147483648", "4294967295", "True" }, - new object[] { "2", "4294967295", "True" }, - new object[] { "2", "18446744073709551616", "True" }, - new object[] { "2", "9223372036854775807", "True" }, - new object[] { "2", "34.0", "True" }, new object[] { "2147483646", "2147483647", "True" }, + new object[] { "2147483647", "4294967295", "True" }, + new object[] { "2147483647", "18446744073709551616", "True" }, + new object[] { "2147483647", "9223372036854775807", "True" }, new object[] { "2147483647", "2147483647", "True" }, new object[] { "2147483647", "2147483646", "False" }, new object[] { "2147483647", "33.0", "False" }, + new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "True" }, new object[] { "4294967295", "33", "False" }, new object[] { "4294967295", "4294967295", "True" }, new object[] { "4294967295", "9223372036854775807", "True" }, new object[] { "4294967295", "18446744073709551615", "True" }, new object[] { "4294967295", "18446744073709551616", "True" }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "True" }, new object[] { "4294967295", "12.0", "False" }, new object[] { "9223372036854775807", "2", "False" }, new object[] { "9223372036854775807", "4294967295", "False" }, new object[] { "9223372036854775807", "9223372036854775807", "True" }, new object[] { "9223372036854775807", "18446744073709551616", "True" }, + new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "True" }, new object[] { "9223372036854775807", "12.0", "False" }, new object[] { "18446744073709551615", "4294967295", "False" }, new object[] { "18446744073709551615", "18446744073709551615", "True" }, new object[] { "18446744073709551615", "18446744073709551616", "True" }, + new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "True" }, new object[] { "18446744073709551615", "12.0", "False" }, new object[] { "18446744073709551616", "2", "False" }, new object[] { "18446744073709551616", "4294967295", "False" }, new object[] { "18446744073709551616", "9223372036854775807", "False" }, new object[] { "18446744073709551616", "18446744073709551615", "False" }, new object[] { "18446744073709551616", "18446744073709551616", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "34.0", "False" }, new object[] { "12.0", "34.0", "True" }, new object[] { "12.0", "-34", "False" }, new object[] { "12.0", "4294967295", "True" }, @@ -214,7 +263,7 @@ public static class BinaryOperatorData new object[] { "12.0", "18446744073709551615", "True" }, new object[] { "12.0", "34.0", "True" }, new object[] { "34.0", "33.0", "False" }, - new object[] { "34.0", "33.0", "False" }, + new object[] { "34.0", "340282349999999991754788743781432688640.0f", "True" }, }; public static IEnumerable LessEqual_unsupported_types => @@ -224,7 +273,9 @@ public static class BinaryOperatorData new object[] { "9223372036854775807", "18446744073709551615", "Unsupported <= operand types: 'long' and 'ulong'" }, new object[] { "18446744073709551615", "2", "Unsupported <= operand types: 'ulong' and 'int'" }, new object[] { "18446744073709551615", "9223372036854775807", "Unsupported <= operand types: 'ulong' and 'long'" }, + new object[] { "18446744073709551616", "340282349999999991754788743781432688640.0f", "Unsupported <= operand types: 'bigint' and 'float'" }, new object[] { "18446744073709551616", "12.0", "Unsupported <= operand types: 'bigint' and 'double'" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551616", "Unsupported <= operand types: 'float' and 'bigint'" }, new object[] { "-12.0", "18446744073709551616", "Unsupported <= operand types: 'double' and 'bigint'" }, }; @@ -235,41 +286,54 @@ public static class BinaryOperatorData new object[] { "12", "12", "False" }, new object[] { "12", "12.0", "True" }, // Same value but different types. Note: this is truthy in C# AND Java. new object[] { "12.0", "12", "True" }, // Same value but different types. Note: this is truthy in C# AND Java. - new object[] { "12.345", "12.345", "False" }, - new object[] { "12.345", "67.890", "True" }, - new object[] { "2", "4294967295", "True" }, - new object[] { "2", "9223372036854775807", "True" }, - new object[] { "2", "18446744073709551615", "True" }, - new object[] { "2", "18446744073709551616", "True" }, + new object[] { "2147483647", "4294967295", "True" }, + new object[] { "2147483647", "9223372036854775807", "True" }, + new object[] { "2147483647", "18446744073709551615", "True" }, + new object[] { "2147483647", "18446744073709551616", "True" }, + new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "True" }, new object[] { "4294967295", "33", "True" }, new object[] { "4294967295", "4294967295", "False" }, new object[] { "4294967295", "9223372036854775807", "True" }, new object[] { "4294967295", "18446744073709551615", "True" }, new object[] { "4294967295", "18446744073709551616", "True" }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "True" }, new object[] { "4294967295", "12.0", "True" }, new object[] { "9223372036854775807", "2", "True" }, new object[] { "9223372036854775807", "4294967295", "True" }, new object[] { "9223372036854775807", "9223372036854775807", "False" }, new object[] { "9223372036854775807", "18446744073709551615", "True" }, new object[] { "9223372036854775807", "18446744073709551616", "True" }, + new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "True" }, new object[] { "9223372036854775807", "12.0", "True" }, new object[] { "18446744073709551615", "2", "True" }, new object[] { "18446744073709551615", "4294967295", "True" }, new object[] { "18446744073709551615", "9223372036854775807", "True" }, new object[] { "18446744073709551615", "18446744073709551615", "False" }, new object[] { "18446744073709551615", "18446744073709551616", "True" }, + new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "True" }, new object[] { "18446744073709551615", "12.0", "True" }, new object[] { "18446744073709551616", "2", "True" }, new object[] { "18446744073709551616", "4294967295", "True" }, new object[] { "18446744073709551616", "9223372036854775807", "True" }, new object[] { "18446744073709551616", "18446744073709551615", "True" }, new object[] { "18446744073709551616", "18446744073709551616", "False" }, + new object[] { "18446744073709551616", "340282349999999991754788743781432688640.0f", "True" }, new object[] { "18446744073709551616", "12.0", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551616", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "12.0", "True" }, new object[] { "-12.0", "4294967295", "True" }, new object[] { "-12.0", "9223372036854775807", "True" }, new object[] { "-12.0", "18446744073709551615", "True" }, new object[] { "-12.0", "18446744073709551616", "True" }, + new object[] { "-12.0", "340282349999999991754788743781432688640.0f", "True" }, + new object[] { "12.345", "12.345", "False" }, + new object[] { "12.345", "67.890", "True" }, }; public static IEnumerable Equal => @@ -282,38 +346,51 @@ public static class BinaryOperatorData new object[] { "12.345", "12.345", "True" }, new object[] { "12.345", "67.890", "False" }, - new object[] { "2", "4294967295", "False" }, - new object[] { "2", "9223372036854775807", "False" }, - new object[] { "2", "18446744073709551615", "False" }, - new object[] { "2", "18446744073709551616", "False" }, + new object[] { "2147483647", "4294967295", "False" }, + new object[] { "2147483647", "9223372036854775807", "False" }, + new object[] { "2147483647", "18446744073709551615", "False" }, + new object[] { "2147483647", "18446744073709551616", "False" }, + new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "False" }, new object[] { "4294967295", "33", "False" }, new object[] { "4294967295", "4294967295", "True" }, new object[] { "4294967295", "9223372036854775807", "False" }, new object[] { "4294967295", "18446744073709551615", "False" }, new object[] { "4294967295", "18446744073709551616", "False" }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "False" }, new object[] { "4294967295", "12.0", "False" }, - new object[] { "9223372036854775807", "2", "False" }, + new object[] { "9223372036854775807", "2147483647", "False" }, new object[] { "9223372036854775807", "4294967295", "False" }, new object[] { "9223372036854775807", "9223372036854775807", "True" }, new object[] { "9223372036854775807", "18446744073709551615", "False" }, new object[] { "9223372036854775807", "18446744073709551616", "False" }, + new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "False" }, new object[] { "9223372036854775807", "12.0", "False" }, - new object[] { "18446744073709551615", "2", "False" }, + new object[] { "18446744073709551615", "2147483647", "False" }, new object[] { "18446744073709551615", "4294967295", "False" }, new object[] { "18446744073709551615", "9223372036854775807", "False" }, new object[] { "18446744073709551615", "18446744073709551615", "True" }, new object[] { "18446744073709551615", "18446744073709551616", "False" }, + new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "False" }, new object[] { "18446744073709551615", "12.0", "False" }, - new object[] { "18446744073709551616", "2", "False" }, + new object[] { "18446744073709551616", "2147483647", "False" }, new object[] { "18446744073709551616", "4294967295", "False" }, new object[] { "18446744073709551616", "9223372036854775807", "False" }, new object[] { "18446744073709551616", "18446744073709551615", "False" }, new object[] { "18446744073709551616", "18446744073709551616", "True" }, + new object[] { "18446744073709551616", "340282349999999991754788743781432688640.0f", "False" }, new object[] { "18446744073709551616", "12.0", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551616", "False" }, + new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "True" }, + new object[] { "340282349999999991754788743781432688640.0f", "12.0", "False" }, new object[] { "-12.0", "4294967295", "False" }, new object[] { "-12.0", "9223372036854775807", "False" }, new object[] { "-12.0", "18446744073709551615", "False" }, new object[] { "-12.0", "18446744073709551616", "False" }, + new object[] { "-12.0", "340282349999999991754788743781432688640.0f", "False" }, }; public static IEnumerable Subtraction_result => @@ -331,31 +408,43 @@ public static class BinaryOperatorData new object[] { "2", "4294967295", "-4294967293", typeof(long) }, new object[] { "2", "9223372036854775807", "-9223372036854775805", typeof(long) }, new object[] { "2", "18446744073709551616", "-18446744073709551614", typeof(BigInteger) }, - new object[] { "2", "-34.0", "36", typeof(double) }, + + new object[] { "2147483647", "2.0f", "2.1474836E+09", typeof(float) }, + new object[] { "2147483647", "2.0", "2147483645", typeof(double) }, new object[] { "4294967295", "12", "4294967283", typeof(long) }, new object[] { "4294967295", "4294967295", "0", typeof(uint) }, new object[] { "4294967295", "9223372036854775807", "-9223372032559808512", typeof(long) }, // Negative integer overflow new object[] { "4294967295", "18446744073709551615", "4294967296", typeof(ulong) }, // Positive integer overflow new object[] { "4294967295", "18446744073709551616", "-18446744069414584321", typeof(BigInteger) }, - new object[] { "4294967295", "12.0", "4294967283", typeof(double) }, + new object[] { "4294967295", "2.0f", "4.2949673E+09", typeof(float) }, + new object[] { "4294967295", "2.0", "4294967293", typeof(double) }, new object[] { "9223372036854775807", "2", "9223372036854775805", typeof(long) }, new object[] { "9223372036854775807", "4294967295", "9223372032559808512", typeof(long) }, new object[] { "9223372036854775807", "9223372036854775807", "0", typeof(long) }, new object[] { "9223372036854775807", "18446744073709551616", "-9223372036854775809", typeof(BigInteger) }, - new object[] { "9223372036854775807", "12.0", "9.223372036854776E+18", typeof(double) }, + new object[] { "9223372036854775807", "2.0f", "9.223372E+18", typeof(float) }, + new object[] { "9223372036854775807", "2.0", "9.223372036854776E+18", typeof(double) }, new object[] { "18446744073709551615", "4294967295", "18446744069414584320", typeof(ulong) }, new object[] { "18446744073709551615", "18446744073709551615", "0", typeof(ulong) }, new object[] { "18446744073709551615", "18446744073709551616", "-1", typeof(BigInteger) }, - new object[] { "18446744073709551615", "12.0", "1.8446744073709552E+19", typeof(double) }, + new object[] { "18446744073709551615", "2.0f", "1.8446744E+19", typeof(float) }, + new object[] { "18446744073709551615", "2.0", "1.8446744073709552E+19", typeof(double) }, new object[] { "18446744073709551616", "2", "18446744073709551614", typeof(BigInteger) }, new object[] { "18446744073709551616", "4294967295", "18446744069414584321", typeof(BigInteger) }, new object[] { "18446744073709551616", "9223372036854775807", "9223372036854775809", typeof(BigInteger) }, new object[] { "18446744073709551616", "18446744073709551615", "1", typeof(BigInteger) }, new object[] { "18446744073709551616", "18446744073709551617", "-1", typeof(BigInteger) }, + new object[] { "2.0f", "2147483647", "-2.1474836E+09", typeof(float) }, + new object[] { "2.0f", "4294967295", "-4.2949673E+09", typeof(float) }, + new object[] { "2.0f", "9223372036854775807", "-9.223372E+18", typeof(float) }, + new object[] { "2.0f", "18446744073709551615", "-1.8446744E+19", typeof(float) }, + new object[] { "2.0f", "2.0f", "0", typeof(float) }, + new object[] { "2.0f", "2.0", "0", typeof(double) }, new object[] { "-12.0", "-34", "22", typeof(double) }, new object[] { "-12.0", "4294967295", "-4294967307", typeof(double) }, new object[] { "-12.0", "9223372036854775807", "-9.223372036854776E+18", typeof(double) }, new object[] { "-12.0", "18446744073709551615", "-1.8446744073709552E+19", typeof(double) }, + new object[] { "2.0", "2.0f", "0", typeof(double) }, new object[] { "12.0", "34.0", "-22", typeof(double) }, // Doubles with fraction part zero => fraction part excluded in string representation. }; @@ -366,8 +455,10 @@ public static class BinaryOperatorData new object[] { "9223372036854775807", "18446744073709551615", "Unsupported - operand types: 'long' and 'ulong'" }, new object[] { "18446744073709551615", "2", "Unsupported - operand types: 'ulong' and 'int'" }, new object[] { "18446744073709551615", "9223372036854775807", "Unsupported - operand types: 'ulong' and 'long'" }, - new object[] { "18446744073709551616", "12.0", "Unsupported - operand types: 'bigint' and 'double'" }, - new object[] { "-12.0", "18446744073709551616", "Unsupported - operand types: 'double' and 'bigint'" }, + new object[] { "18446744073709551616", "2.0f", "Unsupported - operand types: 'bigint' and 'float'" }, + new object[] { "18446744073709551616", "2.0", "Unsupported - operand types: 'bigint' and 'double'" }, + new object[] { "2.0f", "18446744073709551616", "Unsupported - operand types: 'float' and 'bigint'" }, + new object[] { "2.0", "18446744073709551616", "Unsupported - operand types: 'double' and 'bigint'" }, }; public static IEnumerable SubtractionAssignment_result => @@ -383,6 +474,8 @@ public static class BinaryOperatorData new object[] { "12", "-34", "46", typeof(int) }, new object[] { "-12", "34", "-46", typeof(int) }, new object[] { "-12", "-34", "22", typeof(int) }, + + new object[] { "2147483647", "2147483647", "0", typeof(int) }, new object[] { "4294967295", "4294967295", "0", typeof(uint) }, new object[] { "9223372036854775807", "4294967295", "9223372032559808512", typeof(long) }, new object[] { "9223372036854775807", "9223372036854775807", "0", typeof(long) }, @@ -395,6 +488,12 @@ public static class BinaryOperatorData new object[] { "18446744073709551616", "9223372036854775807", "9223372036854775809", typeof(BigInteger) }, new object[] { "18446744073709551616", "18446744073709551615", "1", typeof(BigInteger) }, new object[] { "18446744073709551616", "18446744073709551617", "-1", typeof(BigInteger) }, + new object[] { "2.0f", "2147483647", "-2.1474836E+09", typeof(float) }, + new object[] { "2.0f", "4294967295", "-4.2949673E+09", typeof(float) }, + new object[] { "2.0f", "9223372036854775807", "-9.223372E+18", typeof(float) }, + new object[] { "2.0f", "18446744073709551615", "-1.8446744E+19", typeof(float) }, + new object[] { "2.0f", "34.0f", "-32", typeof(float) }, + new object[] { "12.0", "34.0f", "-22", typeof(double) }, new object[] { "-12.0", "-34", "22", typeof(double) }, // Doubles with fraction part zero => fraction part excluded in string representation. new object[] { "-12.0", "4294967295", "-4294967307", typeof(double) }, new object[] { "-12.0", "9223372036854775807", "-9.223372036854776E+18", typeof(double) }, @@ -405,25 +504,32 @@ public static class BinaryOperatorData public static IEnumerable SubtractionAssignment_unsupported_types => new List { - new object[] { "2", "4294967295", "Cannot assign 'long' to 'int' variable" }, - new object[] { "2", "9223372036854775807", "Cannot assign 'long' to 'int' variable" }, - new object[] { "2", "18446744073709551615", "Cannot assign 'ulong' to 'int' variable" }, - new object[] { "2", "18446744073709551616", "Cannot assign 'bigint' to 'int' variable" }, - new object[] { "2", "-34.0", "Cannot assign 'double' to 'int' variable" }, - new object[] { "4294967295", "33", "Cannot assign 'long' to 'uint' variable" }, + new object[] { "2147483647", "4294967295", "Cannot assign 'long' to 'int' variable" }, + new object[] { "2147483647", "9223372036854775807", "Cannot assign 'long' to 'int' variable" }, + new object[] { "2147483647", "18446744073709551615", "Cannot assign 'ulong' to 'int' variable" }, + new object[] { "2147483647", "18446744073709551616", "Cannot assign 'bigint' to 'int' variable" }, + new object[] { "2147483647", "2.0f", "Cannot assign 'float' to 'int' variable" }, + new object[] { "2147483647", "2.0", "Cannot assign 'double' to 'int' variable" }, + new object[] { "4294967295", "2147483647", "Cannot assign 'long' to 'uint' variable" }, // TODO: Should work when the rvalue is an integer literal, but requires #352 to be implemented. new object[] { "4294967295", "9223372036854775807", "Cannot assign 'long' to 'uint' variable" }, new object[] { "4294967295", "18446744073709551615", "Cannot assign 'ulong' to 'uint' variable" }, new object[] { "4294967295", "18446744073709551616", "Cannot assign 'bigint' to 'uint' variable" }, - new object[] { "4294967295", "12.0", "Cannot assign 'double' to 'uint' variable" }, + new object[] { "4294967295", "2.0f", "Cannot assign 'float' to 'uint' variable" }, + new object[] { "4294967295", "2.0", "Cannot assign 'double' to 'uint' variable" }, new object[] { "9223372036854775807", "18446744073709551615", "Cannot assign 'ulong' to 'long' variable" }, new object[] { "9223372036854775807", "18446744073709551616", "Cannot assign 'bigint' to 'long' variable" }, - new object[] { "9223372036854775807", "12.0", "Cannot assign 'double' to 'long' variable" }, + new object[] { "9223372036854775807", "2.0f", "Cannot assign 'float' to 'long' variable" }, + new object[] { "9223372036854775807", "2.0", "Cannot assign 'double' to 'long' variable" }, new object[] { "18446744073709551615", "2", "Cannot assign 'int' to 'ulong' variable" }, new object[] { "18446744073709551615", "9223372036854775807", "Cannot assign 'long' to 'ulong' variable" }, - new object[] { "18446744073709551615", "12.0", "Cannot assign 'double' to 'ulong' variable" }, + new object[] { "18446744073709551615", "2.0f", "Cannot assign 'float' to 'ulong' variable" }, + new object[] { "18446744073709551615", "2.0", "Cannot assign 'double' to 'ulong' variable" }, new object[] { "18446744073709551615", "18446744073709551616", "Cannot assign 'bigint' to 'ulong' variable" }, - new object[] { "18446744073709551616", "12.0", "Cannot assign 'double' to 'bigint' variable" }, - new object[] { "-12.0", "18446744073709551616", "Cannot assign 'bigint' to 'double' variable" }, + new object[] { "18446744073709551616", "2.0f", "Cannot assign 'float' to 'bigint' variable" }, + new object[] { "18446744073709551616", "2.0", "Cannot assign 'double' to 'bigint' variable" }, + new object[] { "2.0f", "18446744073709551616", "Cannot assign 'bigint' to 'float' variable" }, + new object[] { "2.0f", "2.0", "Cannot assign 'double' to 'float' variable" }, + new object[] { "2.0", "18446744073709551616", "Cannot assign 'bigint' to 'double' variable" } }; public static IEnumerable Addition_result => @@ -440,10 +546,15 @@ public static class BinaryOperatorData new object[] { "-12", "34", "22", typeof(int) }, new object[] { "-12", "4294967295", "4294967283", typeof(long) }, new object[] { "-12", "-34", "-46", typeof(int) }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "3.4028235E+38", typeof(float) }, + new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "3.4028235E+38", typeof(float) }, + new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "3.4028235E+38", typeof(float) }, + new object[] { "-12", "-34.0", "-46", typeof(double) }, new object[] { "-12", "-34.0", "-46", typeof(double) }, new object[] { "2", "4294967295", "4294967297", typeof(long) }, // `int` + `uint` currently expands to `long` to ensure that all potential return values will fit. This may or may nto be a good idea. new object[] { "2", "9223372036854775807", "-9223372036854775807", typeof(long) }, // Wraparound because of overflow new object[] { "2", "18446744073709551616", "18446744073709551618", typeof(BigInteger) }, + new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "3.4028235E+38", typeof(float) }, new object[] { "4294967295", "2", "4294967297", typeof(long) }, new object[] { "4294967295", "4294967295", "4294967294", typeof(uint) }, // Wraparound because of overflow new object[] { "4294967295", "9223372036854775807", "-9223372032559808514", typeof(long) }, @@ -464,10 +575,17 @@ public static class BinaryOperatorData new object[] { "18446744073709551616", "9223372036854775807", "27670116110564327423", typeof(BigInteger) }, new object[] { "18446744073709551616", "18446744073709551615", "36893488147419103231", typeof(BigInteger) }, new object[] { "18446744073709551616", "18446744073709551617", "36893488147419103233", typeof(BigInteger) }, + new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "3.4028235E+38", typeof(float) }, + new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "3.4028235E+38", typeof(float) }, + new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "3.4028235E+38", typeof(float) }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "3.4028235E+38", typeof(float) }, + new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "Infinity", typeof(float) }, + new object[] { "340282349999999991754788743781432688640.0f", "12.0", "3.4028234663852886E+38", typeof(double) }, new object[] { "12.0", "34", "46", typeof(double) }, new object[] { "12.0", "4294967295", "4294967307", typeof(double) }, new object[] { "12.0", "9223372036854775807", "9.223372036854776E+18", typeof(double) }, new object[] { "12.0", "18446744073709551615", "1.8446744073709552E+19", typeof(double) }, + new object[] { "12.0", "340282349999999991754788743781432688640.0f", "3.4028234663852886E+38", typeof(double) }, new object[] { "12.0", "34.0", "46", typeof(double) }, // Doubles with fraction part zero => fraction part excluded in string representation. new object[] { "12.1", "34.2", "46.300000000000004", typeof(double) }, // IEEE-754... :-) }; @@ -476,11 +594,13 @@ public static class BinaryOperatorData new List { new object[] { "2", "18446744073709551615", "Unsupported + operand types: 'int' and 'ulong'" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551616", "Unsupported + operand types: 'float' and 'bigint'" }, new object[] { "12.0", "18446744073709551616", "Unsupported + operand types: 'double' and 'bigint'" }, new object[] { "9223372036854775807", "18446744073709551615", "Unsupported + operand types: 'long' and 'ulong'" }, new object[] { "18446744073709551615", "2", "Unsupported + operand types: 'ulong' and 'int'" }, new object[] { "18446744073709551615", "9223372036854775807", "Unsupported + operand types: 'ulong' and 'long'" }, new object[] { "18446744073709551616", "12.0", "Unsupported + operand types: 'bigint' and 'double'" }, + new object[] { "18446744073709551616", "340282349999999991754788743781432688640.0f", "Unsupported + operand types: 'bigint' and 'float'" }, }; public static IEnumerable AdditionAssignment_result => @@ -508,12 +628,18 @@ public static class BinaryOperatorData new object[] { "18446744073709551616", "9223372036854775807", "27670116110564327423", typeof(BigInteger) }, new object[] { "18446744073709551616", "18446744073709551615", "36893488147419103231", typeof(BigInteger) }, new object[] { "18446744073709551616", "18446744073709551617", "36893488147419103233", typeof(BigInteger) }, + new object[] { "2.0f", "2147483647", "2.1474836E+09", typeof(float) }, + new object[] { "2.0f", "4294967295", "4.2949673E+09", typeof(float) }, + new object[] { "2.0f", "9223372036854775807", "9.223372E+18", typeof(float) }, + new object[] { "2.0f", "18446744073709551615", "1.8446744E+19", typeof(float) }, + new object[] { "2.0f", "2.0f", "4", typeof(float) }, new object[] { "-12.0", "-34", "-46", typeof(double) }, new object[] { "-12.0", "4294967295", "4294967283", typeof(double) }, new object[] { "-12.0", "9223372036854775807", "9.223372036854776E+18", typeof(double) }, new object[] { "-12.0", "18446744073709551615", "1.8446744073709552E+19", typeof(double) }, new object[] { "12.0", "34.0", "46", typeof(double) }, // Doubles with fraction part zero => fraction part excluded in string representation. new object[] { "12.1", "34.2", "46.300000000000004", typeof(double) }, // IEEE-754... :-) + new object[] { "12.0", "340282349999999991754788743781432688640.0f", "3.4028234663852886E+38", typeof(double) }, }; public static IEnumerable AdditionAssignment_unsupported_types => @@ -524,19 +650,34 @@ public static class BinaryOperatorData new object[] { "2", "18446744073709551615", "Cannot assign 'ulong' to 'int' variable" }, new object[] { "2", "18446744073709551616", "Cannot assign 'bigint' to 'int' variable" }, new object[] { "2", "-34.0", "Cannot assign 'double' to 'int' variable" }, + + // This is where it gets interesting: doing `var i = 2147483647; i += + // 340282349999999991754788743781432688640.0f` is perfectly valid in Java and will be truncated to + // Integer.MAX_VALUE, i.e. 2147483647. However, in C# it'll give you a `Cannot convert source type 'float' + // to target type 'int'` error in Rider; the C# compiler seems to suggest explicit conversion is possible: + // [CS0266] Cannot implicitly convert type 'float' to 'int'. An explicit conversion exists (are you missing + // a cast?). We'll go with the C# semantics for now. + new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "Cannot assign 'float' to 'int' variable" }, + new object[] { "4294967295", "33", "Cannot assign 'long' to 'uint' variable" }, new object[] { "4294967295", "9223372036854775807", "Cannot assign 'long' to 'uint' variable" }, new object[] { "4294967295", "18446744073709551615", "Cannot assign 'ulong' to 'uint' variable" }, new object[] { "4294967295", "18446744073709551616", "Cannot assign 'bigint' to 'uint' variable" }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "Cannot assign 'float' to 'uint' variable" }, new object[] { "4294967295", "12.0", "Cannot assign 'double' to 'uint' variable" }, new object[] { "9223372036854775807", "18446744073709551615", "Cannot assign 'ulong' to 'long' variable" }, new object[] { "9223372036854775807", "18446744073709551616", "Cannot assign 'bigint' to 'long' variable" }, + new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "Cannot assign 'float' to 'long' variable" }, new object[] { "9223372036854775807", "12.0", "Cannot assign 'double' to 'long' variable" }, new object[] { "18446744073709551615", "2", "Cannot assign 'int' to 'ulong' variable" }, new object[] { "18446744073709551615", "12.0", "Cannot assign 'double' to 'ulong' variable" }, new object[] { "18446744073709551615", "9223372036854775807", "Cannot assign 'long' to 'ulong' variable" }, new object[] { "18446744073709551615", "18446744073709551616", "Cannot assign 'bigint' to 'ulong' variable" }, + new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "Cannot assign 'float' to 'ulong' variable" }, new object[] { "18446744073709551616", "12.0", "Cannot assign 'double' to 'bigint' variable" }, + new object[] { "18446744073709551616", "340282349999999991754788743781432688640.0f", "Cannot assign 'float' to 'bigint' variable" }, + new object[] { "2.0f", "18446744073709551616", "Cannot assign 'bigint' to 'float' variable" }, // TODO: could consider supporting this (with precision loss), just like we do for smaller signed/unsigned ints + new object[] { "2.0f", "12.0", "Cannot assign 'double' to 'float' variable" }, new object[] { "-12.0", "18446744073709551616", "Cannot assign 'bigint' to 'double' variable" }, }; @@ -551,33 +692,44 @@ public static class BinaryOperatorData { new object[] { "35", "5", "7", typeof(int) }, new object[] { "34", "5", "6", typeof(int) }, // `int` division => expecting to be truncated. - new object[] { "2", "4294967295", "0", typeof(long) }, - new object[] { "2", "9223372036854775807", "0", typeof(long) }, - new object[] { "2", "18446744073709551616", "0", typeof(BigInteger) }, + new object[] { "2147483647", "4294967295", "0", typeof(long) }, + new object[] { "2147483647", "9223372036854775807", "0", typeof(long) }, + new object[] { "2147483647", "18446744073709551616", "0", typeof(BigInteger) }, + new object[] { "2147483647", "340282349999999991754788743781432688640.0f", "6.310888E-30", typeof(float) }, new object[] { "4294967295", "2", "2147483647", typeof(long) }, new object[] { "4294967295", "4294967295", "1", typeof(uint) }, new object[] { "4294967295", "9223372036854775807", "0", typeof(long) }, new object[] { "4294967295", "18446744073709551615", "0", typeof(ulong) }, new object[] { "4294967295", "18446744073709551616", "0", typeof(BigInteger) }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "1.2621776E-29", typeof(float) }, new object[] { "4294967295", "12.0", "357913941.25", typeof(double) }, new object[] { "9223372036854775807", "2", "4611686018427387903", typeof(long) }, new object[] { "9223372036854775807", "4294967295", "2147483648", typeof(long) }, new object[] { "9223372036854775807", "9223372036854775807", "1", typeof(long) }, new object[] { "9223372036854775807", "18446744073709551616", "0", typeof(BigInteger) }, + new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "2.7105058E-20", typeof(float) }, new object[] { "9223372036854775807", "12.0", "7.686143364045646E+17", typeof(double) }, new object[] { "18446744073709551615", "4294967295", "4294967297", typeof(ulong) }, new object[] { "18446744073709551615", "18446744073709551615", "1", typeof(ulong) }, new object[] { "18446744073709551615", "18446744073709551616", "0", typeof(BigInteger) }, + new object[] { "18446744073709551615", "340282349999999991754788743781432688640.0f", "5.4210115E-20", typeof(float) }, new object[] { "18446744073709551615", "12.0", "1.5372286728091292E+18", typeof(double) }, new object[] { "18446744073709551616", "2", "9223372036854775808", typeof(BigInteger) }, new object[] { "18446744073709551616", "4294967295", "4294967297", typeof(BigInteger) }, new object[] { "18446744073709551616", "9223372036854775807", "2", typeof(BigInteger) }, new object[] { "18446744073709551616", "18446744073709551615", "1", typeof(BigInteger) }, new object[] { "18446744073709551616", "18446744073709551616", "1", typeof(BigInteger) }, + new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "1.5845632E+29", typeof(float) }, + new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "7.922816E+28", typeof(float) }, + new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "3.6893486E+19", typeof(float) }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "1.8446743E+19", typeof(float) }, + new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "1", typeof(float) }, + new object[] { "340282349999999991754788743781432688640.0f", "12.0", "2.8356862219877405E+37", typeof(double) }, new object[] { "34.0", "5", "6.8", typeof(double) }, new object[] { "12.0", "4294967295", "2.793967724496957E-09", typeof(double) }, new object[] { "12.0", "18446744073709551615", "6.505213034913027E-19", typeof(double) }, new object[] { "34.0", "9223372036854775807", "3.686287386450715E-18", typeof(double) }, + new object[] { "34.0", "340282349999999991754788743781432688640.0f", "9.991702577541327E-38", typeof(double) }, new object[] { "34.0", "5.0", "6.8", typeof(double) }, new object[] { "34", "5.0", "6.8", typeof(double) } }; @@ -589,7 +741,9 @@ public static class BinaryOperatorData new object[] { "9223372036854775807", "18446744073709551615", "Unsupported / operand types: 'long' and 'ulong'" }, new object[] { "18446744073709551615", "2", "Unsupported / operand types: 'ulong' and 'int'" }, new object[] { "18446744073709551615", "9223372036854775807", "Unsupported / operand types: 'ulong' and 'long'" }, + new object[] { "18446744073709551616", "340282349999999991754788743781432688640.0f", "Unsupported / operand types: 'bigint' and 'float'" }, new object[] { "18446744073709551616", "12.0", "Unsupported / operand types: 'bigint' and 'double'" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551616", "Unsupported / operand types: 'float' and 'bigint'" }, new object[] { "12.0", "18446744073709551616", "Unsupported / operand types: 'double' and 'bigint'" }, }; @@ -608,30 +762,41 @@ public static class BinaryOperatorData new object[] { "2", "18446744073709551616", "36893488147419103232", typeof(BigInteger) }, new object[] { "12", "34.0", "408", typeof(double) }, new object[] { "1073741824", "2", "-2147483648", typeof(int) }, // Becomes negative because of signed `int` overflow. + new object[] { "2147483647", "2.0f", "4.2949673E+09", typeof(float) }, new object[] { "4294967295", "2", "8589934590", typeof(long) }, new object[] { "4294967295", "4294967295", "1", typeof(uint) }, // Unsigned integer overflow new object[] { "4294967295", "9223372036854775807", "9223372032559808513", typeof(long) }, // Signed integer overflow new object[] { "4294967295", "18446744073709551615", "18446744069414584321", typeof(ulong) }, // Unsigned integer overflow new object[] { "4294967295", "18446744073709551616", "79228162495817593519834398720", typeof(BigInteger) }, + new object[] { "4294967295", "2.0f", "8.589935E+09", typeof(float) }, new object[] { "4294967295", "12.0", "51539607540", typeof(double) }, new object[] { "9223372036854775807", "2", "-2", typeof(long) }, new object[] { "9223372036854775807", "4294967295", "9223372032559808513", typeof(long) }, // Overflow new object[] { "9223372036854775807", "9223372036854775807", "1", typeof(long) }, new object[] { "9223372036854775807", "18446744073709551616", "170141183460469231713240559642174554112", typeof(BigInteger) }, + new object[] { "9223372036854775807", "2.0f", "1.8446744E+19", typeof(float) }, new object[] { "9223372036854775807", "12.0", "1.1068046444225731E+20", typeof(double) }, new object[] { "18446744073709551615", "4294967295", "18446744069414584321", typeof(ulong) }, // Unsigned integer overflow new object[] { "18446744073709551615", "18446744073709551615", "1", typeof(ulong) }, // Unsigned integer overflow new object[] { "18446744073709551615", "18446744073709551616", "340282366920938463444927863358058659840", typeof(BigInteger) }, + new object[] { "18446744073709551615", "2.0f", "3.689349E+19", typeof(float) }, new object[] { "18446744073709551615", "12.0", "2.2136092888451462E+20", typeof(double) }, new object[] { "18446744073709551616", "2", "36893488147419103232", typeof(BigInteger) }, new object[] { "18446744073709551616", "4294967295", "79228162495817593519834398720", typeof(BigInteger) }, new object[] { "18446744073709551616", "9223372036854775807", "170141183460469231713240559642174554112", typeof(BigInteger) }, new object[] { "18446744073709551616", "18446744073709551615", "340282366920938463444927863358058659840", typeof(BigInteger) }, new object[] { "18446744073709551616", "18446744073709551616", "340282366920938463463374607431768211456", typeof(BigInteger) }, + new object[] { "2.0f", "2147483647", "4.2949673E+09", typeof(float) }, + new object[] { "2.0f", "4294967295", "8.589935E+09", typeof(float) }, + new object[] { "2.0f", "9223372036854775807", "1.8446744E+19", typeof(float) }, + new object[] { "2.0f", "18446744073709551615", "3.689349E+19", typeof(float) }, + new object[] { "340282349999999991754788743781432688640.0f", "2.0f", "Infinity", typeof(float) }, + new object[] { "340282349999999991754788743781432688640.0f", "2.0", "6.805646932770577E+38", typeof(double) }, new object[] { "12.0", "5", "60", typeof(double) }, new object[] { "12.0", "4294967295", "51539607540", typeof(double) }, new object[] { "12.0", "9223372036854775807", "1.1068046444225731E+20", typeof(double) }, new object[] { "12.0", "18446744073709551615", "2.2136092888451462E+20", typeof(double) }, + new object[] { "12.34", "340282349999999991754788743781432688640.0f", "4.199084157519446E+39", typeof(double) }, new object[] { "12.34", "0.3", "3.702", typeof(double) } }; @@ -642,7 +807,9 @@ public static class BinaryOperatorData new object[] { "9223372036854775807", "18446744073709551615", "Unsupported * operand types: 'long' and 'ulong'" }, new object[] { "18446744073709551615", "2", "Unsupported * operand types: 'ulong' and 'int'" }, new object[] { "18446744073709551615", "9223372036854775807", "Unsupported * operand types: 'ulong' and 'long'" }, - new object[] { "18446744073709551616", "12.0", "Unsupported * operand types: 'bigint' and 'double'" }, + new object[] { "18446744073709551616", "2.0f", "Unsupported * operand types: 'bigint' and 'float'" }, + new object[] { "18446744073709551616", "2.0", "Unsupported * operand types: 'bigint' and 'double'" }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551616", "Unsupported * operand types: 'float' and 'bigint'" }, new object[] { "12.0", "18446744073709551616", "Unsupported * operand types: 'double' and 'bigint'" }, }; @@ -659,16 +826,28 @@ public static class BinaryOperatorData new object[] { "2.1", "10", "1667.9880978201006", typeof(double) }, new object[] { "2.0", "10.0", "1024", typeof(double) }, new object[] { "2", "9.9", "955.425783333691", typeof(double) }, + new object[] { "2147483647", "2.0f", "4.6116860141324206E+18", typeof(double) }, + new object[] { "2147483647", "2.0", "4.6116860141324206E+18", typeof(double) }, new object[] { "4294967295", "2", "18446744065119617025", typeof(BigInteger) }, + new object[] { "4294967295", "340282349999999991754788743781432688640.0f", "Infinity", typeof(double) }, new object[] { "4294967295", "12.0", "3.9402006086306546E+115", typeof(double) }, new object[] { "9223372036854775807", "2", "85070591730234615847396907784232501249", typeof(BigInteger) }, + new object[] { "9223372036854775807", "2.0f", "8.507059173023462E+37", typeof(double) }, new object[] { "9223372036854775807", "2.0", "8.507059173023462E+37", typeof(double) }, new object[] { "18446744073709551615", "2", "340282366920938463426481119284349108225", typeof(BigInteger) }, - new object[] { "18446744073709551615", "12.0", "1.552518092300709E+231", typeof(double) }, + new object[] { "18446744073709551615", "2.0f", "3.402823669209385E+38", typeof(double) }, + new object[] { "18446744073709551615", "2.0", "3.402823669209385E+38", typeof(double) }, new object[] { "18446744073709551616", "2", "340282366920938463463374607431768211456", typeof(BigInteger) }, + new object[] { "340282349999999991754788743781432688640.0f", "2147483647", "Infinity", typeof(double) }, + new object[] { "340282349999999991754788743781432688640.0f", "4294967295", "Infinity", typeof(double) }, + new object[] { "340282349999999991754788743781432688640.0f", "9223372036854775807", "Infinity", typeof(double) }, + new object[] { "340282349999999991754788743781432688640.0f", "18446744073709551615", "Infinity", typeof(double) }, + new object[] { "340282349999999991754788743781432688640.0f", "340282349999999991754788743781432688640.0f", "Infinity", typeof(double) }, + new object[] { "340282349999999991754788743781432688640.0f", "12.0", "Infinity", typeof(double) }, new object[] { "2.0", "4294967295", "Infinity", typeof(double) }, new object[] { "2.0", "9223372036854775807", "Infinity", typeof(double) }, new object[] { "12.0", "18446744073709551615", "Infinity", typeof(double) }, + new object[] { "12.0", "340282349999999991754788743781432688640.0f", "Infinity", typeof(double) }, }; public static IEnumerable Exponential_unsupported_types => @@ -694,8 +873,10 @@ public static class BinaryOperatorData new object[] { "18446744073709551616", "9223372036854775807", "Unsupported ** operand types: 'bigint' and 'long'" }, new object[] { "18446744073709551616", "18446744073709551615", "Unsupported ** operand types: 'bigint' and 'ulong'" }, new object[] { "18446744073709551616", "18446744073709551616", "Unsupported ** operand types: 'bigint' and 'bigint'" }, - new object[] { "18446744073709551616", "12.0", "Unsupported ** operand types: 'bigint' and 'double'" }, - new object[] { "-12.0", "18446744073709551616", "Unsupported ** operand types: 'double' and 'bigint'" }, + new object[] { "18446744073709551616", "2.0f", "Unsupported ** operand types: 'bigint' and 'float'" }, + new object[] { "18446744073709551616", "2.0", "Unsupported ** operand types: 'bigint' and 'double'" }, + new object[] { "2.0", "18446744073709551616", "Unsupported ** operand types: 'double' and 'bigint'" }, + new object[] { "2.0f", "18446744073709551616", "Unsupported ** operand types: 'float' and 'bigint'" }, }; public static IEnumerable Modulo_result => @@ -713,32 +894,45 @@ public static class BinaryOperatorData new object[] { "2", "18446744073709551616", "2", typeof(BigInteger) }, new object[] { "9", "2.0", "1", typeof(double) }, new object[] { "2147483647", "2", "1", typeof(int) }, + new object[] { "2147483647", "2.0f", "0", typeof(float) }, // IEEE-754 :-) + new object[] { "2147483647", "2.0", "1", typeof(double) }, new object[] { "-2147483648", "2", "0", typeof(int) }, new object[] { "4294967295", "2", "1", typeof(long) }, new object[] { "4294967295", "4294967295", "0", typeof(uint) }, new object[] { "4294967295", "9223372036854775807", "4294967295", typeof(long) }, new object[] { "4294967295", "18446744073709551615", "4294967295", typeof(ulong) }, new object[] { "4294967295", "18446744073709551616", "4294967295", typeof(BigInteger) }, + new object[] { "4294967295", "12", "3", typeof(long) }, // TODO: should be uint + new object[] { "4294967295", "12.0f", "4", typeof(float) }, // IEEE-754 :-) new object[] { "4294967295", "12.0", "3", typeof(double) }, new object[] { "9223372036854775807", "2", "1", typeof(long) }, new object[] { "9223372036854775807", "4294967295", "2147483647", typeof(long) }, new object[] { "9223372036854775807", "9223372036854775807", "0", typeof(long) }, new object[] { "9223372036854775807", "18446744073709551616", "9223372036854775807", typeof(BigInteger) }, + new object[] { "9223372036854775807", "12.0f", "8", typeof(float) }, new object[] { "9223372036854775807", "12.0", "8", typeof(double) }, new object[] { "18446744073709551615", "4294967295", "0", typeof(ulong) }, new object[] { "18446744073709551615", "18446744073709551615", "0", typeof(ulong) }, new object[] { "18446744073709551615", "18446744073709551616", "18446744073709551615", typeof(BigInteger) }, + new object[] { "18446744073709551615", "12.0f", "4", typeof(float) }, new object[] { "18446744073709551615", "12.0", "4", typeof(double) }, new object[] { "18446744073709551616", "3", "1", typeof(BigInteger) }, new object[] { "18446744073709551616", "4294967295", "1", typeof(BigInteger) }, new object[] { "18446744073709551616", "9223372036854775807", "2", typeof(BigInteger) }, new object[] { "18446744073709551616", "18446744073709551615", "1", typeof(BigInteger) }, new object[] { "18446744073709551616", "18446744073709551616", "0", typeof(BigInteger) }, - new object[] { "9.0", "2.0", "1", typeof(double) }, + new object[] { "9.0f", "2147483647", "9", typeof(float) }, + new object[] { "9.0f", "4294967295", "9", typeof(float) }, + new object[] { "9.0f", "9223372036854775807", "9", typeof(float) }, + new object[] { "9.0f", "18446744073709551615", "9", typeof(float) }, + new object[] { "9.0f", "2.0f", "1", typeof(float) }, + new object[] { "9.0f", "2.0", "1", typeof(double) }, new object[] { "12.0", "2", "0", typeof(double) }, new object[] { "12.0", "4294967295", "12", typeof(double) }, new object[] { "12.0", "9223372036854775807", "12", typeof(double) }, new object[] { "12.0", "18446744073709551615", "12", typeof(double) }, + new object[] { "9.0", "2.0f", "1", typeof(double) }, + new object[] { "9.0", "2.0", "1", typeof(double) }, }; public static IEnumerable Modulo_unsupported_types => @@ -748,8 +942,10 @@ public static class BinaryOperatorData new object[] { "9223372036854775807", "18446744073709551615", "Unsupported * operand types: 'long' and 'ulong'" }, new object[] { "18446744073709551615", "2", "Unsupported * operand types: 'ulong' and 'int'" }, new object[] { "18446744073709551615", "9223372036854775807", "Unsupported * operand types: 'ulong' and 'long'" }, - new object[] { "18446744073709551616", "12.0", "Unsupported * operand types: 'bigint' and 'double'" }, - new object[] { "-12.0", "18446744073709551616", "Unsupported * operand types: 'double' and 'bigint'" }, + new object[] { "18446744073709551616", "2.0f", "Unsupported * operand types: 'bigint' and 'float'" }, + new object[] { "18446744073709551616", "2.0", "Unsupported * operand types: 'bigint' and 'double'" }, + new object[] { "2.0f", "18446744073709551616", "Unsupported * operand types: 'float' and 'bigint'" }, + new object[] { "2.0", "18446744073709551616", "Unsupported * operand types: 'double' and 'bigint'" }, }; public static IEnumerable ShiftLeft_result => @@ -772,37 +968,50 @@ public static class BinaryOperatorData public static IEnumerable ShiftLeft_unsupported_types => new List { - new object[] { "2", "4294967295", "Unsupported << operand types: 'int' and 'uint'" }, - new object[] { "2", "9223372036854775807", "Unsupported << operand types: 'int' and 'long'" }, - new object[] { "2", "18446744073709551615", "Unsupported << operand types: 'int' and 'ulong'" }, - new object[] { "2", "18446744073709551616", "Unsupported << operand types: 'int' and 'bigint'" }, - new object[] { "2", "12.0", "Unsupported << operand types: 'int' and 'double'" }, + new object[] { "2147483647", "4294967295", "Unsupported << operand types: 'int' and 'uint'" }, + new object[] { "2147483647", "9223372036854775807", "Unsupported << operand types: 'int' and 'long'" }, + new object[] { "2147483647", "18446744073709551615", "Unsupported << operand types: 'int' and 'ulong'" }, + new object[] { "2147483647", "18446744073709551616", "Unsupported << operand types: 'int' and 'bigint'" }, + new object[] { "2147483647", "2.0f", "Unsupported << operand types: 'int' and 'float'" }, + new object[] { "2147483647", "2.0", "Unsupported << operand types: 'int' and 'double'" }, new object[] { "4294967295", "4294967295", "Unsupported << operand types: 'uint' and 'uint'" }, new object[] { "4294967295", "9223372036854775807", "Unsupported << operand types: 'uint' and 'long'" }, new object[] { "4294967295", "18446744073709551615", "Unsupported << operand types: 'uint' and 'ulong'" }, new object[] { "4294967295", "18446744073709551616", "Unsupported << operand types: 'uint' and 'bigint'" }, - new object[] { "4294967295", "12.0", "Unsupported << operand types: 'uint' and 'double'" }, + new object[] { "4294967295", "2.0f", "Unsupported << operand types: 'uint' and 'float'" }, + new object[] { "4294967295", "2.0", "Unsupported << operand types: 'uint' and 'double'" }, new object[] { "9223372036854775807", "4294967295", "Unsupported << operand types: 'long' and 'uint'" }, new object[] { "9223372036854775807", "9223372036854775807", "Unsupported << operand types: 'long' and 'long'" }, new object[] { "9223372036854775807", "18446744073709551615", "Unsupported << operand types: 'long' and 'ulong'" }, new object[] { "9223372036854775807", "18446744073709551616", "Unsupported << operand types: 'long' and 'bigint'" }, - new object[] { "9223372036854775807", "12.0", "Unsupported << operand types: 'long' and 'double'" }, + new object[] { "9223372036854775807", "2.0f", "Unsupported << operand types: 'long' and 'float'" }, + new object[] { "9223372036854775807", "2.0", "Unsupported << operand types: 'long' and 'double'" }, new object[] { "18446744073709551615", "4294967295", "Unsupported << operand types: 'ulong' and 'uint'" }, new object[] { "18446744073709551615", "9223372036854775807", "Unsupported << operand types: 'ulong' and 'long'" }, new object[] { "18446744073709551615", "18446744073709551615", "Unsupported << operand types: 'ulong' and 'ulong'" }, new object[] { "18446744073709551615", "18446744073709551616", "Unsupported << operand types: 'ulong' and 'bigint'" }, - new object[] { "18446744073709551615", "12.0", "Unsupported << operand types: 'ulong' and 'double'" }, + new object[] { "18446744073709551615", "2.0f", "Unsupported << operand types: 'ulong' and 'float'" }, + new object[] { "18446744073709551615", "2.0", "Unsupported << operand types: 'ulong' and 'double'" }, new object[] { "18446744073709551616", "4294967295", "Unsupported << operand types: 'bigint' and 'uint'" }, new object[] { "18446744073709551616", "9223372036854775807", "Unsupported << operand types: 'bigint' and 'long'" }, new object[] { "18446744073709551616", "18446744073709551615", "Unsupported << operand types: 'bigint' and 'ulong'" }, new object[] { "18446744073709551616", "18446744073709551616", "Unsupported << operand types: 'bigint' and 'bigint'" }, - new object[] { "18446744073709551616", "12.0", "Unsupported << operand types: 'bigint' and 'double'" }, - new object[] { "12.0", "2", "Unsupported << operand types: 'double' and 'int'" }, - new object[] { "12.0", "4294967295", "Unsupported << operand types: 'double' and 'uint'" }, - new object[] { "12.0", "9223372036854775807", "Unsupported << operand types: 'double' and 'long'" }, - new object[] { "12.0", "18446744073709551615", "Unsupported << operand types: 'double' and 'ulong'" }, - new object[] { "12.0", "18446744073709551616", "Unsupported << operand types: 'double' and 'bigint'" }, - new object[] { "12.0", "12.0", "Unsupported << operand types: 'double' and 'double'" }, + new object[] { "18446744073709551616", "2.0f", "Unsupported << operand types: 'bigint' and 'float'" }, + new object[] { "18446744073709551616", "2.0", "Unsupported << operand types: 'bigint' and 'double'" }, + new object[] { "1.0f", "10", "Unsupported << operand types: 'float' and 'int'" }, + new object[] { "1.0f", "4294967295", "Unsupported << operand types: 'float' and 'uint'" }, + new object[] { "1.0f", "9223372036854775807", "Unsupported << operand types: 'float' and 'long'" }, + new object[] { "1.0f", "18446744073709551615", "Unsupported << operand types: 'float' and 'ulong'" }, + new object[] { "1.0f", "18446744073709551616", "Unsupported << operand types: 'float' and 'bigint'" }, + new object[] { "1.0f", "10.0f", "Unsupported << operand types: 'float' and 'float'" }, + new object[] { "1.0f", "10.0", "Unsupported << operand types: 'float' and 'double'" }, + new object[] { "1.0", "10", "Unsupported << operand types: 'double' and 'int'" }, + new object[] { "1.0", "4294967295", "Unsupported << operand types: 'double' and 'uint'" }, + new object[] { "1.0", "9223372036854775807", "Unsupported << operand types: 'double' and 'long'" }, + new object[] { "1.0", "18446744073709551615", "Unsupported << operand types: 'double' and 'ulong'" }, + new object[] { "1.0", "18446744073709551616", "Unsupported << operand types: 'double' and 'bigint'" }, + new object[] { "1.0", "10.0f", "Unsupported << operand types: 'double' and 'float'" }, + new object[] { "1.0", "10.0", "Unsupported << operand types: 'double' and 'double'" }, }; public static IEnumerable ShiftRight_result => @@ -824,37 +1033,50 @@ public static class BinaryOperatorData public static IEnumerable ShiftRight_unsupported_types => new List { - new object[] { "2", "4294967295", "Unsupported >> operand types: 'int' and 'uint'" }, - new object[] { "2", "9223372036854775807", "Unsupported >> operand types: 'int' and 'long'" }, - new object[] { "2", "18446744073709551615", "Unsupported >> operand types: 'int' and 'ulong'" }, - new object[] { "2", "18446744073709551616", "Unsupported >> operand types: 'int' and 'bigint'" }, - new object[] { "2", "12.0", "Unsupported >> operand types: 'int' and 'double'" }, + new object[] { "2147483647", "4294967295", "Unsupported >> operand types: 'int' and 'uint'" }, + new object[] { "2147483647", "9223372036854775807", "Unsupported >> operand types: 'int' and 'long'" }, + new object[] { "2147483647", "18446744073709551615", "Unsupported >> operand types: 'int' and 'ulong'" }, + new object[] { "2147483647", "18446744073709551616", "Unsupported >> operand types: 'int' and 'bigint'" }, + new object[] { "2147483647", "2.0f", "Unsupported >> operand types: 'int' and 'float'" }, + new object[] { "2147483647", "2.0", "Unsupported >> operand types: 'int' and 'double'" }, new object[] { "4294967295", "4294967295", "Unsupported >> operand types: 'uint' and 'uint'" }, new object[] { "4294967295", "9223372036854775807", "Unsupported >> operand types: 'uint' and 'long'" }, new object[] { "4294967295", "18446744073709551615", "Unsupported >> operand types: 'uint' and 'ulong'" }, new object[] { "4294967295", "18446744073709551616", "Unsupported >> operand types: 'uint' and 'bigint'" }, - new object[] { "4294967295", "12.0", "Unsupported >> operand types: 'uint' and 'double'" }, + new object[] { "4294967295", "2.0f", "Unsupported >> operand types: 'uint' and 'float'" }, + new object[] { "4294967295", "2.0", "Unsupported >> operand types: 'uint' and 'double'" }, new object[] { "9223372036854775807", "4294967295", "Unsupported >> operand types: 'long' and 'uint'" }, new object[] { "9223372036854775807", "9223372036854775807", "Unsupported >> operand types: 'long' and 'long'" }, new object[] { "9223372036854775807", "18446744073709551615", "Unsupported >> operand types: 'long' and 'ulong'" }, new object[] { "9223372036854775807", "18446744073709551616", "Unsupported >> operand types: 'long' and 'bigint'" }, - new object[] { "9223372036854775807", "12.0", "Unsupported >> operand types: 'long' and 'double'" }, + new object[] { "9223372036854775807", "2.0f", "Unsupported >> operand types: 'long' and 'float'" }, + new object[] { "9223372036854775807", "2.0", "Unsupported >> operand types: 'long' and 'double'" }, new object[] { "18446744073709551615", "4294967295", "Unsupported >> operand types: 'ulong' and 'uint'" }, new object[] { "18446744073709551615", "9223372036854775807", "Unsupported >> operand types: 'ulong' and 'long'" }, new object[] { "18446744073709551615", "18446744073709551615", "Unsupported >> operand types: 'ulong' and 'ulong'" }, new object[] { "18446744073709551615", "18446744073709551616", "Unsupported >> operand types: 'ulong' and 'bigint'" }, - new object[] { "18446744073709551615", "12.0", "Unsupported >> operand types: 'ulong' and 'double'" }, + new object[] { "18446744073709551615", "2.0f", "Unsupported >> operand types: 'ulong' and 'float'" }, + new object[] { "18446744073709551615", "2.0", "Unsupported >> operand types: 'ulong' and 'double'" }, new object[] { "18446744073709551616", "4294967295", "Unsupported >> operand types: 'bigint' and 'uint'" }, new object[] { "18446744073709551616", "9223372036854775807", "Unsupported >> operand types: 'bigint' and 'long'" }, new object[] { "18446744073709551616", "18446744073709551615", "Unsupported >> operand types: 'bigint' and 'ulong'" }, new object[] { "18446744073709551616", "18446744073709551616", "Unsupported >> operand types: 'bigint' and 'bigint'" }, - new object[] { "18446744073709551616", "12.0", "Unsupported >> operand types: 'bigint' and 'double'" }, - new object[] { "12.0", "2", "Unsupported >> operand types: 'double' and 'int'" }, - new object[] { "12.0", "4294967295", "Unsupported >> operand types: 'double' and 'uint'" }, - new object[] { "12.0", "9223372036854775807", "Unsupported >> operand types: 'double' and 'long'" }, - new object[] { "12.0", "18446744073709551615", "Unsupported >> operand types: 'double' and 'ulong'" }, - new object[] { "12.0", "18446744073709551616", "Unsupported >> operand types: 'double' and 'bigint'" }, - new object[] { "12.0", "12.0", "Unsupported >> operand types: 'double' and 'double'" }, + new object[] { "18446744073709551616", "2.0f", "Unsupported >> operand types: 'bigint' and 'float'" }, + new object[] { "18446744073709551616", "2.0", "Unsupported >> operand types: 'bigint' and 'double'" }, + new object[] { "2.0f", "2", "Unsupported >> operand types: 'float' and 'int'" }, + new object[] { "2.0f", "4294967295", "Unsupported >> operand types: 'float' and 'uint'" }, + new object[] { "2.0f", "9223372036854775807", "Unsupported >> operand types: 'float' and 'long'" }, + new object[] { "2.0f", "18446744073709551615", "Unsupported >> operand types: 'float' and 'ulong'" }, + new object[] { "2.0f", "18446744073709551616", "Unsupported >> operand types: 'float' and 'bigint'" }, + new object[] { "2.0f", "2.0f", "Unsupported >> operand types: 'float' and 'float'" }, + new object[] { "2.0f", "2.0", "Unsupported >> operand types: 'float' and 'double'" }, + new object[] { "2.0", "2", "Unsupported >> operand types: 'double' and 'int'" }, + new object[] { "2.0", "4294967295", "Unsupported >> operand types: 'double' and 'uint'" }, + new object[] { "2.0", "9223372036854775807", "Unsupported >> operand types: 'double' and 'long'" }, + new object[] { "2.0", "18446744073709551615", "Unsupported >> operand types: 'double' and 'ulong'" }, + new object[] { "2.0", "18446744073709551616", "Unsupported >> operand types: 'double' and 'bigint'" }, + new object[] { "2.0", "2.0f", "Unsupported >> operand types: 'double' and 'float'" }, + new object[] { "2.0", "2.0", "Unsupported >> operand types: 'double' and 'double'" }, }; } diff --git a/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorDataTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorDataTests.cs index 923ff3f0..a822a305 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorDataTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorDataTests.cs @@ -304,6 +304,8 @@ private static void EnsureAllPrimitiveTypePairsAreHandled(IEnumerable /// private sealed class HashSetFormatter : IValueFormatter { + private const int MaxElementsDisplayed = 10; + /// /// Indicates whether the current can handle the specified . /// @@ -320,7 +322,7 @@ public void Format(object value, FormattedObjectGraph formattedGraph, Formatting { var list = ((HashSet<(Type, Type)>)value).ToList(); - for (int i = 0; i < list.Count; i++) + for (int i = 0; i < Math.Min(MaxElementsDisplayed, list.Count); i++) { (Type, Type) obj = list[i]; @@ -341,6 +343,11 @@ public void Format(object value, FormattedObjectGraph formattedGraph, Formatting } } + if (list.Count > MaxElementsDisplayed) + { + formattedGraph.AddFragment(" - (...)"); + } + if (list.Count == 0) { formattedGraph.AddFragment("{empty}"); diff --git a/src/Perlang.Tests.Integration/Typing/DoubleTests.cs b/src/Perlang.Tests.Integration/Typing/DoubleTests.cs index 03f49767..8684a415 100644 --- a/src/Perlang.Tests.Integration/Typing/DoubleTests.cs +++ b/src/Perlang.Tests.Integration/Typing/DoubleTests.cs @@ -10,7 +10,7 @@ public class DoubleTests public void double_variable_can_be_printed() { string source = @" - var d: double = 103.1; + var d: double = 103.1d; print(d); "; @@ -87,9 +87,9 @@ public void double_variable_throws_expected_exception_when_assigned_to_long_vari } [Fact] - public void double_variable_has_expected_type_when_initialized_to_8bit_value() + public void double_variable_has_expected_type_when_initialized_to_int_value() { - // An 8-bit integer (sbyte) should be expanded to 64-bit when the assignment target is of the 'long' type. + // A 32-bit integer should be converted to `double` when assigned to a variable of that type. string source = @" var d: double = 103; @@ -102,9 +102,9 @@ public void double_variable_has_expected_type_when_initialized_to_8bit_value() } [Fact] - public void double_variable_has_expected_type_when_assigned_8bit_value_from_another_variable() + public void double_variable_has_expected_type_when_assigned_int_value_from_another_variable() { - // An 8-bit integer (sbyte) should be expanded to 64-bit when the assignment target is of the 'long' type. + // A 32-bit integer should be converted to `double` when assigned to a variable of that type. string source = @" var d: double = 103; var e = d; @@ -117,8 +117,7 @@ public void double_variable_has_expected_type_when_assigned_8bit_value_from_anot Assert.Equal("System.Double", output); } - // The value becomes a uint in this case, but uints are not fully supported in the language yet. - [Fact(Skip = "Pending https://github.com/perlang-org/perlang/issues/70")] + [Fact] public void double_variable_has_expected_type_for_large_value() { string source = @" diff --git a/src/Perlang.Tests.Integration/Typing/FloatTests.cs b/src/Perlang.Tests.Integration/Typing/FloatTests.cs new file mode 100644 index 00000000..cee6f32c --- /dev/null +++ b/src/Perlang.Tests.Integration/Typing/FloatTests.cs @@ -0,0 +1,148 @@ +using System.Linq; +using FluentAssertions; +using Xunit; +using static Perlang.Tests.Integration.EvalHelper; + +namespace Perlang.Tests.Integration.Typing; + +public class FloatTests +{ + [Fact] + public void float_variable_can_be_printed() + { + string source = @" + var f: float = 103.1f; + + print(f); + "; + + var output = EvalReturningOutputString(source); + + Assert.Equal("103.1", output); + } + + [Fact] + public void float_variable_can_be_reassigned() + { + string source = @" + var f: float = 103; + f = 104; + + print(f); + "; + + var result = EvalReturningOutputString(source); + + Assert.Equal("104", result); + } + + [Fact] + public void float_variable_can_be_initialized_from_int_constant() + { + // 32-bit integers cannot be reliably stored in a float, since IEEE 754 single-precision floating point can only + // represent without data loss integers in the range -2^24+1 to 2^24-1. More details: + // https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limitations_on_integer_values + // + // _However_, since other well-respected languages like Java and C# allow this implicit conversion, we decided + // to allow it in Perlang alike, to reduce end-user confusion. + + string source = @" + var f: float = 2147483647; + + print(f); + "; + + var result = EvalReturningOutputString(source); + + // Note how this is less exact than the source value + Assert.Equal("2.1474836E+09", result); + } + + [Fact] + public void float_variable_throws_expected_exception_when_initialized_from_long_constant() + { + string source = @" + var f: float = 9223372036854775807; + "; + + var result = EvalWithValidationErrorCatch(source); + var exception = result.Errors.First(); + + Assert.Single(result.Errors); + Assert.Matches("Cannot assign long to float variable", exception.Message); + } + + [Fact] + public void float_variable_throws_expected_exception_when_initialized_from_bigint_constant() + { + string source = @" + var f: float = 18446744073709551616; + "; + + var result = EvalWithValidationErrorCatch(source); + var exception = result.Errors.First(); + + Assert.Single(result.Errors); + Assert.Matches("Cannot assign bigint to float variable", exception.Message); + } + + [Fact] + public void float_variable_throws_expected_exception_when_assigned_to_long_variable() + { + string source = @" + var f: float = 8589934592.1f; + var l: long = f; + "; + + var result = EvalWithValidationErrorCatch(source); + + result.Errors.Should() + .ContainSingle().Which + .Message.Should().Match("Cannot assign float to long variable"); + } + + [Fact] + public void float_variable_has_expected_type_when_initialized_to_int_value() + { + // A 32-bit integer should be converted to `float` when assigned to a variable of that type. + string source = @" + var f: float = 103; + + print(f.get_type()); + "; + + var output = EvalReturningOutputString(source); + + Assert.Equal("System.Single", output); + } + + [Fact] + public void float_variable_has_expected_type_when_assigned_int_value_from_another_variable() + { + // A 32-bit integer should be converted to `float` when assigned to a variable of that type. + string source = @" + var f: float = 103; + var g = f; + + print(g.get_type()); + "; + + var output = EvalReturningOutputString(source); + + Assert.Equal("System.Single", output); + } + + [Fact] + public void float_variable_has_expected_type_for_large_value() + { + string source = @" + var f: float = 2147483647; + + print(f.get_type()); + "; + + var output = EvalReturningOutputString(source); + + Assert.Equal("System.Single", output); + } +}