// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // IntIO.cpp // 多倍長整数の入出力に関する操作を実装します #include #include #include #include #include #include #include #include #include #include #include #include namespace calx { std::string IntIOUtils::fromRawWords(const std::vector& words, int sign) { if (words.empty()) { return "0"; } // 先頭の0を削除した新しいvectorを作成 std::vector trimmed_words; auto it = words.rbegin(); // 最初の非ゼロ値を見つける while (it != words.rend() && *it == 0) { ++it; } // 残りの有効な値をコピー while (it != words.rend()) { trimmed_words.push_back(*it); ++it; } if (trimmed_words.empty()) { return "0"; } std::string result; // 16進数表現を生成 for (auto it = trimmed_words.begin(); it != trimmed_words.end(); ++it) { std::stringstream ss; ss << std::hex << std::setfill('0'); // 最初の値以外は16桁になるように0埋め if (it != trimmed_words.begin()) { ss << std::setw(16); } ss << *it; result += ss.str(); } // 先頭の0を削除 result.erase(0, result.find_first_not_of('0')); if (result.empty()) { return "0"; } // 符号を追加 if (sign < 0) { result = "-" + result; } return result; } // ================================================================ // 10 進変換: 小サイズ naive + 大サイズ分割統治法 // ================================================================ // // 小サイズ (< DC_TOSTRING_THRESHOLD limbs): // divmod_1(10^18) の繰り返しで 18 桁ずつ抽出。O(n²) だが // 定数が小さく、DC のオーバーヘッド (多倍長 divmod, pow10 合成) を回避。 // // 大サイズ: // 分割統治法 O(M(N) log N)。事前に出力バッファを確保し、 // 再帰の各レベルで直接書き込む (文字列結合を回避)。 static constexpr double LOG10_2 = 0.30102999566398120; static constexpr uint64_t POW10_18 = 1000000000000000000ULL; // 10^18 static constexpr size_t DIGITS_PER_CHUNK = 18; // DC 閾値: これ未満は naive (divmod_1 繰り返し) static constexpr size_t DC_TOSTRING_THRESHOLD = 30; // ビット数から10進桁数を推定 static size_t estimateDecimalDigits(size_t bitLength) { return static_cast(static_cast(bitLength) * LOG10_2) + 1; } // 10^k のキャッシュ (二乗法で構築) // cache[i] = 10^(2^i) — i=0: 10, i=1: 100, i=2: 10000, ... static std::vector s_pow10_cache; // 10^n を効率的に計算 (キャッシュ付き) static Int pow10_cached(size_t n) { if (n == 0) return Int(1); // キャッシュを構築: s_pow10_cache[i] = 10^(2^i) while (s_pow10_cache.empty() || (size_t(1) << (s_pow10_cache.size() - 1)) < n) { if (s_pow10_cache.empty()) { s_pow10_cache.push_back(Int(10)); // 10^1 } else { const Int& prev = s_pow10_cache.back(); s_pow10_cache.push_back(prev * prev); // 10^(2^k) = (10^(2^(k-1)))^2 } } // n をバイナリ分解して積を計算 Int result(1); size_t remaining = n; for (size_t i = 0; remaining > 0 && i < s_pow10_cache.size(); i++) { if (remaining & (size_t(1) << i)) { result = result * s_pow10_cache[i]; remaining &= ~(size_t(1) << i); } } return result; } // uint64_t → 18桁文字列をバッファに書き込み static void u64_to_18digits(uint64_t v, char* out) { // 後ろから 18 桁を生成 for (int i = 17; i >= 0; --i) { out[i] = '0' + static_cast(v % 10); v /= 10; } } // uint64_t → 可変長文字列をバッファに書き込み、桁数を返す static size_t u64_to_digits(uint64_t v, char* out) { if (v == 0) { out[0] = '0'; return 1; } char tmp[20]; size_t len = 0; while (v > 0) { tmp[len++] = '0' + static_cast(v % 10); v /= 10; } for (size_t i = 0; i < len; i++) out[i] = tmp[len - 1 - i]; return len; } // naive 変換: divmod_1(10^18) の繰り返しで 18 桁チャンクに分解 // 小サイズ向け (< DC_TOSTRING_THRESHOLD limbs) static std::string toDecimalNaive(const uint64_t* data, size_t n) { // 上位のゼロを除去 while (n > 0 && data[n - 1] == 0) --n; if (n == 0) return "0"; // 1 limb 高速パス: snprintf で直接変換 if (n == 1) { char buf[21]; int len = std::snprintf(buf, sizeof(buf), "%" PRIu64, data[0]); return std::string(buf, static_cast(len)); } // 作業コピー (divmod_1 は in-place で商を書く) // 小サイズはスタックバッファで heap 確保を回避 constexpr size_t STACK_LIMIT = 64; uint64_t stack_buf[STACK_LIMIT]; uint64_t* work_ptr; std::vector work_vec; if (n <= STACK_LIMIT) { std::memcpy(stack_buf, data, n * sizeof(uint64_t)); work_ptr = stack_buf; } else { work_vec.assign(data, data + n); work_ptr = work_vec.data(); } // 18桁チャンクを逆順に収集 // 最大チャンク数: ceil(n * 64 * log10(2) / 18) + 1 std::vector chunks; chunks.reserve(n * 4); // 十分な余裕 while (n > 0) { uint64_t rem = mpn::divmod_1(work_ptr, work_ptr, n, POW10_18); chunks.push_back(rem); while (n > 0 && work_ptr[n - 1] == 0) --n; } // 先頭チャンクは可変長、残りは 18 桁固定 size_t totalLen = 20 + (chunks.size() - 1) * DIGITS_PER_CHUNK; std::string result; result.resize(totalLen); char* p = result.data(); // 最上位チャンク (ゼロパディングなし) size_t headLen = u64_to_digits(chunks.back(), p); p += headLen; // 残りのチャンク (18桁ゼロパディング) for (size_t i = chunks.size() - 1; i-- > 0; ) { u64_to_18digits(chunks[i], p); p += DIGITS_PER_CHUNK; } result.resize(static_cast(p - result.data())); return result; } // DC 再帰本体: buf[pos..pos+padDigits-1] に書き込む // padDigits == 0 のとき (最上位): 先頭ゼロなし、書き込み後の位置を返す static void decimalSplitRec(const Int& x, char* buf, size_t pos, size_t padDigits) { // 基底: naive 変換に委譲 size_t n = x.size(); if (n < DC_TOSTRING_THRESHOLD) { if (x.isZero()) { // padDigits 分のゼロを書く std::memset(buf + pos, '0', padDigits); return; } // naive で文字列化してバッファにコピー std::string s = toDecimalNaive(x.data(), n); if (padDigits > s.size()) { size_t pad = padDigits - s.size(); std::memset(buf + pos, '0', pad); std::memcpy(buf + pos + pad, s.data(), s.size()); } else { std::memcpy(buf + pos, s.data(), s.size()); } return; } // padDigits の半分で分割 size_t half = padDigits / 2; size_t upperPad = padDigits - half; // 上位は残り (half + upperPad == padDigits を保証) // 10^half をキャッシュ付きで計算 Int divisor = pow10_cached(half); Int R; Int Q = IntOps::divmod(x, divisor, R); // 上位半分: pos から upperPad 桁 decimalSplitRec(Q, buf, pos, upperPad); // 下位半分: pos + upperPad から half 桁 decimalSplitRec(R, buf, pos + upperPad, half); } // 10 進文字列変換 static std::string toDecimalString(const Int& value) { if (value.isZero()) return "0"; bool isNeg = value.isNegative(); size_t n = value.size(); // 小サイズ: naive (divmod_1 繰り返し) — Int コピー不要、data() 直接参照 if (n < DC_TOSTRING_THRESHOLD) { std::string result = toDecimalNaive(value.data(), n); if (isNeg) result.insert(0, 1, '-'); return result; } // 大サイズ: absVal が必要 (DC は Int を分割するため) Int absVal = isNeg ? -value : value; n = absVal.size(); // 大サイズ: DC (事前確保バッファ) // estimateDecimalDigits は ceil(bits * log10(2)) + 1 で 1 桁余分になることがある。 // バッファは余裕を持って確保し、DC 変換後に naive 変換で正確な桁数を検証する。 size_t estDigits = estimateDecimalDigits(absVal.bitLength()); size_t bufSize = estDigits + 4; std::string buf(bufSize, '0'); // DC 再帰でバッファに書き込み decimalSplitRec(absVal, buf.data(), 0, estDigits); // 先頭のゼロを除去 size_t start = 0; while (start < buf.size() - 1 && buf[start] == '0') ++start; // 末尾: estDigits 分のバッファのうち、先頭ゼロを除いた部分が結果。 // ただし estDigits が 1-2 桁多い場合、末尾に余分なゼロが付く。 // 正確な桁数: floor(log10(value)) + 1 = bitLength * log10(2) の整数部 + 1 // naive で先頭数桁を計算して正確な桁数を判定するのは高コストなので、 // 代わりに: value >= 10^(estDigits-1) なら estDigits 桁、そうでなければ estDigits-1 桁 size_t actualDigits = estDigits; if (estDigits > 1) { Int threshold = pow10_cached(estDigits - 1); if (absVal < threshold) { actualDigits = estDigits - 1; } } // start からバッファの actualDigits 桁分を切り出す std::string result = buf.substr(start, actualDigits); if (isNeg) { result.insert(0, 1, '-'); } return result; } // ================================================================ // 汎用 toString (任意基数) // ================================================================ // base == 10 以外は従来の逐次 divmod (小さい数向け) static std::string toStringNaive(const Int& value, int base) { static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; Int x = value; bool isNeg = x.isNegative(); if (isNeg) x = -x; std::string result; Int baseInt(base); while (!x.isZero()) { Int remainder; x = IntOps::divmod(x, baseInt, remainder); result.push_back(digits[remainder.toInt()]); } std::reverse(result.begin(), result.end()); if (isNeg) result.insert(0, 1, '-'); return result; } std::string IntIOUtils::toString(const Int& value, int base) { // 特殊状態を処理 std::string specialResult = IntSpecialStates::handleToString(value); if (!specialResult.empty()) { return specialResult; } if (base < 2 || base > 36) { throw std::invalid_argument("Base must be between 2 and 36"); } if (value.m_sign == 0) { return "0"; } // base == 10: 分割統治法 (O(M(N) * log N)) if (base == 10) { return toDecimalString(value); } // 他の基数: 従来の逐次変換 return toStringNaive(value, base); } // ================================================================ // fromString 内部ヘルパー // ================================================================ // 1文字を数値に変換 (無効文字は -1、区切り文字は -2) static int charToDigit(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'z') return c - 'a' + 10; if (c >= 'A' && c <= 'Z') return c - 'A' + 10; if (c == '_' || c == '\'') return -2; // 区切り文字 return -1; // 無効文字 } // 数字部分をバリデーションし、区切り文字を除去した clean な文字列を返す static std::string cleanDigits(std::string_view str, size_t start, int base) { std::string digits; digits.reserve(str.size() - start); for (size_t i = start; i < str.size(); ++i) { int d = charToDigit(str[i]); if (d == -2) continue; // 区切り文字をスキップ if (d < 0) { throw std::invalid_argument( "Invalid character in string: " + std::string(1, str[i])); } if (d >= base) { throw std::invalid_argument("Digit out of range for specified base"); } digits.push_back(str[i]); } return digits; } // 2のべき乗基数のビット数を返す (非2べきなら 0) static int bitsForPow2Base(int base) { switch (base) { case 2: return 1; case 4: return 2; case 8: return 3; case 16: return 4; case 32: return 5; default: return 0; } } // ---------------------------------------------------------------- // (1) ビットパッキング: 2のべき乗基数 (2, 4, 8, 16, 32) // 乗算不要、O(n) // ---------------------------------------------------------------- static Int fromStringPow2(const std::string& digits, int bitsPerDigit) { if (digits.empty()) return Int::Zero(); // 総ビット数からワード数を計算 size_t totalBits = digits.size() * static_cast(bitsPerDigit); size_t numWords = (totalBits + 63) / 64; // ワード配列を確保 std::vector words(numWords, 0); // 末尾 (最下位桁) から処理してリトルエンディアンで詰める size_t bitPos = 0; for (size_t i = digits.size(); i-- > 0; ) { int d = charToDigit(digits[i]); size_t wordIdx = bitPos / 64; size_t bitIdx = bitPos % 64; words[wordIdx] |= static_cast(d) << bitIdx; // bitsPerDigit がワード境界をまたぐ場合 if (bitIdx + bitsPerDigit > 64) { words[wordIdx + 1] |= static_cast(d) >> (64 - bitIdx); } bitPos += bitsPerDigit; } // 上位のゼロワードを除去 while (!words.empty() && words.back() == 0) { words.pop_back(); } if (words.empty()) return Int::Zero(); return Int::fromRawWords(words, 1); } // ---------------------------------------------------------------- // (2) Horner 法: 一般基数の小桁数 // result = result * base + digit を逐次計算 // ---------------------------------------------------------------- // チャンク Horner 法: 最大 chunk_size 桁ずつまとめて処理 // base=10 なら 18桁ずつ (10^18 < 2^64) で多倍長×1limb乗算に帰着 static Int fromStringHorner(const std::string& digits, int base) { if (digits.empty()) return Int::Zero(); // base^k が uint64_t に収まる最大の k を計算 int chunk_size = 1; uint64_t chunk_base = static_cast(base); { uint64_t limit = UINT64_MAX / base; uint64_t b = chunk_base; while (b <= limit) { b *= base; chunk_size++; } chunk_base = b / base; // base^(chunk_size-1) chunk_size--; // chunk_base = base^chunk_size chunk_base = 1; for (int i = 0; i < chunk_size; i++) chunk_base *= base; } Int result = Int::Zero(); size_t pos = 0; size_t len = digits.size(); // 最初の端数チャンク (len % chunk_size 桁) size_t first_chunk = len % chunk_size; if (first_chunk == 0 && len > 0) first_chunk = chunk_size; { uint64_t v = 0; for (size_t i = 0; i < first_chunk; i++) { v = v * base + charToDigit(digits[pos++]); } result = Int(v); } // 残りを chunk_size 桁ずつ処理 // multiplyWord + addWord で一時 Int 生成を完全排除 while (pos < len) { uint64_t v = 0; for (int i = 0; i < chunk_size; i++) { v = v * base + charToDigit(digits[pos++]); } IntOps::multiplyWord(result, chunk_base); IntOps::addWord(result, v); } return result; } // ---------------------------------------------------------------- // (3) 分割統治法: 一般基数の大桁数 // 文字列を半分に分割し、upper * base^(下位桁数) + lower // 計算量: O(M(n) * log n) // ---------------------------------------------------------------- // Horner と分割統治の閾値 (10進桁数ベース) // チャンクHorner により basecase が高速化したため閾値を引き下げ static size_t FROMSTRING_DC_THRESHOLD = 400; static Int fromStringDC(const std::string& digits, int base) { if (digits.size() <= FROMSTRING_DC_THRESHOLD) { return fromStringHorner(digits, base); } size_t half = digits.size() / 2; size_t lowerLen = digits.size() - half; // 上位 half 桁、下位 lowerLen 桁 std::string upperStr = digits.substr(0, half); std::string lowerStr = digits.substr(half); Int upper = fromStringDC(upperStr, base); Int lower = fromStringDC(lowerStr, base); // upper * base^lowerLen + lower Int basePow = pow(Int(base), static_cast(lowerLen)); return upper * basePow + lower; } // ================================================================ // fromString 本体 // ================================================================ Int IntIOUtils::fromString(std::string_view str, int base) { if (base != 0 && (base < 2 || base > 36)) { throw std::invalid_argument("Base must be between 2 and 36"); } // 空文字列チェック if (str.empty()) { throw std::invalid_argument("Cannot convert empty string to Int"); } // 特殊文字列の処理 if (str == "NaN") return Int::NaN(); if (str == "Infinity" || str == "+Infinity") return Int::PositiveInfinity(); if (str == "-Infinity") return Int::NegativeInfinity(); // 符号の処理 bool isNegative = false; size_t start = 0; if (str[0] == '-') { isNegative = true; start = 1; } else if (str[0] == '+') { start = 1; } // 基数プレフィックスの処理 if (base == 0) { if (str.size() > start + 1 && str[start] == '0') { if (str[start + 1] == 'x' || str[start + 1] == 'X') { base = 16; start += 2; } else if (str[start + 1] == 'b' || str[start + 1] == 'B') { base = 2; start += 2; } else { base = 8; start += 1; } } else { base = 10; } } // 10進 small fast path: 20桁以下なら手動パース (cleanDigits を回避) // 18桁以下: オーバーフローなし (10^18 < UINT64_MAX) // 19-20桁: オーバーフローチェック付き { size_t ndigits = str.size() - start; if (base == 10 && ndigits <= 20) { bool clean = true; for (size_t i = start; i < str.size(); i++) { char c = str[i]; if (c < '0' || c > '9') { clean = false; break; } } if (clean) { uint64_t val = 0; bool overflow = false; for (size_t i = start; i < str.size(); i++) { uint64_t d = str[i] - '0'; if (ndigits >= 20) { // オーバーフローチェック if (val > UINT64_MAX / 10 || (val == UINT64_MAX / 10 && d > UINT64_MAX % 10)) { overflow = true; break; } } val = val * 10 + d; } if (!overflow) { if (val == 0) return Int::Zero(); Int result(val); if (isNegative) result.negate(); return result; } // overflow → 通常パスにフォールスルー } } } // 数字部分をバリデーション・クリーン化 std::string digits = cleanDigits(str, start, base); if (digits.empty()) return Int::Zero(); // アルゴリズム選択 Int result; int bitsPerDigit = bitsForPow2Base(base); if (bitsPerDigit > 0) { // 2のべき乗基数: ビットパッキング O(n) result = fromStringPow2(digits, bitsPerDigit); } else if (digits.size() <= FROMSTRING_DC_THRESHOLD) { // 一般基数・小桁数: Horner 法 result = fromStringHorner(digits, base); } else { // 一般基数・大桁数: 分割統治法 result = fromStringDC(digits, base); } // 符号を適用 if (isNegative) { result = -result; } return result; } std::string IntIOUtils::format(const Int& value, const FormatOptions& options) { // 特殊状態を処理 std::string specialResult = IntSpecialStates::handleToString(value); if (!specialResult.empty()) { return specialResult; } // 通常の値の処理 std::stringstream ss; // 符号の処理 bool isNegative = value.m_sign < 0; // 符号の表示設定に基づいて処理 if (options.showSign && !isNegative && value.m_sign != 0) { ss << "+"; } else if (isNegative) { ss << "-"; } // 基数プレフィックスの表示 if (options.showBase) { switch (options.base) { case 2: ss << "0b"; break; case 8: ss << "0"; break; case 16: ss << "0x"; break; // 他の基数は特別なプレフィックスなし } } // 大文字/小文字設定 std::string rawString = toString(isNegative ? -value : value, options.base); // 最小幅と0埋めの処理 if (options.width > 0 && rawString.length() < static_cast(options.width)) { size_t padSize = options.width - rawString.length(); if (options.zeroPad) { rawString = std::string(padSize, '0') + rawString; } else { rawString = std::string(padSize, ' ') + rawString; } } // 大文字/小文字変換 if (options.uppercase) { std::transform(rawString.begin(), rawString.end(), rawString.begin(), [](unsigned char c) { return std::toupper(c); }); } ss << rawString; return ss.str(); } void IntIOUtils::setFromStringDCThreshold(size_t threshold) { FROMSTRING_DC_THRESHOLD = threshold; } size_t IntIOUtils::getFromStringDCThreshold() { return FROMSTRING_DC_THRESHOLD; } // ================================================================ // exportBinary / importBinary // ================================================================ static inline bool is_native_little_endian() { uint16_t x = 1; return *reinterpret_cast(&x) == 1; } static inline uint64_t swap64(uint64_t v) { return ((v & 0x00000000000000FFULL) << 56) | ((v & 0x000000000000FF00ULL) << 40) | ((v & 0x0000000000FF0000ULL) << 24) | ((v & 0x00000000FF000000ULL) << 8) | ((v & 0x000000FF00000000ULL) >> 8) | ((v & 0x0000FF0000000000ULL) >> 24) | ((v & 0x00FF000000000000ULL) >> 40) | ((v & 0xFF00000000000000ULL) >> 56); } static inline uint64_t to_target_endian(uint64_t v, IntIOUtils::Endian endian) { bool target_le = (endian == IntIOUtils::Endian::Little) || (endian == IntIOUtils::Endian::Native && is_native_little_endian()); bool native_le = is_native_little_endian(); if (target_le == native_le) return v; return swap64(v); } std::vector IntIOUtils::exportBinary(const Int& value, Endian endian) { if (value.isNaN() || value.isInfinite()) { throw std::invalid_argument("exportBinary: NaN/Infinity cannot be serialized"); } uint32_t n = 0; uint8_t sign_byte = 0; // 0 = zero if (!value.isZero()) { n = static_cast(value.size()); sign_byte = value.isNegative() ? 0xFF : 0x01; } // ヘッダ: sign(1) + size(4) + データ: n*8 std::vector result(5 + static_cast(n) * 8); result[0] = sign_byte; // size を LE 4バイトで書き込み std::memcpy(&result[1], &n, 4); // ワードを書き込み (LSW first) const uint64_t* words = value.data(); for (uint32_t i = 0; i < n; ++i) { uint64_t w = to_target_endian(words[i], endian); std::memcpy(&result[5 + i * 8], &w, 8); } return result; } Int IntIOUtils::importBinary(std::span data, Endian endian) { if (data.size() < 5) { throw std::invalid_argument("importBinary: data too short (need at least 5 bytes)"); } uint8_t sign_byte = data[0]; uint32_t n; std::memcpy(&n, &data[1], 4); if (data.size() < 5 + static_cast(n) * 8) { throw std::invalid_argument("importBinary: data too short for declared word count"); } if (n == 0 || sign_byte == 0) { return Int(0); } std::vector words(n); for (uint32_t i = 0; i < n; ++i) { uint64_t w; std::memcpy(&w, &data[5 + i * 8], 8); words[i] = to_target_endian(w, endian); } int sign = (sign_byte == 0xFF) ? -1 : 1; return Int::fromRawWords(words, sign); } } // namespace calx