// 2^(expn2)を有効数字disp10桁の10進数で指数表示する。
// Usage: print_by_decimal <expn2> <disp10>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <vector>
#include <utility>
#include <time.h>
// 指数の型
typedef uint64_t expn_t;
// 2乗したものを足し合わせてもuint64_tが桁あふれしない10進数
#define BIG_DECIMAL 10000000ULL
static_assert(UINT64_MAX - (BIG_DECIMAL - 1) * (BIG_DECIMAL - 1) >= 2 * (BIG_DECIMAL - 1), "*** ERR ***");
static_assert(2 * (BIG_DECIMAL - 1) + (BIG_DECIMAL - 1) * (BIG_DECIMAL - 1) <= UINT64_MAX, "*** ERR ***");
namespace
{
struct BigFloat
{
std::vector<uint32_t> frac;
expn_t expn;
BigFloat() : expn(0) { }
BigFloat(const size_t sz, const uint32_t inival = 0)
: frac(sz, inival), expn(0) { }
size_t size() const { return frac.size(); }
uint32_t operator[](const size_t k) const { return frac[k]; }
};
/// 先頭の0を切り詰めたサイズを返す。
size_t truncated_size(const std::vector<uint32_t>& mbuf)
{
const size_t sz = mbuf.size();
for (size_t i = sz; i > 0; i--) {
if (mbuf[i - 1] != 0UL) {
return i;
}
}
return sz;
}
/// 計算結果の記憶桁数に制限を設けた乗算
/// メモリ消費を半分にするため、キャリー伝搬を単純に桁の積和の都度行う。
/// (最上位の桁が0になることがあるため、記憶桁数bufszは厳密には精度を意味しない。)
std::vector<uint32_t> mlen_mul(const std::vector<uint32_t>& lhs, const std::vector<uint32_t>& rhs,
const expn_t bufsz_, const uint32_t a_priori_cy,
expn_t& expn)
{
const size_t sz_lhs = truncated_size(lhs);
const size_t sz_rhs = truncated_size(rhs);
const size_t dstsz = sz_lhs + sz_rhs;
const size_t bufsz = std::min(dstsz, bufsz_);
const size_t capable_min_idx = dstsz - bufsz;
// 指数出力
expn = capable_min_idx;
std::vector<uint32_t> mbuf(bufsz, 0UL);
for (expn_t i = 0; i < sz_lhs; i++) {
uint32_t cy = 0UL;
for (expn_t j = 0; j < sz_rhs; j++) {
// 計算結果を格納すべき本来の位置
const size_t dst_idx = i + j;
if (dst_idx >= capable_min_idx) {
if (dst_idx == capable_min_idx && j > 0) {
// アプリオリなキャリー設定
// [capable_min_idx]より小さい桁の計算を行わないため、
// mbuf[]の最下位桁に繰り上がる真のキャリーはわからない。
cy = a_priori_cy;
}
const size_t k = dst_idx - capable_min_idx;
const uint64_t val64 = (uint64_t)mbuf[k] + (uint64_t)lhs[i] * (uint64_t)rhs[j] + (uint64_t)cy;
cy = (uint32_t)(val64 / BIG_DECIMAL);
mbuf[k] = (uint32_t)(val64 - cy * BIG_DECIMAL);
}
}
{
const expn_t j = sz_rhs;
// 計算結果を格納すべき本来の位置
const size_t dst_idx = i + j;
if (dst_idx >= capable_min_idx) {
if (dst_idx == capable_min_idx) {
// アプリオリなキャリー設定
// [capable_min_idx]より小さい桁の計算を行わないため、
// mbuf[]の最下位桁に繰り上がる真のキャリーはわからない。
cy = a_priori_cy;
}
const size_t k = dst_idx - capable_min_idx;
const uint64_t val64 = (uint64_t)mbuf[k] + (uint64_t)cy;
cy = (uint32_t)(val64 / BIG_DECIMAL);
mbuf[k] = (uint32_t)(val64 - cy * BIG_DECIMAL);
assert(cy == 0);
}
}
}
return mbuf;
}
/// 浮動小数点演算による乗算
BigFloat mlen_mul(const BigFloat& lhs, const BigFloat& rhs,
const expn_t bufsz, const uint32_t a_priori_cy)
{
BigFloat result;
expn_t res_expn;
result.frac = mlen_mul(lhs.frac, rhs.frac, bufsz, a_priori_cy, res_expn);
result.expn = lhs.expn + rhs.expn + res_expn;
return result;
}
/// 2^nをBIG_DECIMAL進数で計算する。
BigFloat pow2(const expn_t n_, const size_t bufsz, const uint32_t a_priori_cy)
{
const time_t startTmg = time(NULL);
BigFloat mbuf((size_t)1, 1UL);
BigFloat mbuf_work((size_t)1, 2UL);
expn_t n = n_;
for (;;) {
if ((n & 0x1) != 0) {
mbuf = mlen_mul(mbuf, mbuf_work, bufsz, a_priori_cy);
}
const time_t now = time(NULL);
printf("n=%llu, mbuf sz=%zu --- %lld sec\n", n, mbuf.size(), now - startTmg);
n >>= 1;
if (n == 0ULL) {
break;
}
mbuf_work = mlen_mul(mbuf_work, mbuf_work, bufsz, a_priori_cy);
}
return mbuf;
}
/// 計算結果をファイルに出力する。
void BigFloat_op_dump(const BigFloat& mbuf, FILE* const fp)
{
const size_t sz = mbuf.size();
const size_t sqsz = ((sz + 9) / 10) * 10;
size_t cnt = 0;
for (size_t i = sqsz; i > 0; i--) {
if (i > sz) {
fprintf(fp, " %7s", " ");
}
else {
fprintf(fp, " %07lu", mbuf[i - 1]);
}
cnt++;
if (cnt % 10 == 0) {
if (i != 1) {
fprintf(fp, "\n");
}
else {
fprintf(fp, ". x 10^%llu\n", 7ULL * mbuf.expn);
}
}
}
}
#ifdef _WIN32
/// 計算結果をファイルに出力する。
bool write_to_file(const BigFloat& mbuf, const char* const fpath)
{
FILE* fp;
errno_t errt;
errt = fopen_s(&fp, fpath, "w");
if (errt != 0) {
return false;
}
BigFloat_op_dump(mbuf, fp);
fclose(fp);
return true;
}
#endif
} // namespace
int main(const int argc, char* argv[])
{
// 2のべきの指数
expn_t expn2 = 17179869183LL;
//expn_t expn2 = 171798;
//expn_t expn2 = 1000;
//expn_t expn2 = 64;
//expn_t expn2 = 32;
//expn_t expn2 = 16;
//expn_t expn2 = 8;
//expn_t expn2 = 0;
// 表示桁数(有効数字)
expn_t disp10 = 64;
#ifdef _WIN32
if (argc >= 2) {
expn2 = _atoi64(argv[1]);
}
if (argc >= 3) {
disp10 = _atoi64(argv[2]);
}
#else
(void)(argc, argv);
#endif
printf("Calculating 2^%llu ...\n", expn2);
// テスト
//BigFloat lhs((size_t)1, 9000000UL);
//BigFloat rhs((size_t)1, 9000000UL);
//BigFloat result = mlen_mul(lhs, rhs, 10, 0);
//printf("result=");
//BigFloat_op_dump(result, stdout);
const size_t bufsz = (disp10 + 6) / 7;
// 2^(expn2)の下限
const BigFloat minv = pow2(expn2, bufsz, 0UL);
BigFloat_op_dump(minv, stdout);
// 2^(expn2)の上限
const BigFloat maxv = pow2(expn2, bufsz, 1UL);
BigFloat_op_dump(maxv, stdout);
// 最終結果表示
printf("\n\n\n");
printf("2^%llu is between\n", expn2);
BigFloat_op_dump(minv, stdout);
BigFloat_op_dump(maxv, stdout);
#ifdef _WIN32
// ファイル出力
{
char buf[_MAX_PATH];
sprintf_s(buf, _countof(buf), "min_of_pow2(%llu)_in_decimal.txt", expn2);
write_to_file(minv, buf);
sprintf_s(buf, _countof(buf), "max_of_pow2(%llu)_in_decimal.txt", expn2);
write_to_file(maxv, buf);
}
#endif
}