diff --git a/include/exceptions.hpp b/include/exceptions.hpp index afc440a01..ed6a7fdd5 100644 --- a/include/exceptions.hpp +++ b/include/exceptions.hpp @@ -31,6 +31,8 @@ #pragma once #include +#include +#include namespace steppable::exceptions { @@ -38,19 +40,22 @@ namespace steppable::exceptions * @class ZeroDenominatorException * @brief Thrown when initializing a fraction with zero as denominator. */ - class ZeroDenominatorException : public std::exception + class ZeroDenominatorException final : public std::exception { public: - const char* what() const throw() { return "The denominator is zero, which is not allowed."; } + [[nodiscard]] const char* what() const noexcept override + { + return "The denominator is zero, which is not allowed."; + } }; /** * @class MultiLengthLetterException * @brief Thrown when initializing a Key object with two or more letters. */ - class MultiLengthLetterException : public std::exception + class MultiLengthLetterException final : public std::exception { public: - const char* what() const throw() { return "The length of letter exceeds the 1 limit."; } + [[nodiscard]] const char* what() const noexcept override { return "The length of letter exceeds the 1 limit."; } }; } // namespace steppable::exceptions diff --git a/include/fn/basicArithm.hpp b/include/fn/basicArithm.hpp index e28ba7c8d..42210be43 100644 --- a/include/fn/basicArithm.hpp +++ b/include/fn/basicArithm.hpp @@ -186,9 +186,11 @@ namespace steppable::__internals::arithmetic * @param _number The number to take root of. * @param base The base of the root. * @param decimals The decimals of the operation. + * @param steps The steps to show while taking the root. + * * @return The result of the root operation. */ - std::string root(const std::string& _number, const std::string& base, const size_t decimals = 8); + std::string root(const std::string& _number, const std::string& base, const size_t decimals = 8, const int steps = 2); /** * @brief Executes a given predicate function a specified number of times. diff --git a/include/fraction.hpp b/include/fraction.hpp index 8205339d9..fa9ec62f7 100644 --- a/include/fraction.hpp +++ b/include/fraction.hpp @@ -32,6 +32,7 @@ #include "number.hpp" +#include #include namespace steppable @@ -57,6 +58,13 @@ namespace steppable */ Fraction(const std::string& top = "1", const std::string& bottom = "1"); + /** + * @brief Initialized a fraction from a number. + * + * @param number The number to convert to a fraction. + */ + Fraction(const Number& number); + /** * @brief Initializes a fraction with no top component and bottom component specified. * By default, this fraction equals to 1. @@ -66,8 +74,17 @@ namespace steppable /** * @brief Returns the fraction as a string. * The string is formatted as "top/bottom", and it will automatically simplify the fraction. + * + * @param inLine Whether to present the fraction in a single line. + * @return The fraction as a string. + */ + std::string present(const bool inLine = true); + + /** + * @brief Returns the fraction as an array of its top and bottom components. + * @return The array of top and bottom components. */ - std::string present(); + std::array asArray() const; /** * @brief Adds two fractions together. diff --git a/include/rounding.hpp b/include/rounding.hpp index e90b7ae38..68456eec6 100644 --- a/include/rounding.hpp +++ b/include/rounding.hpp @@ -28,10 +28,11 @@ namespace steppable::__internals::numUtils /** * @brief Round off a number to the nearest integer. * - * @param[in] number The number to round. + * @param[in] _number The number to round. + * @param[in] digits The number of decimal places to round to. * @return The rounded number. */ - std::string roundOff(const std::string& number); + std::string roundOff(const std::string& _number, const size_t digits = 0); /** * @brief Move the decimal places of a number. diff --git a/include/symbols.hpp b/include/symbols.hpp index 7c87d5e0f..77fc8f642 100644 --- a/include/symbols.hpp +++ b/include/symbols.hpp @@ -33,8 +33,132 @@ #pragma once #include +#include #include +#include +#pragma once + +#include +#include + +/** + * @namespace steppable::prettyPrint + * @brief The namespace containing utilities for pretty printing. + */ +namespace steppable::prettyPrint +{ + /** + * @brief Represents a position in the console. + */ + struct Position + { + long long x = 0; + long long y = 0; + }; + + // // Not currently in use. + // enum PrintingAlignment + // { + // BASELINE = 0, + // MIDDLE = 1 + // }; + + // struct PrintingObj + // { + // std::string str; + // PrintingAlignment alignment; + // }; + + /** + * @brief Represents a console output buffer. + */ + class ConsoleOutput + { + private: + /// @brief The current position. + Position curPos; + + /// @brief The buffer object. + std::vector> buffer; + + /// @brief The height of the buffer. + long long height = 10; + + /// @brief The width of the buffer. + long long width = 10; + + public: + /** + * @brief Creates a new console output buffer. + * + * @param height The height of the buffer. + * @param width The width of the buffer. + */ + ConsoleOutput(long long height, long long width); + + /** + * @brief Writes a character to the buffer. + * + * @param c The character to write. + * @param dLine The change in line. + * @param dCol The change in column. + * @param updatePos Whether to update the current position. + */ + void write(const char c, const long long dLine, const long long dCol, bool updatePos = false); + + /** + * @brief Writes a character to the buffer. + * + * @param c The character to write. + * @param pos The position to write to. + * @param updatePos Whether to update the current position. + */ + void write(const char c, const Position& pos, bool updatePos = false); + + /** + * @brief Writes a string to the buffer. + * + * @param s The string to write. + * @param dLine The change in line. + * @param dCol The change in column. + * @param updatePos Whether to update the current position. + */ + void write(const std::string& s, const Position& pos, bool updatePos = false); + + /** + * @brief Gets the buffer as a string. + * @return The buffer as a string. + */ + [[nodiscard]] std::string asString() const; + }; + + /** + * @brief Gets the minimal width needed to print a string. + * + * @param s The string. + * @return The width of the string. + */ + size_t getStringWidth(const std::string& s); + + /** + * @brief Gets the minimal height needed to print a string. + * + * @param s The string. + * @return The height of the string. + */ + size_t getStringHeight(const std::string& s); +} // namespace steppable::prettyPrint + +/** + * @namespace steppable::__internals::symbols + * @brief The namespace containing various unicode symbols. + * + * @deprecated This namespace is deprecated and will be removed in the future, as the unicode output is not flexible + * enough. + * @warning Usage of this namespace is strongly discouraged. Use the steppable::prettyPrint namespace for basic tools, + * and implement yours in steppable::prettyPrint::printers. + */ namespace steppable::__internals::symbols { /// @brief The because symbol (3 dots in a triangle, Unicode U+2235) @@ -47,8 +171,12 @@ namespace steppable::__internals::symbols /// @brief The divide symbol (Unicode U+00F7) #define DIVIDED_BY "\u00F7" +#define SURD "\u221A" +#define COMBINE_MACRON "\u0305" + /// @brief The large dot symbol (Unicode U+25C9) #define LARGE_DOT "\u25C9" +#define ABOVE_DOT "\u02D9" // Subscripts /** @@ -68,7 +196,7 @@ namespace steppable::__internals::symbols #define SUB_MAGIC_NUMBER 8272 /// @brief A list of subscript characters. - extern const std::array& SUPERSCRIPTS; + extern const std::array& SUPERSCRIPTS; /** * @brief Create a subscript string from a normal string. @@ -117,5 +245,57 @@ namespace steppable::__internals::symbols * @param[in] normal The normal character. * @return The superscript string. */ - std::string_view makeSuperscript(char normal); + std::string makeSuperscript(char normal); + + /** + * @brief Makes a surd expression from a radicand. + * + * @param radicand The radicand. + * @return The surd expression. + */ + std::string makeSurd(const std::string& radicand); } // namespace steppable::__internals::symbols + +/** + * @namespace steppable::prettyPrint::printers + * @brief The custom-implemented printer engines for outputting expressions. + */ +namespace steppable::prettyPrint::printers +{ + /** + * @brief Pretty print a root expression. + * + * @param radicand The radicand. + * @param index The index. + * @return The pretty printed root expression. + */ + std::string ppRoot(const std::string& radicand, const std::string& index = "2"); + + /** + * @brief Pretty print a fraction. + * + * @param numerator The numerator. + * @param denominator The denominator. + * @param inLine Whether to print in a single line. + * @return The pretty printed fraction. + */ + std::string ppFraction(const std::string& numerator, const std::string& denominator, const bool inLine = false); + + /** + * @brief Pretty print a base expression, (aka, subscript). + * + * @param base The base. + * @param subscript The subscript. + * @return The pretty printed base expression. + */ + std::string ppSubscript(const std::string& base, const std::string& subscript); + + /** + * @brief Pretty print a power expression, (aka, superscript). + * + * @param base The base. + * @param exponent The exponent. + * @return The pretty printed power expression. + */ + std::string ppSuperscript(const std::string& base, const std::string& superscript); +} // namespace steppable::prettyPrint::printers diff --git a/include/util.hpp b/include/util.hpp index e7863632c..1993774b7 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -40,6 +40,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -72,6 +75,20 @@ namespace steppable::__internals::utils { +#ifndef MS_STDLIB_BUGS + #if (_MSC_VER || __MINGW32__ || __MSVCRT__) + #define MS_STDLIB_BUGS 1 + #else + #define MS_STDLIB_BUGS 0 + #endif +#endif + +#if MS_STDLIB_BUGS + #include + #include +#endif + + void initLocale(); #ifdef WINDOWS #include #include @@ -134,6 +151,7 @@ namespace steppable::__internals::utils */ Utf8CodePage() : oldCodePage(GetConsoleOutputCP()) { + initLocale(); SetConsoleOutputCP(CP_UTF8); dwModeOrig = enableVtMode(); } @@ -164,6 +182,8 @@ namespace steppable::__internals::utils */ class Utf8CodePage { + public: + Utf8CodePage() { initLocale(); } }; #endif } // namespace steppable::__internals::utils diff --git a/src/baseConvert/baseConvert.cpp b/src/baseConvert/baseConvert.cpp index 0a115566f..22152c445 100644 --- a/src/baseConvert/baseConvert.cpp +++ b/src/baseConvert/baseConvert.cpp @@ -33,6 +33,7 @@ #include "baseConvertReport.hpp" #include "fn/basicArithm.hpp" #include "output.hpp" +#include "symbols.hpp" #include "util.hpp" #include @@ -46,6 +47,22 @@ using namespace steppable::__internals::arithmetic; using namespace steppable::__internals::symbols; using namespace steppable::output; +namespace steppable::prettyPrint::printers +{ + std::string ppSubscript(const std::string& base, const std::string& subscript) + { + auto subscriptWidth = prettyPrint::getStringWidth(subscript); + auto width = prettyPrint::getStringWidth(base) + subscriptWidth + 1, + height = prettyPrint::getStringHeight(base) + 1; // +1 for the superscript + + prettyPrint::ConsoleOutput output(height, width); + prettyPrint::Position pos{ static_cast(width - subscriptWidth - 1), 1 }; + output.write(subscript, pos, false); + output.write(base, { 0, 0 }, false); + return output.asString(); + } +} // namespace steppable::prettyPrint::printers + namespace steppable::__internals::arithmetic { /** @@ -112,6 +129,7 @@ namespace steppable::__internals::arithmetic #ifndef NO_MAIN int main(const int _argc, const char* _argv[]) { + std::cout << steppable::prettyPrint::printers::ppSubscript("342", "32341"); Utf8CodePage _; ProgramArgs program(_argc, _argv); program.addPosArg('a', "Number to convert"); diff --git a/src/division/division.cpp b/src/division/division.cpp index 0a5765669..4f07c90fe 100644 --- a/src/division/division.cpp +++ b/src/division/division.cpp @@ -33,6 +33,7 @@ #include "fn/basicArithm.hpp" #include "logging.hpp" #include "output.hpp" +#include "rounding.hpp" #include "util.hpp" #include @@ -146,6 +147,9 @@ namespace steppable::__internals::arithmetic return ss.str(); } + if (compare(_divisor, "1", 0) == "2") + return roundOff(static_cast(_number), _decimals); + auto splitNumberResult = splitNumber(_number, _divisor, false, true); bool numberIsNegative = splitNumberResult.aIsNegative, divisorIsNegative = splitNumberResult.bIsNegative; auto [numberInteger, numberDecimal, divisorInteger, divisorDecimal] = splitNumberResult.splitNumberArray; @@ -215,7 +219,7 @@ namespace steppable::__internals::arithmetic auto numberDecimals = quotient.length() - numberIntegers; quotient = removeLeadingZeros(quotient); std::string finalQuotient = quotient; - if ((numberIntegers < 0) and (-numberIntegers + 1 >= decimals)) + if ((numberIntegers < 0) and (-numberIntegers >= decimals)) { if (steps != 0) warning("The result is inaccurate, as the decimals you specified is not enough to display the result."); @@ -224,26 +228,12 @@ namespace steppable::__internals::arithmetic // Scenario 1: No decimal places returned // Solution : Do nothing - if (static_cast(numberIntegers) == quotient.length() - 1 or decimals == 0) + if (static_cast(numberIntegers) == quotient.length() - 1) finalQuotient = quotient.substr(0, quotient.length() - 1); // Scenario 2: Decimal places more than requested // Solution : Round to the nearest decimal place else if (numberDecimals >= decimals and numberIntegers > 0) { - bool carry = false; - if (not quotient.empty() and quotient.back() > '4') - { - quotient = add(quotient, "10", 0, false, false); - - if (quotient.front() == '0') - quotient.erase(0, 1); - else - { - carry = true; - quotient += '0'; - } - } - quotient.pop_back(); auto beforeDecimal = quotient.substr(0, numberIntegers), afterDecimal = quotient.substr(numberIntegers, numberDecimals); if (beforeDecimal.empty()) @@ -252,9 +242,7 @@ namespace steppable::__internals::arithmetic finalQuotient = beforeDecimal + "." + afterDecimal; else finalQuotient = beforeDecimal; - - if (afterDecimal.length() > decimals) - finalQuotient = multiply(finalQuotient, "10", 0); + finalQuotient = roundOff(finalQuotient, _decimals); } // Scenario 3: Result is less than one // Solution : 1. Append "0." to the beginning @@ -262,20 +250,8 @@ namespace steppable::__internals::arithmetic // 3. Apply rounding else if (numberIntegers <= 0) { - if (not quotient.empty() and quotient.back() > '4') - { - quotient = add(quotient, "10", 0, false, false); - if (quotient.front() == '0') - quotient.erase(0, 1); - else - quotient += '0'; - } - quotient.pop_back(); auto afterDecimal = std::string(-numberIntegers, '0') + quotient; - finalQuotient = "0." + afterDecimal; - - if (afterDecimal.length() > decimals) - finalQuotient = multiply(finalQuotient, "10", 0); + finalQuotient = roundOff("0." + afterDecimal, _decimals); } // Scenario 4: Decimal places less than requested diff --git a/src/fraction.cpp b/src/fraction.cpp index 2f9f27cdc..83f63a255 100644 --- a/src/fraction.cpp +++ b/src/fraction.cpp @@ -33,13 +33,35 @@ #include "exceptions.hpp" #include "fn/basicArithm.hpp" #include "number.hpp" +#include "symbols.hpp" #include "util.hpp" #include +#ifdef WINDOWS + #undef max + #undef min +#endif + using namespace steppable::__internals::arithmetic; using namespace steppable::__internals::numUtils; +namespace steppable::prettyPrint::printers +{ + std::string ppFraction(const std::string& top, const std::string& bottom, const bool inLine) + { + // Output in single line + if (inLine) + return top + "/" + bottom; + // Output in three lines, with top and bottom aligned to center. + auto topWidth = prettyPrint::getStringWidth(top), bottomWidth = prettyPrint::getStringWidth(bottom); + auto width = std::max(topWidth, bottomWidth) + 2; + auto topSpacing = std::string((width - topWidth) / 2, ' '), + bottomSpacing = std::string((width - bottomWidth) / 2, ' '); + return topSpacing + top + '\n' + std::string(width, '-') + '\n' + bottomSpacing + bottom; + } +} // namespace steppable::prettyPrint::printers + namespace steppable { Fraction::Fraction() @@ -54,14 +76,24 @@ namespace steppable throw exceptions::ZeroDenominatorException(); this->top = top; this->bottom = bottom; + simplify(); } - std::string Fraction::present() + Fraction::Fraction(const Number& number) + { + this->top = number.present(); + this->bottom = "1"; + simplify(); + } + + std::string Fraction::present(const bool inLine) { simplify(); - return top + "/" + bottom; + return prettyPrint::printers::ppFraction(top, bottom, inLine); } + std::array Fraction::asArray() const { return { top, bottom }; } + Fraction Fraction::operator+(const Fraction& rhs) const { auto newTop = add(multiply(top, rhs.bottom, 0), multiply(rhs.top, bottom, 0), 0); @@ -203,6 +235,12 @@ namespace steppable void Fraction::simplify() { + // Make sure the fraction does not contain decimal points. + while (isDecimal(top) or isDecimal(bottom)) + { + top = multiply(top, "10", 0); + bottom = multiply(bottom, "10", 0); + } auto gcd = getGCD(top, bottom); top = divide(top, gcd, 0, 0); bottom = divide(bottom, gcd, 0, 0); diff --git a/src/number.cpp b/src/number.cpp index d45b97f5b..f234ae81d 100644 --- a/src/number.cpp +++ b/src/number.cpp @@ -33,6 +33,11 @@ #include "fn/basicArithm.hpp" #include "output.hpp" +#ifdef WINDOWS + #undef max + #undef min +#endif + namespace steppable { using namespace steppable::__internals::arithmetic; diff --git a/src/power/power.cpp b/src/power/power.cpp index 7a3aef0c4..ae84edff2 100644 --- a/src/power/power.cpp +++ b/src/power/power.cpp @@ -29,19 +29,49 @@ */ #include "argParse.hpp" #include "fn/basicArithm.hpp" +#include "fraction.hpp" #include "powerReport.hpp" +#include "symbols.hpp" #include "util.hpp" using namespace steppable::__internals::numUtils; using namespace steppable::output; using namespace steppable::__internals::arithmetic; +namespace steppable::prettyPrint::printers +{ + std::string ppSuperscript(const std::string& base, const std::string& superscript) + { + auto width = prettyPrint::getStringWidth(base) + 1, + height = prettyPrint::getStringHeight(base) + 1; // +1 for the superscript + + prettyPrint::ConsoleOutput output(height, width); + prettyPrint::Position pos{ static_cast(width - 1), 0 }; + output.write(superscript, pos, false); + output.write(base, { 0, 1 }, false); + return output.asString(); + } +} // namespace steppable::prettyPrint::printers + namespace steppable::__internals::arithmetic { std::string power(const std::string_view _number, const std::string_view& _raiseTo, const int steps) { std::string raiseTo = static_cast(_raiseTo), number = static_cast(_number); + if (isDecimal(raiseTo)) + { + // Raising to decimal power. + // Steps: + // 1. Convert the decimal to a fraction. + // 2. Raise the number to the numerator of the fraction. + // 3. Take the root of the number to the denominator of the fraction. + const auto& fraction = Fraction(raiseTo); + const auto& [top, bottom] = fraction.asArray(); + const auto &powerResult = power(_number, top, 0), rootResult = root(powerResult, bottom, 8, 0); + return reportPowerRoot(number, raiseTo, fraction, rootResult, steps); + } + // Here, we attempt to give a quick answer, instead of doing pointless iterations. if (number == "1") { diff --git a/src/power/powerReport.cpp b/src/power/powerReport.cpp index 0e98cb91d..9331e8bcb 100644 --- a/src/power/powerReport.cpp +++ b/src/power/powerReport.cpp @@ -32,15 +32,38 @@ #include "powerReport.hpp" #include "fn/basicArithm.hpp" +#include "fraction.hpp" #include "symbols.hpp" +#include "util.hpp" #include using namespace std::literals; using namespace steppable::output; +using namespace steppable::prettyPrint; using namespace steppable::__internals::symbols; using namespace steppable::__internals::arithmetic; +std::string reportPowerRoot(const std::string& _number, + const std::string& raiseTo, + const steppable::Fraction& fraction, + const std::string& result, + const int steps) +{ + auto array = fraction.asArray(); + std::stringstream ss; + + if (steps == 2) + ss << "The exponent " << raiseTo << " is a decimal. Therefore, the result is a root." << '\n'; + if (steps >= 1) + { + ss << _number << makeSuperscript(static_cast(raiseTo)); + ss << " = " << makeSuperscript(array[1]) << makeSurd(_number + makeSuperscript(array[0])) << " = "; + } + ss << result; + return ss.str(); +} + std::string reportPower(const std::string_view _number, const std::string_view& raiseTo, const size_t numberTrailingZeros, @@ -60,7 +83,7 @@ std::string reportPower(const std::string_view _number, if (steps == 2) return "Since the number is 0, the result is 0."; if (steps == 1) - return "0"s + makeSuperscript(static_cast(raiseTo)) + " = 0"; + return printers::ppSuperscript("0", static_cast(raiseTo)) + " = 0"; return "0"; } @@ -69,21 +92,23 @@ std::string reportPower(const std::string_view _number, number = multiply(number, numberOrig, 0); else number = divide("1", number, 0); - const auto& currentPower = add(i, "1", 0); + auto currentPower = add(i, "1", 0); if (steps == 2) { if (not negative) ss << BECAUSE " " << multiply(number, numberOrig, 1) << '\n'; else ss << BECAUSE " " << divide("1", number, 1) << '\n'; - ss << THEREFORE " " << numberOrig; if (negative) - ss << makeSuperscript('-'); - ss << makeSuperscript(currentPower) << " = " << number << '\n'; + currentPower = "-" + currentPower; + + ss << printers::ppSuperscript(numberOrig, currentPower) << " = " << number << '\n'; } }); finish: + number = numUtils::standardizeNumber(number); + if (negative) { if (steps == 2) diff --git a/src/power/powerReport.hpp b/src/power/powerReport.hpp index 0042ed41a..2821fcbc1 100644 --- a/src/power/powerReport.hpp +++ b/src/power/powerReport.hpp @@ -27,8 +27,27 @@ */ #pragma once +#include "fraction.hpp" + #include +/** + * @brief Reports a power operation for a root. + * + * @param _number The number to raise. + * @param raiseTo The exponent to raise to. + * @param fraction The exponent as a fraction. + * @param result The root result. + * @param steps The steps to show. + * + * @return The forrmatted power report. + */ +std::string reportPowerRoot(const std::string& _number, + const std::string& raiseTo, + const steppable::Fraction& fraction, + const std::string& result, + const int steps); + /** * @brief Reports a power operation. * @@ -43,4 +62,4 @@ std::string reportPower(const std::string_view _number, const std::string_view& raiseTo, const size_t numberTrailingZeros, const bool negative, - const int steps); \ No newline at end of file + const int steps); diff --git a/src/root/root.cpp b/src/root/root.cpp index e35a1ffb1..2dcc1b477 100644 --- a/src/root/root.cpp +++ b/src/root/root.cpp @@ -30,18 +30,70 @@ #include "argParse.hpp" #include "fn/basicArithm.hpp" +#include "fraction.hpp" +#include "rootReport.hpp" +#include "symbols.hpp" #include "util.hpp" #include +#ifdef WINDOWS + #undef max + #undef min +#endif + using namespace steppable::__internals::arithmetic; using namespace steppable::__internals::utils; +using namespace steppable::__internals::stringUtils; using namespace std::literals; +namespace steppable::prettyPrint::printers +{ + std::string ppRoot(const std::string& radicand, const std::string& index) + { + // Result looks something like: + // 3/--------- + // / 2 + // \/ radicand + auto indexWidth = prettyPrint::getStringWidth(index); + auto width = prettyPrint::getStringWidth(radicand) + indexWidth + 3, + height = prettyPrint::getStringHeight(radicand) + 1; + auto spacingWidth = std::max(height, indexWidth), firstLineSpacingWidth = spacingWidth - indexWidth; + auto lines = split(radicand, '\n'); + auto spacing = std::string(firstLineSpacingWidth, ' '), + topBar = std::string(prettyPrint::getStringWidth(radicand), '-'); + + prettyPrint::ConsoleOutput output(height, width); + prettyPrint::Position pos; + output.write(spacing + index + '/' + topBar + '\n', { 0, 0 }, false); + for (size_t i = 0; i < lines.size(); i++) + { + const auto& line = lines[i]; + pos.y++; + pos.x = spacingWidth - i - 1; + output.write('/' + line, pos, true); + } + output.write("\\/"s, { pos.x - 1, pos.y }, true); + + return output.asString(); + } +} // namespace steppable::prettyPrint::printers + namespace steppable::__internals::arithmetic { - std::string root(const std::string& _number, const std::string& base, const size_t _decimals) + std::string root(const std::string& _number, const std::string& base, const size_t _decimals, const int steps) { + if (numUtils::isDecimal(base)) + { + const auto& fraction = Fraction(base); + const auto& [top, bottom] = fraction.asArray(); + const auto &powerResult = power(_number, bottom, 0), rootResult = root(powerResult, top, _decimals, 0); + return reportRootPower(_number, base, fraction, rootResult, steps); + } + + if (compare(base, "1", 0) == "2") + return _number; + auto decimals = _decimals + 1; size_t raisedTimes = 0; std::string number = static_cast(_number); @@ -62,7 +114,7 @@ namespace steppable::__internals::arithmetic auto test = power(radicand, base, 0); if (compare(newAvg, "0", 0) == "2") - return divide(radicand, denominator, 0, _decimals); + return numUtils::standardizeNumber(divide(radicand, denominator, 0, _decimals)); if (compare(test, number, 0) == "1") x = radicand; else if (compare(test, number, 0) == "0") diff --git a/src/root/rootReport.cpp b/src/root/rootReport.cpp index 31dd71e30..fa8e49e62 100644 --- a/src/root/rootReport.cpp +++ b/src/root/rootReport.cpp @@ -22,8 +22,34 @@ #include "rootReport.hpp" +#include "symbols.hpp" + +#include #include +using namespace steppable::__internals::symbols; +using namespace steppable::prettyPrint::printers; + +std::string reportRootPower(const std::string& _number, + const std::string& base, + const steppable::Fraction& fraction, + const std::string& rootResult, + const int steps) +{ + const auto& array = fraction.asArray(); + std::stringstream ss; + + if (steps == 2) + ss << "The base " << base << " is a decimal. Therefore, we need to perform a power operation first." << '\n'; + if (steps >= 1) + { + ss << ppRoot(_number, base) << '\n'; + ss << "= " << ppRoot(ppSuperscript(_number, array[0]), array[1]) << "\n= "; + } + ss << rootResult; + return ss.str(); +} + std::string reportRoot() { // Intentionally not implemented. diff --git a/src/root/rootReport.hpp b/src/root/rootReport.hpp index d6b499603..b8766fcdb 100644 --- a/src/root/rootReport.hpp +++ b/src/root/rootReport.hpp @@ -22,6 +22,14 @@ #pragma once +#include "fraction.hpp" + #include +std::string reportRootPower(const std::string& _number, + const std::string& base, + const steppable::Fraction& fraction, + const std::string& rootResult, + const int steps); + std::string reportRoot(); diff --git a/src/rounding.cpp b/src/rounding.cpp index 25f256a69..d653f9363 100644 --- a/src/rounding.cpp +++ b/src/rounding.cpp @@ -29,19 +29,56 @@ namespace steppable::__internals::numUtils { - std::string roundOff(const std::string& _number) + std::string roundOff(const std::string& _number, const size_t digits) { auto number = _number; if (number.empty()) return "0"; if (number.find('.') == std::string::npos) return number; - auto splitNumberResult = splitNumber(number, "0", false, false, true).splitNumberArray; + // Round off the number + auto splitNumberResult = splitNumber(number, "0", true, true, false, false).splitNumberArray; auto integer = splitNumberResult[0], decimal = splitNumberResult[1]; - if (arithmetic::compare(decimal, "5", 0) != "1") - return integer + "." + decimal; - return arithmetic::add(integer, "1", 0) + "." + decimal; + // Preserve one digit after the rounded digit + decimal = decimal.substr(0, digits + 1); + // Modify the integer part if the digit is greater than 5 + if (decimal.front() >= '5' and digits == 0) + { + integer = arithmetic::add(integer, "1", 0); + return integer; + } + auto newDecimal = decimal.substr(0, digits); + std::ranges::reverse(newDecimal.begin(), newDecimal.end()); + + if (decimal.back() >= '5') + { + // Need to round up the digit + for (size_t i = 0; i < newDecimal.length(); i++) + { + if (newDecimal[i] == '.') + continue; + if (newDecimal[i] == '9') + { + newDecimal[i] = '0'; + if (i == newDecimal.length() - 1) + integer = arithmetic::add(integer, "1", 0); + } + else + { + newDecimal[i]++; + break; + } + } + } + std::ranges::reverse(newDecimal.begin(), newDecimal.end()); + decimal = newDecimal.substr(0, digits); + // decimal = newDecimal; + if (decimal.empty() and digits > 0) + return integer + "." + std::string(digits, '0'); + if (decimal.empty()) + return integer; + return integer + "." + decimal; } std::string moveDecimalPlaces(const std::string& _number, const long places) diff --git a/src/symbols.cpp b/src/symbols.cpp index 6e385b79a..676c10d1d 100644 --- a/src/symbols.cpp +++ b/src/symbols.cpp @@ -26,13 +26,106 @@ #include #include +#include + +#ifdef WINDOWS + #undef max + #undef min +#endif + +namespace steppable::prettyPrint +{ + using namespace steppable::__internals::stringUtils; + + ConsoleOutput::ConsoleOutput(long long height, long long width) : height(height), width(width) + { + buffer = std::vector>(height, std::vector(width, ' ')); + } + + void ConsoleOutput::write(const char c, const Position& pos, bool updatePos) + { + std::vector vector = buffer[pos.y]; + vector[pos.x] = c; + buffer[pos.y] = vector; + + if (updatePos) + curPos = pos; + } + + void ConsoleOutput::write(const char c, const long long dLine, const long long dCol, bool updatePos) + { + if (dLine < 0) + { + // We need to add extra lines for printing at the very top + buffer.insert(buffer.begin(), -dLine, std::vector(width, ' ')); + curPos.y = 0; + height -= dLine; + } + if (dCol < 0) + { + for (long long i = 0; i < buffer.size(); i++) + { + std::vector vector = buffer[i]; + vector.resize(vector.size() - dCol, ' '); + buffer[i] = vector; + } + + curPos.x -= dCol; + width -= dCol; + } + else + curPos.x += dCol; + write(c, curPos, updatePos); + } + + void ConsoleOutput::write(const std::string& s, const Position& pos, bool updatePos) + { + Position p = pos; + for (const auto& c : s) + { + if (c == '\n') + { + p.y++; + p.x = pos.x; + continue; + } + write(c, p, false); + p.x++; + if (updatePos) + curPos = p; + } + } + + std::string ConsoleOutput::asString() const + { + std::string res; + for (const auto& line : buffer) + { + for (const auto& c : line) + res += c; + res += '\n'; + } + return res; + } + + size_t getStringWidth(const std::string& s) + { + auto strings = split(s, '\n'); + size_t max = 0; + for (const auto& string : strings) + max = std::max(max, string.length()); + return max; + } + + size_t getStringHeight(const std::string& s) { return split(s, '\n').size(); } +} // namespace steppable::prettyPrint namespace steppable::__internals::symbols { using namespace steppable::__internals::stringUtils; - const std::array& SUPERSCRIPTS = { "\u2070", "\u00b9", "\u00b2", "\u00b3", "\u2074", - "\u2075", "\u2076", "\u2077", "\u2078", "\u2079" }; + const std::array& SUPERSCRIPTS = { "\u2070", "\u00b9", "\u00b2", "\u00b3", "\u2074", + "\u2075", "\u2076", "\u2077", "\u2078", "\u2079" }; std::string makeSubscript(const std::string& normal) { @@ -48,16 +141,29 @@ namespace steppable::__internals::symbols { std::stringstream ss; for (const char c : normal) - ss << SUPERSCRIPTS[c - '0']; + if (isdigit(c)) + ss << SUPERSCRIPTS[c - '0']; + else + ss << ABOVE_DOT; std::string string = ss.str(); return string; } - std::string_view makeSuperscript(const char normal) + std::string makeSuperscript(const char normal) { if (normal == '-') return "\u207B"; return SUPERSCRIPTS[normal - '0']; } + + std::string makeSurd(const std::string& radicand) + { + std::stringstream ss; + ss << SURD; + for (char c : radicand) + ss << std::string(1, c) << COMBINE_MACRON; + + return ss.str(); + } } // namespace steppable::__internals::symbols diff --git a/src/util.cpp b/src/util.cpp index 23e4a90fd..08ccb78af 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -128,15 +128,18 @@ namespace steppable::__internals::numUtils const std::vector aParts = stringUtils::split(a, '.'), bParts = stringUtils::split(b, '.'); auto aInteger = static_cast(aParts.front()), aDecimal = static_cast(aParts.back()), bInteger = static_cast(bParts.front()), bDecimal = static_cast(bParts.back()); - // If the numbers are integers, discard their decimal points - if (isZeroString(aDecimal) or aParts.size() == 1) - aDecimal = ""; - if (isZeroString(bDecimal) or bParts.size() == 1) - bDecimal = ""; - aInteger = simplifyZeroPolarity(aInteger); - bInteger = simplifyZeroPolarity(bInteger); - aDecimal = removeTrailingZeros(aDecimal); - bDecimal = removeTrailingZeros(bDecimal); + if (properlyFormat) + { + // If the numbers are integers, discard their decimal points + if (isZeroString(aDecimal) or aParts.size() == 1) + aDecimal = ""; + if (isZeroString(bDecimal) or bParts.size() == 1) + bDecimal = ""; + aInteger = simplifyZeroPolarity(aInteger); + bInteger = simplifyZeroPolarity(bInteger); + aDecimal = removeTrailingZeros(aDecimal); + bDecimal = removeTrailingZeros(bDecimal); + } // Pad with zeros if (padInteger) @@ -169,8 +172,7 @@ namespace steppable::__internals::numUtils if (const auto firstNonZero = std::ranges::find_if(out, [](const int num) { return num != 0; }); out.begin() != firstNonZero && out.front() == 0) { - std::replace_if( - out.begin(), firstNonZero, [](const int num) { return num == 0; }, -2); + std::replace_if(out.begin(), firstNonZero, [](const int num) { return num == 0; }, -2); } return out; @@ -237,7 +239,7 @@ namespace steppable::__internals::numUtils bool isInteger(const std::string& number) { - auto splitNumberResult = splitNumber(number, "0", false, false, false).splitNumberArray; + auto splitNumberResult = splitNumber(number, "0", false, false, true).splitNumberArray; // If the decimal part is zero, it is an integer. if (isZeroString(splitNumberResult[1])) return true; @@ -359,3 +361,22 @@ namespace steppable::__internals::stringUtils return ""; } } // namespace steppable::__internals::stringUtils + +namespace steppable::__internals::utils +{ + void initLocale() + { +#if MS_STDLIB_BUGS + constexpr char cp_utf16le[] = ".1200"; + setlocale(LC_ALL, cp_utf16le); + _setmode(_fileno(stdout), _O_WTEXT); +#else + // The correct locale name may vary by OS, e.g., "en_US.utf8". + constexpr char locale_name[] = ""; + setlocale(LC_ALL, locale_name); + std::locale::global(std::locale(locale_name)); + std::wcin.imbue(std::locale()); + std::wcout.imbue(std::locale()); +#endif + } +} // namespace steppable::__internals::utils diff --git a/tests/testFraction.cpp b/tests/testFraction.cpp index 66165dc9a..518fd2a0a 100644 --- a/tests/testFraction.cpp +++ b/tests/testFraction.cpp @@ -53,4 +53,10 @@ _.assertIsEqual((Fraction("13122", "54251") / Fraction("22451", "3423")).present _.assertIsEqual((Fraction("22451", "3423") / Fraction("13122", "54251")).present(), "1217989201/44916606"); SECTION_END() +SECTION(Fraction from Number) +_.assertIsEqual(Fraction("0.25").present(), "1/4"); +_.assertIsEqual(Fraction("0.5").present(), "1/2"); +_.assertIsEqual(Fraction(Number("0.25")).present(), "1/4"); +SECTION_END() + TEST_END() diff --git a/tests/testPower.cpp b/tests/testPower.cpp index 2eecde8b5..2a25872a5 100644 --- a/tests/testPower.cpp +++ b/tests/testPower.cpp @@ -23,6 +23,7 @@ #include "colors.hpp" #include "fn/basicArithm.hpp" #include "output.hpp" +#include "rounding.hpp" #include "testing.hpp" #include "util.hpp" @@ -53,4 +54,11 @@ const auto& result = power(number, raiseTo, 0); _.assertIsEqual(result, "0.0009765625"); SECTION_END() + +SECTION(Power with Decimal Exponents) +const std::string_view &number = "4", &raiseTo = "0.5"; +const auto& result = power(number, raiseTo, 0); + +_.assertIsEqual(numUtils::roundOff(result), "2"); +SECTION_END() TEST_END() diff --git a/tests/testRoot.cpp b/tests/testRoot.cpp index b43ac876e..67e434ef0 100644 --- a/tests/testRoot.cpp +++ b/tests/testRoot.cpp @@ -32,6 +32,11 @@ TEST_START() SECTION(Root with a sqare number) using namespace steppable::__internals::arithmetic; -_.assertIsEqual(root("4", "2"), "2.00000000"); +_.assertIsEqual(root("4", "2", 0, 0), "2"); +SECTION_END() + +SECTION(Root with a decimal index) +using namespace steppable::__internals::arithmetic; +_.assertIsEqual(root("4", "0.5", 0, 0), "16"); SECTION_END() TEST_END() diff --git a/tests/testUtil.cpp b/tests/testUtil.cpp index 9708bd428..b3fedf38c 100644 --- a/tests/testUtil.cpp +++ b/tests/testUtil.cpp @@ -29,6 +29,7 @@ #include #include +#include TEST_START() @@ -144,4 +145,14 @@ const auto& out = makeWider(in); _.assertIsEqual(out, "a b c d e f g"); SECTION_END() +SECTION(Test Rounding) +const std::string& number1 = "1.565"; +_.assertIsEqual(roundOff(number1, 0), "2"); +_.assertIsEqual(roundOff(number1, 1), "1.6"); + +const std::string& number2 = "1.9"; +_.assertIsEqual(roundOff(number2, 0), "2"); +_.assertIsEqual(roundOff(number2, 1), "2.0"); +SECTION_END() + TEST_END() diff --git a/wiki b/wiki index 4e4ee7a2d..0425e6a11 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 4e4ee7a2d59b4696a176ed423d928ded1f80ac6e +Subproject commit 0425e6a11408ad96476c522469e664faa8203ec1