正如其他答案所说,GMP 中没有对数函数。部分答案提供了对数函数的实现,但double仅具有精度,而不是无限精度。
我在下面实现了完整(任意)精度的对数函数,如果您愿意,甚至可以达到数千位的精度。使用mpf,GMP 的通用浮点类型。
我的代码使用泰勒级数 ln(1 + x)加上mpf_sqrt()(用于增强计算)。
代码是用 C++ 编写的,由于两个事实,代码相当大。首先,它进行精确的时间测量,以确定机器内部计算参数的最佳组合。其次,它使用了额外的速度改进,例如额外使用mpf_sqrt()来准备初始值。
我的代码的算法如下:
使用mpf_get_d_2exp()从 input 中分解出 2 的指数,x即 rewrite 。x = d * 2^exp
使d(从上面的步骤)使得2/3 <= d <= 4/3,这可以通过乘以d2 并执行来实现--exp。这确保了d始终与 1 最多相差1/3,换句话说,d从 1 开始在两个方向(负和正)上以相等的距离延伸。
除以x,2^exp使用mpf_div_2exp()和mpf_mul_2exp()。
x对几次(次)取平方根num_sqrt,使其x变得更接近1。这保证了泰勒级数收敛得更快。因为多次平方根的计算比在泰勒级数的额外迭代中贡献更多时间要快。
计算ln(1 + x) 的泰勒级数,直至达到所需的精度(如果需要,甚至可以达到数千位精度)。
因为在步骤4中,我们多次求平方根,现在我们需要将y(泰勒级数的结果)乘以2^num_sqrt。
最后因为在步骤1.中我们分解了2^exp,现在我们需要添加ln(2) * exp到y。这里ln(2)仅通过对实现整个算法的同一函数的一次递归调用来计算。
上述步骤来自公式序列ln(x) = ln(d * 2^exp) = ln(d) + exp * ln(2) = ln(sqrt(...sqrt(d))) * num_sqrt + exp * ln(2)。
我的实现会自动进行计时(每个程序运行一次),以计算出需要多少个平方根来平衡泰勒级数计算。如果您需要避免计时,请将第三个参数传递sqrt_range为mpf_ln()等于0.001而不是零。
main()函数包含用法示例、正确性测试(通过与较低精度的std::log()进行比较)、不同详细信息的计时和输出。函数在Pi数的前 1024 位上进行测试。
在调用我的函数之前,mpf_ln()不要忘记通过以所需的位精度调用mpf_set_default_prec(bits)来设置所需的计算精度。
对于位精度,我的计算时间mpf_ln()约为40-90微秒。1024精度越高,需要的时间越长,这与精度位数大致成线性比例。
函数的第一次运行需要相当长的时间,因为它会预先计算时序表和 的值ln(2)。因此,建议在程序启动时首先进行单个计算,以避免稍后在代码中在时间关键区域内进行更长的计算。
例如,要在 Linux 上进行编译,您必须安装GMP 库并发出命令:
clang++-14 -std=c++20 -O3 -lgmp -lgmpxx -o main main.cpp && ./main
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <cmath>
#include <chrono>
#include <mutex>
#include <vector>
#include <unordered_map>
#include <gmpxx.h>
double Time() {
static auto const gtb = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::duration<double>>(
std::chrono::high_resolution_clock::now() - gtb).count();
}
mpf_class mpf_ln(mpf_class x, bool verbose = false, double sqrt_range = 0) {
auto total_time = verbose ? Time() : 0.0;
int const prec = mpf_get_prec(x.get_mpf_t());
if (sqrt_range == 0) {
static std::mutex mux;
std::lock_guard<std::mutex> lock(mux);
static std::vector<std::pair<size_t, double>> ranges;
if (ranges.empty())
mpf_ln(3.14, false, 0.01);
while (ranges.empty() || ranges.back().first < prec) {
size_t const bits = ranges.empty() ? 64 : ranges.back().first * 3 / 2;
mpf_class x = 3.14;
mpf_set_prec(x.get_mpf_t(), bits);
double sr = 0.35, sr_best = 1, time_best = 1000;
size_t constexpr ntests = 5;
while (true) {
auto tim = Time();
for (size_t i = 0; i < ntests; ++i)
mpf_ln(x, false, sr);
tim = (Time() - tim) / ntests;
bool updated = false;
if (tim < time_best) {
sr_best = sr;
time_best = tim;
updated = true;
}
sr /= 1.5;
if (sr <= 1e-8) {
ranges.push_back(std::make_pair(bits, sr_best));
break;
}
}
}
sqrt_range = std::lower_bound(ranges.begin(), ranges.end(), size_t(prec),
[](auto const & a, auto const & b){
return a.first < b;
})->second;
}
signed long int exp = 0;
// https://gmplib.org/manual/Converting-Floats
double d = mpf_get_d_2exp(&exp, x.get_mpf_t());
if (d < 2.0 / 3) {
d *= 2;
--exp;
}
mpf_class t;
// https://gmplib.org/manual/Float-Arithmetic
if (exp >= 0)
mpf_div_2exp(x.get_mpf_t(), x.get_mpf_t(), exp);
else
mpf_mul_2exp(x.get_mpf_t(), x.get_mpf_t(), -exp);
auto sqrt_time = verbose ? Time() : 0.0;
// Multiple Sqrt of x
int num_sqrt = 0;
if (x >= 1)
while (x >= 1.0 + sqrt_range) {
// https://gmplib.org/manual/Float-Arithmetic
mpf_sqrt(x.get_mpf_t(), x.get_mpf_t());
++num_sqrt;
}
else
while (x <= 1.0 - sqrt_range) {
mpf_sqrt(x.get_mpf_t(), x.get_mpf_t());
++num_sqrt;
}
if (verbose)
sqrt_time = Time() - sqrt_time;
static mpf_class const eps = [&]{
mpf_class eps = 1;
mpf_div_2exp(eps.get_mpf_t(), eps.get_mpf_t(), prec + 8);
return eps;
}(), meps = -eps;
// Taylor Serie for ln(1 + x)
// https://math.stackexchange.com/a/878376/826258
x -= 1;
mpf_class k = x, y = x, mx = -x;
size_t num_iters = 0;
for (int32_t i = 2;; ++i) {
k *= mx;
y += k / i;
// Check if error is small enough
if (meps <= k && k <= eps) {
num_iters = i;
break;
}
}
auto VerboseInfo = [&]{
if (!verbose)
return;
total_time = Time() - total_time;
std::cout << std::fixed << "Sqrt range " << sqrt_range << ", num sqrts "
<< num_sqrt << ", sqrt time " << sqrt_time << " sec" << std::endl;
std::cout << "Ln number of iterations " << num_iters << ", ln time "
<< total_time << " sec" << std::endl;
};
// Correction due to multiple sqrt of x
y *= 1 << num_sqrt;
if (exp == 0) {
VerboseInfo();
return y;
}
mpf_class ln2;
{
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<size_t, mpf_class> ln2s;
auto it = ln2s.find(size_t(prec));
if (it == ln2s.end()) {
mpf_class sqrt_sqrt_2 = 2;
mpf_sqrt(sqrt_sqrt_2.get_mpf_t(), sqrt_sqrt_2.get_mpf_t());
mpf_sqrt(sqrt_sqrt_2.get_mpf_t(), sqrt_sqrt_2.get_mpf_t());
it = ln2s.insert(std::make_pair(size_t(prec), mpf_class(mpf_ln(sqrt_sqrt_2, false, sqrt_range) * 4))).first;
}
ln2 = it->second;
}
y += ln2 * exp;
VerboseInfo();
return y;
}
std::string mpf_str(mpf_class const & x) {
mp_exp_t exp;
auto s = x.get_str(exp);
return s.substr(0, exp) + "." + s.substr(exp);
}
int main() {
// https://gmplib.org/manual/Initializing-Floats
mpf_set_default_prec(1024); // bit-precision
// http://www.math.com/tables/constants/pi.htm
mpf_class x(
"3."
"1415926535 8979323846 2643383279 5028841971 6939937510 "
"5820974944 5923078164 0628620899 8628034825 3421170679 "
"8214808651 3282306647 0938446095 5058223172 5359408128 "
"4811174502 8410270193 8521105559 6446229489 5493038196 "
"4428810975 6659334461 2847564823 3786783165 2712019091 "
"4564856692 3460348610 4543266482 1339360726 0249141273 "
"7245870066 0631558817 4881520920 9628292540 9171536436 "
);
std::cout << std::boolalpha << std::fixed << std::setprecision(14);
std::cout << "x:" << std::endl << mpf_str(x) << std::endl;
auto cmath_val = std::log(mpf_get_d(x.get_mpf_t()));
std::cout << "cmath ln(x): " << std::endl << cmath_val << std::endl;
auto volatile tmp = mpf_ln(x); // Pre-Compute to heat-up timings table.
auto time_start = Time();
size_t constexpr ntests = 20;
for (size_t i = 0; i < ntests; ++i) {
auto volatile tmp = mpf_ln(x);
}
std::cout << "mpf ln(x) time " << (Time() - time_start) / ntests << " sec" << std::endl;
auto mpf_val = mpf_ln(x, true);
std::cout << "mpf ln(x):" << std::endl << mpf_str(mpf_val) << std::endl;
std::cout << "equal to cmath: " << (std::abs(mpf_get_d(mpf_val.get_mpf_t()) - cmath_val) <= 1e-14) << std::endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
输出:
x:
3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587007
cmath ln(x):
1.14472988584940
mpf ln(x) time 0.00004426845000 sec
Sqrt range 0.00000004747981, num sqrts 23, sqrt time 0.00001440000000 sec
Ln number of iterations 42, ln time 0.00003873100000 sec
mpf ln(x):
1.144729885849400174143427351353058711647294812915311571513623071472137769884826079783623270275489707702009812228697989159048205527923456587279081078810286825276393914266345902902484773358869937789203119630824756794011916028217227379888126563178049823697313310695003600064405487263880223270096433504959511813198
equal to cmath: true
Run Code Online (Sandbox Code Playgroud)
我知道你没有问过如何实现它,但......
您可以使用对数属性实现粗略的:http://gnumbers.blogspot.com.au/2011/10/logarithm-of-large-number-it-is-not.html
以及GMP库的内部:https://gmplib.org/manual/Integer-Internals.html
(编辑:基本上,你只需要使用GMP表示最显著"数字",因为表现的基础是巨大的 B^N远远大于B^{N-1})
这是我对Rational的实现.
double LogE(mpq_t m_op)
{
// log(a/b) = log(a) - log(b)
// And if a is represented in base B as:
// a = a_N B^N + a_{N-1} B^{N-1} + ... + a_0
// => log(a) \approx log(a_N B^N)
// = log(a_N) + N log(B)
// where B is the base; ie: ULONG_MAX
static double logB = log(ULONG_MAX);
// Undefined logs (should probably return NAN in second case?)
if (mpz_get_ui(mpq_numref(m_op)) == 0 || mpz_sgn(mpq_numref(m_op)) < 0)
return -INFINITY;
// Log of numerator
double lognum = log(mpq_numref(m_op)->_mp_d[abs(mpq_numref(m_op)->_mp_size) - 1]);
lognum += (abs(mpq_numref(m_op)->_mp_size)-1) * logB;
// Subtract log of denominator, if it exists
if (abs(mpq_denref(m_op)->_mp_size) > 0)
{
lognum -= log(mpq_denref(m_op)->_mp_d[abs(mpq_denref(m_op)->_mp_size)-1]);
lognum -= (abs(mpq_denref(m_op)->_mp_size)-1) * logB;
}
return lognum;
}
Run Code Online (Sandbox Code Playgroud)
(很久以后编辑)回到这个5年之后,我认为log(a) = N log(B) + log(a_N)即使在原生浮点实现中出现的核心概念也很酷,这里是ia64的glibc并且我在遇到这个问题后再次使用它
下面的方法使用mpz_get_d_2exp并从gmp R 包中获取。biginteger_log它可以在文件中的函数下找到bigintegerR.cc(您首先必须下载源代码(即 tar 文件))。您还可以在这里看到它:biginteger_log。
// Adapted for general use from the original biginteger_log
// xi = di * 2 ^ ex ==> log(xi) = log(di) + ex * log(2)
double biginteger_log_modified(mpz_t x) {
signed long int ex;
const double di = mpz_get_d_2exp(&ex, x);
return log(di) + log(2) * (double) ex;
}
Run Code Online (Sandbox Code Playgroud)
当然,可以修改上述方法以使用对数的属性(例如改变底数公式)返回任何底数的对数。