#include "v_mode_query.h" #include #include #include #include #include namespace metasequoia::local_modes { namespace { constexpr const char *kReadingDigits[] = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九"}; constexpr const char *kFinancialDigits[] = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"}; // 逐位读数字用 〇,和日期里的「二〇二四年」一样。 constexpr const char *kSpelledDigits[] = {"〇", "一", "二", "三", "四", "五", "六", "七", "八", "九"}; constexpr const char *kReadingUnits[] = {"", "十", "百", "千"}; constexpr const char *kFinancialUnits[] = {"", "拾", "佰", "仟"}; constexpr const char *kGroupUnits[] = {"", "万", "亿", "万亿"}; // 万亿以上没有通用的读法,读法和大写都只到 16 位。 constexpr std::size_t kMaxIntegerDigits = 16; // 括号嵌套的上限,防止病态输入把递归下降打爆栈。 constexpr int kMaxExpressionDepth = 64; bool all_digits(const std::string &text) { return !text.empty() && std::all_of(text.begin(), text.end(), [](unsigned char ch) { return ch >= '0' && ch <= '9'; }); } std::string strip_leading_zeros(const std::string &digits) { const std::size_t first = digits.find_first_not_of('0'); return first == std::string::npos ? std::string{} : digits.substr(first); } // 四位以内的一节,节内的零按「中间有零读一个零,末尾的零不读」处理。 std::string group_text(unsigned group, const char *const *digits, const char *const *units) { static constexpr unsigned kPowers[] = {1000, 100, 10, 1}; std::string text; bool pending_zero = false; for (int position = 0; position < 4; ++position) { const unsigned digit = group / kPowers[position] % 10; if (digit == 0) { pending_zero = !text.empty(); continue; } if (pending_zero) { text += digits[0]; pending_zero = false; } text += digits[digit]; text += units[3 - position]; } return text; } // 按四位一节从高到低拼,节与节之间:前面有数、这一节不满千或中间隔了全零的节,就补一个零。 std::string integer_text(const std::string &digits_text, bool financial) { const char *const *digits = financial ? kFinancialDigits : kReadingDigits; const char *const *units = financial ? kFinancialUnits : kReadingUnits; const std::string digits_only = strip_leading_zeros(digits_text); if (digits_only.empty()) return digits[0]; if (digits_only.size() > kMaxIntegerDigits) return {}; const std::size_t group_count = (digits_only.size() + 3) / 4; const std::size_t head = digits_only.size() - (group_count - 1) * 4; std::string text; bool need_zero = false; for (std::size_t index = 0; index < group_count; ++index) { const std::size_t start = index == 0 ? 0 : head + (index - 1) * 4; const std::size_t length = index == 0 ? head : 4; const unsigned group = static_cast(std::stoul(digits_only.substr(start, length))); if (group == 0) { need_zero = true; continue; } if (!text.empty() && (need_zero || group < 1000)) text += digits[0]; need_zero = false; text += group_text(group, digits, units); text += kGroupUnits[group_count - 1 - index]; } // 读法里打头的「一十」说成「十」:十五、十万;大写照写「壹拾」。 if (!financial && text.rfind("一十", 0) == 0) text.erase(0, std::strlen("一")); return text; } std::string spelled_digits(const std::string &digits, const char *const *table) { std::string text; for (const char digit : digits) text += table[digit - '0']; return text; } std::string with_thousands_separators(const std::string &digits) { std::string text; for (std::size_t index = 0; index < digits.size(); ++index) { if (index > 0 && (digits.size() - index) % 3 == 0) text += ','; text += digits[index]; } return text; } // 纯数字:整数,或「整数.小数」,各至少一位。 bool split_number(const std::string &input, std::string &integer, std::string &fraction) { const std::size_t dot = input.find('.'); integer = input.substr(0, dot); fraction = dot == std::string::npos ? std::string{} : input.substr(dot + 1); return all_digits(integer) && (dot == std::string::npos || all_digits(fraction)); } std::vector number_candidates(const std::string &integer, const std::string &fraction) { std::vector texts; const std::string reading = integer_text(integer, false); const std::string financial = integer_text(integer, true); if (fraction.empty()) { const std::string digits_only = strip_leading_zeros(integer); texts.push_back(reading); texts.push_back(financial); texts.push_back(chinese_money(integer, {})); texts.push_back(spelled_digits(integer, kSpelledDigits)); if (digits_only.size() >= 4) texts.push_back(with_thousands_separators(digits_only)); } else { // 带小数时多半是金额,金额排第一。 texts.push_back(chinese_money(integer, fraction)); if (!reading.empty()) texts.push_back(reading + "点" + spelled_digits(fraction, kReadingDigits)); if (!financial.empty()) texts.push_back(financial + "点" + spelled_digits(fraction, kFinancialDigits)); } return texts; } // + - 一层、* / 一层、一元正负号和括号,按通常的优先级和左结合求值。 class ExpressionParser { public: explicit ExpressionParser(const std::string &text) : text_(text) { } bool parse(double &value) { return expression(value, 0) && position_ == text_.size(); } private: bool expression(double &value, int depth) { if (!term(value, depth)) return false; while (position_ < text_.size() && (text_[position_] == '+' || text_[position_] == '-')) { const char op = text_[position_++]; double right = 0; if (!term(right, depth)) return false; value = op == '+' ? value + right : value - right; } return true; } bool term(double &value, int depth) { if (!factor(value, depth)) return false; while (position_ < text_.size() && (text_[position_] == '*' || text_[position_] == '/')) { const char op = text_[position_++]; double right = 0; if (!factor(right, depth)) return false; if (op == '/' && right == 0) return false; value = op == '*' ? value * right : value / right; } return true; } bool factor(double &value, int depth) { if (depth > kMaxExpressionDepth || position_ >= text_.size()) return false; const char ch = text_[position_]; if (ch == '+' || ch == '-') { ++position_; if (!factor(value, depth + 1)) return false; value = ch == '-' ? -value : value; return true; } if (ch == '(') { ++position_; if (!expression(value, depth + 1) || position_ >= text_.size() || text_[position_] != ')') return false; ++position_; return true; } return number(value); } // 数字至少一位,小数点两边都要有数字:1.、.5、1.2.3 都不认。 bool number(double &value) { const std::size_t start = position_; while (position_ < text_.size() && text_[position_] >= '0' && text_[position_] <= '9') ++position_; if (position_ == start) return false; if (position_ < text_.size() && text_[position_] == '.') { const std::size_t fraction_start = ++position_; while (position_ < text_.size() && text_[position_] >= '0' && text_[position_] <= '9') ++position_; if (position_ == fraction_start) return false; } value = std::strtod(text_.substr(start, position_ - start).c_str(), nullptr); return true; } const std::string &text_; std::size_t position_ = 0; }; // 至多十位小数,去掉末尾的零:0.1+0.2 给 0.3,1/3 给 0.3333333333。太大的数用有效数字表示。 std::string format_result(double value) { if (!std::isfinite(value)) return {}; char buffer[64] = {}; if (std::fabs(value) >= 1e15) { std::snprintf(buffer, sizeof(buffer), "%.15g", value); return buffer; } std::snprintf(buffer, sizeof(buffer), "%.10f", value); std::string text = buffer; text.erase(text.find_last_not_of('0') + 1); if (!text.empty() && text.back() == '.') text.pop_back(); return text == "-0" ? "0" : text; } bool has_operator(const std::string &input) { return input.find_first_of("+-*/()") != std::string::npos; } } // namespace std::string chinese_number_reading(const std::string &digits) { return all_digits(digits) ? integer_text(digits, false) : std::string{}; } std::string chinese_financial_number(const std::string &digits) { return all_digits(digits) ? integer_text(digits, true) : std::string{}; } std::string chinese_money(const std::string &integer_digits, const std::string &fraction_digits) { if (!all_digits(integer_digits) || fraction_digits.size() > 2 || (!fraction_digits.empty() && !all_digits(fraction_digits))) return {}; const std::string integer = integer_text(integer_digits, true); if (integer.empty()) return {}; const bool integer_zero = strip_leading_zeros(integer_digits).empty(); const unsigned jiao = fraction_digits.size() >= 1 ? static_cast(fraction_digits[0] - '0') : 0; const unsigned fen = fraction_digits.size() >= 2 ? static_cast(fraction_digits[1] - '0') : 0; std::string text = integer_zero ? std::string{} : integer + "元"; if (jiao == 0 && fen == 0) return integer_zero ? std::string(kFinancialDigits[0]) + "元整" : text + "整"; if (jiao > 0) text += std::string(kFinancialDigits[jiao]) + "角"; else if (!integer_zero) text += kFinancialDigits[0]; // 壹佰元零伍分 if (fen > 0) text += std::string(kFinancialDigits[fen]) + "分"; return text; } bool evaluate_v_mode_expression(const std::string &expression, std::string &result) { double value = 0; if (expression.empty() || !ExpressionParser(expression).parse(value)) return false; result = format_result(value); return !result.empty(); } std::vector query_v_mode(const std::string &input, int limit) { if (limit <= 0 || input.empty()) return {}; std::vector texts; std::string integer; std::string fraction; if (!has_operator(input) && split_number(input, integer, fraction)) { texts = number_candidates(integer, fraction); } else if (has_operator(input)) { std::string result; if (evaluate_v_mode_expression(input, result)) { texts.push_back(result); texts.push_back(input + "=" + result); } } texts.erase(std::remove(texts.begin(), texts.end(), std::string{}), texts.end()); std::vector results; for (std::string &text : texts) { if (std::find_if(results.begin(), results.end(), [&](const WordItem &item) { return item.word == text; }) != results.end()) continue; if (static_cast(results.size()) >= limit) break; results.emplace_back("", std::move(text), 0, CandidateSource::Generated); } for (std::size_t index = 0; index < results.size(); ++index) results[index].weight = static_cast(results.size() - index); return results; } } // namespace metasequoia::local_modes