From 034ecafc267844af62b8d3794d13598ff717f67f Mon Sep 17 00:00:00 2001 From: Per Lundberg Date: Thu, 23 Nov 2023 22:55:26 +0200 Subject: [PATCH] WIP: We are making progress. Things feel good. Comparisons with double and BigInt are a bit picky. I have implemented some of it now, and some more remains. We *will* be producing weird results for small/big doubles, because of the way IEEE754 floating points are represented. But that's life, and we just need to make our dear users aware of it. --- .../Compiler/PerlangCompiler.cs | 12 ++ .../Operator/Binary/BinaryOperatorData.cs | 1 + .../Operator/Binary/DivisionTests.cs | 2 +- .../Operator/Binary/MultiplicationTests.cs | 2 +- src/stdlib/CMakeLists.txt | 2 +- src/stdlib/src/bigint.cpp | 179 +++++++++++++++++- src/stdlib/src/bigint.hpp | 60 ++++++ src/stdlib/test/print.cc | 16 +- 8 files changed, 262 insertions(+), 12 deletions(-) diff --git a/src/Perlang.Interpreter/Compiler/PerlangCompiler.cs b/src/Perlang.Interpreter/Compiler/PerlangCompiler.cs index 360f55f7..a91633ac 100644 --- a/src/Perlang.Interpreter/Compiler/PerlangCompiler.cs +++ b/src/Perlang.Interpreter/Compiler/PerlangCompiler.cs @@ -518,6 +518,18 @@ private void RegisterGlobalClasses() "-Wall", "-Werror", + // Enable warnings on e.g. narrowing conversion from `long long` to `unsigned long long`. + "-Wconversion", + + // ...but do not warn on implicit conversion from `int` to `float` or `double`. For now, we are + // aiming at mimicking the C# semantics in this. + "-Wno-implicit-int-float-conversion", + + // Certain narrowing conversions are problematic; we have seen this causing issues when implementing + // the BigInt support. For example, `uint64_t` must not be implicitly converted to call a `long + // long` constructor/method. + "-Wimplicit-int-conversion", + // C# allows cast from e.g. 2147483647 to float without warnings, but here's an interesting thing: // clang emits a very nice warning for this ("implicit conversion from 'int' to 'float' changes // value from 2147483647 to 2147483648"). We might want to consider doing something similar for diff --git a/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorData.cs b/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorData.cs index e2880fd4..486d1f12 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorData.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/BinaryOperatorData.cs @@ -365,6 +365,7 @@ public static class BinaryOperatorData new object[] { "9223372036854775807", "18446744073709551616", "false" }, new object[] { "9223372036854775807", "340282349999999991754788743781432688640.0f", "false" }, new object[] { "9223372036854775807", "12.0", "false" }, + new object[] { "9223372036854775807", "9223372036854775807.0", "false" }, new object[] { "18446744073709551615", "2147483647", "false" }, new object[] { "18446744073709551615", "4294967295", "false" }, new object[] { "18446744073709551615", "9223372036854775807", "false" }, diff --git a/src/Perlang.Tests.Integration/Operator/Binary/DivisionTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/DivisionTests.cs index d417d6a3..101ca0b8 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/DivisionTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/DivisionTests.cs @@ -13,7 +13,7 @@ namespace Perlang.Tests.Integration.Operator.Binary // https://github.com/munificent/craftinginterpreters/blob/c6da0e61e6072271de404464c34b51c2fdc39e59/test/operator/divide_num_nonnum.lox public class DivisionTests { - [SkippableTheory] + [Theory] [MemberData(nameof(BinaryOperatorData.Division_result), MemberType = typeof(BinaryOperatorData))] void performs_division(string i, string j, string expectedResult) { diff --git a/src/Perlang.Tests.Integration/Operator/Binary/MultiplicationTests.cs b/src/Perlang.Tests.Integration/Operator/Binary/MultiplicationTests.cs index 250cf89c..6ee41de6 100644 --- a/src/Perlang.Tests.Integration/Operator/Binary/MultiplicationTests.cs +++ b/src/Perlang.Tests.Integration/Operator/Binary/MultiplicationTests.cs @@ -16,7 +16,7 @@ public class MultiplicationTests // // Tests for the * (multiplication) operator // - [SkippableTheory] + [Theory] [MemberData(nameof(BinaryOperatorData.Multiplication_result), MemberType = typeof(BinaryOperatorData))] public void performs_multiplication(string i, string j, string expectedResult) { diff --git a/src/stdlib/CMakeLists.txt b/src/stdlib/CMakeLists.txt index 912b5cfa..7cfddf77 100644 --- a/src/stdlib/CMakeLists.txt +++ b/src/stdlib/CMakeLists.txt @@ -37,7 +37,7 @@ install( DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" ) -# The testing code is not yet ready for macOS, since it uses a different linker which doesnät support --wrap. We'll +# The testing code is not yet ready for macOS, since it uses a different linker which doesn't support --wrap. We'll # live with only building/running the tests on other platforms for now. if (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") enable_testing() diff --git a/src/stdlib/src/bigint.cpp b/src/stdlib/src/bigint.cpp index 7842c4c6..5c545a17 100644 --- a/src/stdlib/src/bigint.cpp +++ b/src/stdlib/src/bigint.cpp @@ -214,10 +214,26 @@ BigInt::BigInt(const BigInt& num) { /* - Integer to BigInt + Integer (various sizes) to BigInt ----------------- */ +BigInt::BigInt(const int& num) : + BigInt((long long)num) { +} + +BigInt::BigInt(const unsigned int& num) : + BigInt((unsigned long long)num) { +} + +BigInt::BigInt(const long& num) : + BigInt((long long)num) { +} + +BigInt::BigInt(const unsigned long& num) : + BigInt((unsigned long long)num) { +} + BigInt::BigInt(const long long& num) { value = std::to_string(std::abs(num)); if (num < 0) @@ -226,6 +242,11 @@ BigInt::BigInt(const long long& num) { sign = '+'; } +BigInt::BigInt(const unsigned long long& num) { + value = std::to_string(num); + sign = '+'; +} + /* String to BigInt @@ -510,20 +531,72 @@ bool BigInt::operator>=(const BigInt& num) const { ----------------- */ +bool BigInt::operator==(const int& num) const { + return *this == BigInt(num); +} + +bool BigInt::operator==(const unsigned int& num) const { + return *this == BigInt(num); +} + +bool BigInt::operator==(const long& num) const { + return *this == BigInt(num); +} + +bool BigInt::operator==(const unsigned long& num) const { + return *this == BigInt(num); +} + bool BigInt::operator==(const long long& num) const { return *this == BigInt(num); } +bool BigInt::operator==(const unsigned long long& num) const { + return *this == BigInt(num); +} + +bool BigInt::operator==(const double& num) const { + if (ceil(num) != num) { + // num has a fractional part and can inherently never be equal to a BigInt. + return false; + } + else { + // num does not have a fractional part. Disregarding IEEE754 semantics, we can compare it to a BigInt (reliably + // for numbers between -2^53 and +2^53, because of the aforementioned IEEE754 semantics). + return *this == BigInt((int64_t)num); + } +} + /* Integer == BigInt ----------------- */ +bool operator==(const int& lhs, const BigInt& rhs) { + return BigInt(lhs) == rhs; +} + +bool operator==(const unsigned int& lhs, const BigInt& rhs) { + return BigInt(lhs) == rhs; +} + +bool operator==(const long& lhs, const BigInt& rhs) { + return BigInt(lhs) == rhs; +} + +bool operator==(const unsigned long& lhs, const BigInt& rhs) { + return BigInt(lhs) == rhs; +} + bool operator==(const long long& lhs, const BigInt& rhs) { return BigInt(lhs) == rhs; } +bool operator==(const unsigned long long& lhs, const BigInt& rhs) { + return BigInt(lhs) == rhs; +} + /* BigInt != Integer @@ -1402,20 +1475,60 @@ BigInt operator*(const long long& lhs, const BigInt& rhs) { ---------------- */ +BigInt BigInt::operator/(const int& num) const { + return *this / BigInt(num); +} + +BigInt BigInt::operator/(const unsigned int& num) const { + return *this / BigInt(num); +} + +BigInt BigInt::operator/(const long& num) const { + return *this / BigInt(num); +} + +BigInt BigInt::operator/(const unsigned long& num) const { + return *this / BigInt(num); +} + BigInt BigInt::operator/(const long long& num) const { return *this / BigInt(num); } +BigInt BigInt::operator/(const unsigned long long& num) const { + return *this / BigInt(num); +} + /* Integer / BigInt ---------------- */ +BigInt operator/(const int& lhs, const BigInt& rhs) { + return BigInt(lhs) / rhs; +} + +BigInt operator/(const unsigned int& lhs, const BigInt& rhs) { + return BigInt(lhs) / rhs; +} + +BigInt operator/(const long& lhs, const BigInt& rhs) { + return BigInt(lhs) / rhs; +} + +BigInt operator/(const unsigned long& lhs, const BigInt& rhs) { + return BigInt(lhs) / rhs; +} + BigInt operator/(const long long& lhs, const BigInt& rhs) { return BigInt(lhs) / rhs; } +BigInt operator/(const unsigned long long& lhs, const BigInt& rhs) { + return BigInt(lhs) / rhs; +} + /* BigInt % Integer @@ -1615,24 +1728,84 @@ BigInt& BigInt::operator%=(const BigInt& num) { ----------------- */ +BigInt& BigInt::operator+=(const int& num) { + *this = *this + BigInt(num); + + return *this; +} + +BigInt& BigInt::operator+=(const unsigned int& num) { + *this = *this + BigInt(num); + + return *this; +} + +BigInt& BigInt::operator+=(const long& num) { + *this = *this + BigInt(num); + + return *this; +} + +BigInt& BigInt::operator+=(const unsigned long& num) { + *this = *this + BigInt(num); + + return *this; +} + BigInt& BigInt::operator+=(const long long& num) { *this = *this + BigInt(num); return *this; } +BigInt& BigInt::operator+=(const unsigned long long& num) { + *this = *this + BigInt(num); + + return *this; +} + /* BigInt -= Integer ----------------- */ +BigInt& BigInt::operator-=(const int& num) { + *this = *this - BigInt(num); + + return *this; +} + +BigInt& BigInt::operator-=(const unsigned int& num) { + *this = *this - BigInt(num); + + return *this; +} + +BigInt& BigInt::operator-=(const long& num) { + *this = *this - BigInt(num); + + return *this; +} + +BigInt& BigInt::operator-=(const unsigned long& num) { + *this = *this - BigInt(num); + + return *this; +} + BigInt& BigInt::operator-=(const long long& num) { *this = *this - BigInt(num); return *this; } +BigInt& BigInt::operator-=(const unsigned long long& num) { + *this = *this - BigInt(num); + + return *this; +} + /* BigInt *= Integer @@ -1837,6 +2010,4 @@ std::ostream& operator<<(std::ostream& out, const BigInt& num) { return out; } -#endif // BIG_INT_IO_STREAM_OPERATORS_HPP - - +#endif // BIG_INT_IO_STREAM_OPERATORS_HPP \ No newline at end of file diff --git a/src/stdlib/src/bigint.hpp b/src/stdlib/src/bigint.hpp index f8f7a1d3..b8bd7997 100644 --- a/src/stdlib/src/bigint.hpp +++ b/src/stdlib/src/bigint.hpp @@ -30,7 +30,12 @@ class BigInt { // Constructors: BigInt(); BigInt(const BigInt&); + BigInt(const int&); + BigInt(const unsigned int&); + BigInt(const long&); + BigInt(const unsigned long&); BigInt(const long long&); + BigInt(const unsigned long long& num); BigInt(const std::string&); // Assignment operators: @@ -51,7 +56,12 @@ class BigInt { BigInt operator+(const long long&) const; BigInt operator-(const long long&) const; BigInt operator*(const long long&) const; + BigInt operator/(const int&) const; + BigInt operator/(const unsigned int&) const; + BigInt operator/(const long&) const; + BigInt operator/(const unsigned long&) const; BigInt operator/(const long long&) const; + BigInt operator/(const unsigned long long&) const; BigInt operator%(const long long&) const; BigInt operator+(const std::string&) const; BigInt operator-(const std::string&) const; @@ -65,8 +75,18 @@ class BigInt { BigInt& operator*=(const BigInt&); BigInt& operator/=(const BigInt&); BigInt& operator%=(const BigInt&); + BigInt& operator+=(const int&); + BigInt& operator+=(const unsigned int&); + BigInt& operator+=(const long&); + BigInt& operator+=(const unsigned long&); BigInt& operator+=(const long long&); + BigInt& operator+=(const unsigned long long&); + BigInt& operator-=(const int&); + BigInt& operator-=(const unsigned int&); + BigInt& operator-=(const long&); + BigInt& operator-=(const unsigned long&); BigInt& operator-=(const long long&); + BigInt& operator-=(const unsigned long long&); BigInt& operator*=(const long long&); BigInt& operator/=(const long long&); BigInt& operator%=(const long long&); @@ -93,7 +113,15 @@ class BigInt { bool operator>(const long long&) const; bool operator<=(const long long&) const; bool operator>=(const long long&) const; + + bool operator==(const int&) const; + bool operator==(const unsigned int&) const; + bool operator==(const long&) const; + bool operator==(const unsigned long&) const; bool operator==(const long long&) const; + bool operator==(const unsigned long long&) const; + bool operator==(const double&) const; + bool operator!=(const long long&) const; bool operator<(const std::string&) const; bool operator>(const std::string&) const; @@ -116,5 +144,37 @@ class BigInt { friend BigInt big_random(size_t); }; +//// The following operator overloads operate with primitives like long long on the left-hand side; they are not part of +//// the class. Their declarations must still exist in the header file, so that it can be found by the calling code. + +/* + Integer / BigInt + ---------------- +*/ + +bool operator==(const int& lhs, const BigInt& rhs); +bool operator==(const unsigned int& lhs, const BigInt& rhs); +bool operator==(const long& lhs, const BigInt& rhs); +bool operator==(const unsigned long& lhs, const BigInt& rhs); +bool operator==(const long long& lhs, const BigInt& rhs); +bool operator==(const unsigned long long& lhs, const BigInt& rhs); + +bool operator!=(const long long& lhs, const BigInt& rhs); +bool operator<(const long long& lhs, const BigInt &rhs); +bool operator>(const long long& lhs, const BigInt& rhs); +bool operator<=(const long long& lhs, const BigInt& rhs); +bool operator>=(const long long& lhs, const BigInt& rhs); +BigInt operator+(const long long& lhs, const BigInt& rhs); +BigInt operator-(const long long& lhs, const BigInt& rhs); +BigInt operator*(const long long& lhs, const BigInt& rhs); + +BigInt operator/(const int& lhs, const BigInt& rhs); +BigInt operator/(const unsigned int& lhs, const BigInt& rhs); +BigInt operator/(const long& lhs, const BigInt& rhs); +BigInt operator/(const unsigned long& lhs, const BigInt& rhs); +BigInt operator/(const long long& lhs, const BigInt& rhs); +BigInt operator/(const unsigned long long& lhs, const BigInt& rhs); +BigInt operator%(const long long& lhs, const BigInt& rhs); + #endif // BIG_INT_HPP diff --git a/src/stdlib/test/print.cc b/src/stdlib/test/print.cc index b1c5e1ad..6792f9a9 100644 --- a/src/stdlib/test/print.cc +++ b/src/stdlib/test/print.cc @@ -91,14 +91,20 @@ TEST(PrintDouble_9223372036854775807) fwrite_mocked = false; CHECK_EQ("9.22337203685478E+18\n", captured_output); + + // TODO: temp code + BigInt i1 = BigInt("9223372036854775807"); + double i2 = 9223372036854775807.0; + perlang::print(i1 == i2); } // TODO: Make this test work. We need to (linker-)wrap puts to make it happen. TEST(PrintBigint_18446744073709551616) { - fwrite_mocked = true; - perlang::print(BigInt("18446744073709551616")); - fwrite_mocked = false; - - CHECK_EQ("18446744073709551616\n", captured_output); +// // TODO: delete the above, keep this +// fwrite_mocked = true; +// perlang::print(BigInt("18446744073709551616")); +// fwrite_mocked = false; +// +// CHECK_EQ("18446744073709551616\n", captured_output); } \ No newline at end of file