BME680 sensor faulty gas reference readings - arduino-c++

I have a problem with my BME680 readings. I found example code to collect all the air data and make an air quality score (AIQ) out of that (it goes from 0 to 500). You see in the code bellow that in loop() data is collected and AIQ calculated every 2 seconds, and gas reference is updated/collected on every 5-th reading -> if (getgasreference_count++) % 5 == 0) GetGasReference(); so 5*2 sec= every 10 sec (around 230k ohms).
Problem starts here, where I implement this code in my other program which runs website and controls motors. Here data is collected and AIQ calculated once a minute, gas reference is updated/collected on every 5-th reading -> if (getgasreference_count++) % 5 == 0) GetGasReference(); so 5*60 sec= every 300 sec= 5 min. Thats when I'm getting faulty gas reference readings (around 100k ohms) and of course following calculated AIQ is wrong.
I'm wondering where the problem is because why it matters when you get new gas reference? I'm thinking that bme.setGasHeater(); function has something to do with it, but I don't know how it is related with gas reference readings? I would be grateful if anybody can explain where the problem is.
The working example code is here:
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME680.h>
#define SEALEVELPRESSURE_HPA (1013.25)
#define YOUR_SENSOR_I2C_ADDRESS 0x76
Adafruit_BME680 bme; // I2C
float hum_weighting = 0.25; // so hum effect is 25% of the total air quality score
float gas_weighting = 0.75; // so gas effect is 75% of the total air quality score
int humidity_score, gas_score;
float gas_reference = 2500;
float hum_reference = 40;
int getgasreference_count = 0;
int gas_lower_limit = 10000; // Bad air quality limit
int gas_upper_limit = 300000; // Good air quality limit
void setup() {
Serial.begin(115200);
Serial.println(F("BME680 test"));
Wire.begin();
if (!bme.begin(YOUR_SENSOR_I2C_ADDRESS)) {
Serial.println("Could not find a valid BME680 sensor, check wiring!");
while (1);
} else Serial.println("Found a sensor");
// Set up oversampling and filter initialization
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320°C for 150 ms
// Now run the sensor to normalise the readings, then use combination of relative humidity and gas resistance to estimate indoor air quality as a percentage.
// The sensor takes ~30-mins to fully stabilise
GetGasReference();
}
void loop() {
Serial.println("Sensor Readings:");
Serial.println(" Temperature = " + String(bme.readTemperature(), 2) + "°C");
Serial.println(" Pressure = " + String(bme.readPressure() / 100.0F) + " hPa");
Serial.println(" Humidity = " + String(bme.readHumidity(), 1) + "%");
Serial.println(" Gas = " + String(gas_reference) + " ohms\n");
Serial.print("Qualitative Air Quality Index ");
humidity_score = GetHumidityScore();
gas_score = GetGasScore();
//Combine results for the final IAQ index value (0-100% where 100% is good quality air)
float air_quality_score = humidity_score + gas_score;
Serial.println(" comprised of " + String(humidity_score) + "% Humidity and " + String(gas_score) + "% Gas");
if ((getgasreference_count++) % 5 == 0) GetGasReference();
Serial.println(CalculateIAQ(air_quality_score));
Serial.println("--------------------------------------------------------------");
delay(2000);
}
void GetGasReference() {
// Now run the sensor for a burn-in period, then use combination of relative humidity and gas resistance to estimate indoor air quality as a percentage.
//Serial.println("Getting a new gas reference value");
int readings = 10;
for (int i = 1; i <= readings; i++) { // read gas for 10 x 0.150mS = 1.5secs
gas_reference += bme.readGas();
}
gas_reference = gas_reference / readings;
//Serial.println("Gas Reference = "+String(gas_reference,3));
}
String CalculateIAQ(int score) {
String IAQ_text = "air quality is ";
score = (100 - score) * 5;
if (score >= 301) IAQ_text += "Hazardous";
else if (score >= 201 && score <= 300 ) IAQ_text += "Very Unhealthy";
else if (score >= 176 && score <= 200 ) IAQ_text += "Unhealthy";
else if (score >= 151 && score <= 175 ) IAQ_text += "Unhealthy for Sensitive Groups";
else if (score >= 51 && score <= 150 ) IAQ_text += "Moderate";
else if (score >= 00 && score <= 50 ) IAQ_text += "Good";
Serial.print("IAQ Score = " + String(score) + ", ");
return IAQ_text;
}
int GetHumidityScore() { //Calculate humidity contribution to IAQ index
float current_humidity = bme.readHumidity();
if (current_humidity >= 38 && current_humidity <= 42) // Humidity +/-5% around optimum
humidity_score = 0.25 * 100;
else
{ // Humidity is sub-optimal
if (current_humidity < 38)
humidity_score = 0.25 / hum_reference * current_humidity * 100;
else
{
humidity_score = ((-0.25 / (100 - hum_reference) * current_humidity) + 0.416666) * 100;
}
}
return humidity_score;
}
int GetGasScore() {
//Calculate gas contribution to IAQ index
gas_score = (0.75 / (gas_upper_limit - gas_lower_limit) * gas_reference - (gas_lower_limit * (0.75 / (gas_upper_limit - gas_lower_limit)))) * 100.00;
if (gas_score > 75) gas_score = 75; // Sometimes gas readings can go outside of expected scale maximum
if (gas_score < 0) gas_score = 0; // Sometimes gas readings can go outside of expected scale minimum
return gas_score;
}

From this Learn Adafruit page it seems the gas readouts can take up to 30 minutes to stabilize.
Gas is returned as a resistance value in ohms. This value takes up to 30 minutes to stabilize! Once it stabilizes, you can use that as your baseline reading. Higher concentrations of VOC will make the resistance lower.
and on their first page:
We recommend that you run this sensor for 48 hours when you first receive it to "burn it in", and then 30 minutes in the desired mode every time the sensor is in use.

Related

Computation of 64 bit CRC polynomial performance

I found the following page in the web:
https://users.ece.cmu.edu/~koopman/crc/crc64.html
It lists the performance of a handful of 64 bit CRC polynomials. The optimal payload for a hamming distance of 3 is listed as 18446744073709551551 bit. A polynomial providing that HD 3 payload is 0xd6c9e91aca649ad4 (Koopman notation).
On the same website there is also some basic "HDLen" C code that can compute the performance of any polynomial (https://users.ece.cmu.edu/~koopman/crc/hdlen.html). I checked that code and the HD 3 optimized loop is very simple, similar to this:
Poly_t accum = cPoly;
Length_t len = 0;
while(accum != cTopBitSet)
{
accum = (accum & 1) ? (accum >> 1) ^ cPoly) : (accum >> 1);
len++;
}
18446744073709551551 is a huge number. It is almost the full range of a 64 bit integral. Even that simple loop would run centuries on the most powerful CPU core available.
It also appears to me that this loop can not be parallelized since each iteration depends from the previous iteration.
It is claimed that payload is optimal amongst all possible 64 bit polynomials which means that all possible 64 bit polynomials would have been checked for their individual HD 3 performance. This task can be parallelized, still the huge number of candidate polynomials seems to be undoable.
I can't see a way to even compute a single (good) polynomial's (HD 3) performance. Not to mention all possible 64 bit wide polynomials.
So I wonder: How has the number been found? What kind of code or method (in contrast to the simple HDLen software) was used to find the mentioned optimal HD 3 payload?
It is a primitive polynomial, where it can be shown that the HD=3 length of any primitive polynomial over GF(2) is 2n-(n+1), where n is the degree of the polynomial.
It can be shown pretty quickly whether a polynomial over a finite field is primitive or not.
Also, it is possible to compute the CRC of a very sparse codeword of n bits in O(log n) time instead of O(n) time. Here is an example in C, demonstrating the case mentioned for the provided CRC:
#include <stdio.h>
#include <stdint.h>
// Jones' 64-bit primitive polynomial (the constant excludes the x^64 term):
// 1 + x^3 + x^5 + x^7 + x^8 + x^10 + x^12 + x^13 + x^16 + x^19 + x^22 + x^23 +
// x^26 + x^28 + x^31 + x^32 + x^34 + x^36 + x^37 + x^41 + x^44 + x^46 + x^47 +
// x^48 + x^49 + x^52 + x^55 + x^56 + x^58 + x^59 + x^61 + x^63 + x^64
#define POLY 0xad93d23594c935a9
#define HIGH 0x8000000000000000 // high bit set
// Return polynomial a times polynomial b modulo p (POLY). a must be non-zero.
static uint64_t multmodp(uint64_t a, uint64_t b) {
uint64_t prod = 0;
for (;;) {
if (a & 1) {
prod ^= b;
if (a == 1)
break;
}
a >>= 1;
b = b & HIGH ? (b << 1) ^ POLY : b << 1;
}
return prod;
}
// x2n_table[n] is x^2^n mod p.
static uint64_t x2n_table[64];
// Initialize x2n_table[].
static void x2n_table_init(void) {
uint64_t p = 2; // first entry is x^2^0 == x^1
x2n_table[0] = p;
for (size_t n = 1; n < 64; n++)
x2n_table[n] = p = multmodp(p, p);
}
// Compute x^n modulo p. This takes O(log n) time.
static uint64_t xtonmodp(uintmax_t n) {
uint64_t x = 1;
int k = 0;
for (;;) {
if (n & 1)
x = multmodp(x2n_table[k], x);
n >>= 1;
if (n == 0)
break;
k++;
}
return x;
}
// Feed n zero bits into the CRC, taking O(log n) time.
static uint64_t crc64zeros(uint64_t crc, uint64_t n) {
return multmodp(xtonmodp(n), crc);
}
// Feed one one bit into the CRC.
static uint64_t crc64one(uint64_t crc) {
return crc & HIGH ? crc << 1 : (crc << 1) ^ POLY;
}
// Return the CRC-64 of one one bit, followed by n zero bits, followed by one
// more one bit.
static uint64_t crc64_one_zeros_one(uint64_t n) {
return crc64one(crc64zeros(crc64one(0), n));
}
int main(void) {
x2n_table_init();
uint64_t n = -2; // code word with 2^64 bits: a 1, 2^64-2 0's, and a 1
printf("%llx\n", crc64_one_zeros_one(n)); // prints 0
return 0;
}
That calculation completes in about 7.4 µs on my machine. As opposed to the bit-at-a-time calculation, which would take about 560 years on my machine.

How to meet properly if condition

I'm having a problem with this task. My goal is to calculate car price providing its mileage.
The formula is:
price lowers by 200$ for every 10000 kilometers that the car passed. For example, for 19999 km the price decreases by 200 dollars, but for 20000 km the price lowers by 400 dollars. And additionally the prices lowers due to car's age multiplied by 200.
What I did is:
when (input) {
in 0..9999 -> {
moneyToRemove = 2000
price -= moneyToRemove
}
else -> {
var counter = 0
moneyToRemove = old * 2000
for (x in 10000..input) {
if (x % 10000 == 0 ) {
counter++
if (x == input) {
println(counter)
price = price - moneyToRemove - counter*200
}
}
}
}
}
input - mileage provided from scanner
In first when condition moneyToRemove variable is set to 2000 ( 5years *200)
In else condition I tried to count how many extra dollars will lower the car price using counter. But then I have a problem with e.g. value 299 999. The counter in debugger mode shows 29 which is correct, but I can't meet the second condition and properly count car's price. Can you help?
This is from JetBrains academy Kotlin dev track.
You don't need any if conditions for that. You can put it in a simple formula
import kotlin.math.*
fun main() {
var originalprice = 30000; // the original price
var miles = 12345.6; // the mileage
var age = 7; //the age in years
var reducedprice = originalprice -
(floor(miles / 10000.0)) * 200 - //substract 200$ for each full 10000 miles
age * 200 //substract 200$ foreach year
print(reducedprice);
}
floor(miles/ 10000.0) will divide the miles by 10000 and round it downwards to the next integer. So it will be 0 for 0-9999, 1 for 10000 - 19999 and so on ... floor
If miles is an integer value, you don't even need floor, because the integer division will only return whole numbers. Ie 9999/10000 = 0, 19999/10000 = 1 and so on ...
var reducedprice = originalprice -
(miles / 10000) * 200 - //substract 200$ for each full 10000 miles
age * 200 //substract 200$ foreach year
EDIT
Eventhough I consider your approach as quite complicated and not easily maintainable
You have code duplication for the age part,
You do a lot of unnecessary % calculations for each value from 10000 up to your input
The when isn't necessary, because all the code exectued in the first branch is also executed in the second branch. Just could just do the loop, starting at 10000, so if the input is < 10000 it won't be executed ...
But all in all, your approach will in principle work too. But there are two issues with it
You don't consider the age of the car, when the mileage is below 10000, but you just withdraw a fixed amount.
When the mileage is above 10000, you calculate the reduced price only if the input is an exact multiple of 10000. You must put that calculation after the loop.
when (input) {
in 0..9999 -> {
moneyToRemove = old * 2000
price -= moneyToRemove
}
else -> {
moneyToRemove = old * 2000
for (x in 10000..input) {
if (x % 10000 == 0 ) {
moneyToRemove += 200
}
}
println(counter)
price -= moneyToRemove
}
}

Issues in ADC of PIC16F887 reading LM35 temperature value is frequently varying

I have connected the LM35 temperature sensor in analog channel of AN1 then transmitting the temperature through serial RS232 in PIC16F887. I have read the temperature value but the value is frequently varying as 31 and 32, also when touch the LM35 temperature is varying very fast as 32, 33 and etc. How to control the constant temperature value. Here is my code, Please any one help me.
#include <htc.h>
void main(void)
{
TRISA1 = 1;
ANS1 = 1;
OSCCON = 0x78; // OSCILLATOR CONTROL REGISTER
TXSTA = 0x26;
RCSTA = 0x90;
SPBRG = 10;
ADCON1 = 0x80;
unsigned int current_temp, initial_temp = temperature();
transmit_char(initial_temp);
flag = 0;
while (1)
{
current_temp = temperature();
if((current_temp == (initial_temp + 1)) || (current_temp == (initial_temp - 1)))
{
flag = 1;
}
if(flag == 1)
{
flag = 0;
transmit_char(current_temp);
initial_temp = current_temp;
}
}
}
int temperature(void)
{
ADCON0 = 0xC5;
GODONE = 1;
while(GODONE);
int temp;
temp = (ADRESH << 8) + ADRESL;
temp = temp/2;
return temp;
}
Do this,
Collect ten adc data,
Remove min and max from that values.
sum the remaining 8 ADC values and divide the value by 8.
this should be continuous process, average for every ten samples
note: sampling and averaging should be ... 1 to 10 , next 2 to 11 and 3 to 12. at every reading you will get the adc value. sudden fluctuations also get filtered here.
Hope this helps.. good luck..

STM32F4 PWM ramp

I'm working on a project where I want to ramp the pwm duty cycle from 0 to 50%. My period is 16000 counts or 1ms (16MHz default timer count). For some reason, instead of updating the duty cycle each period, it updates it much slower. I wonder if it's because I'm calculating the new duty cycle within the timer interrupt? Here's what I'm using:
void TIM4_IRQHandler()
{
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
if (loop <= 8000) {
TIM4 -> CCR1 = CCR_i;
uint16_t y = CCR_i;
CCR_i = y + 1;
int x = loop;
loop = x + 1;
}
if (loop == 8001) {
TIM4 -> CCR1 = 0;
uint16_t x = CCR_i;
CCR_i = x + 1;
int c = loop;
loop = c + 1;
}
if (loop > 8001) {
int t;
for(t = 0; t < 10; t++){
// wait
}
GPIO_SetBits(GPIOG, GPIO_Pin_8);
//Stop2();
TIM_ITConfig(TIM4, TIM_IT_Update, DISABLE);
NVIC_DisableIRQ(TIM4_IRQn);
}
}
}
blast, it looks like I was being silly - the timer is doing exactly what I want it to - it just takes 8 seconds with a period of 1ms to ramp to a pulsewidth of 500us adding 62.5ns per period.

Is there any GMP logarithm function?

Is there any logarithm function implemented in the GMP library?
I know you didn't ask how to implement it, but...
You can implement a rough one using the properties of logarithms: http://gnumbers.blogspot.com.au/2011/10/logarithm-of-large-number-it-is-not.html
And the internals of the GMP library: https://gmplib.org/manual/Integer-Internals.html
(Edit: Basically you just use the most significant "digit" of the GMP representation since the base of the representation is huge B^N is much larger than B^{N-1})
Here is my implementation for Rationals.
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;
}
(Much later edit)
Coming back to this 5 years later, I just think it's cool that the core concept of log(a) = N log(B) + log(a_N) shows up even in native floating point implementations, here is the glibc one for ia64
And I used it again after encountering this question
No there is no such function in GMP.
Only in MPFR.
The method below makes use of mpz_get_d_2exp and was obtained from the gmp R package. It can be found under the function biginteger_log in the file bigintegerR.cc (You first have to download the source (i.e. the tar file)). You can also see it here: 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;
}
Of course, the above method could be modified to return the log with any base using the properties of logarithm (e.g. the change of base formula).
Here it is:
https://github.com/linas/anant
Provides gnu mp real and complex logarithm, exp, sine, cosine, gamma, arctan, sqrt, polylogarithm Riemann and Hurwitz zeta, confluent hypergeometric, topologists sine, and more.
As other answers said, there is no logarithmic function in GMP. Part of answers provided implementations of logarithmic function, but with double precision only, not infinite precision.
I implemented full (arbitrary) precision logarithmic function below, even up to thousands bits of precision if you wish. Using mpf, generic floating point type of GMP.
My code uses Taylor serie for ln(1 + x) plus mpf_sqrt() (for boosting computation).
Code is in C++, and is quite large due to two facts. First is that it does precise time measurements to figure out best combinations of internal computational parameters for your machine. Second is that it uses extra speed improvements like extra usage of mpf_sqrt() for preparing initial value.
Algorithm of my code is following:
Factor out exponent of 2 from input x, i.e. rewrite x = d * 2^exp, with usage of mpf_get_d_2exp().
Make d (from step above) such that 2/3 <= d <= 4/3, this is achieved by possibly multiplying d by 2 and doing --exp. This ensures that d always differs from 1 by at most 1/3, in other words d extends from 1 in both directions (negative and positive) in equal distance.
Divide x by 2^exp, with usage of mpf_div_2exp() and mpf_mul_2exp().
Take square root of x several times (num_sqrt times) so that x becomes closer to 1. This ensures that Taylor Serie converges more rapidly. Because computation of square root several times is faster than contributing much more time in extra iterations of Taylor Serie.
Compute Taylor Serie for ln(1 + x) up to desired precision (even thousands of bit of precision if needed).
Because in Step 4. we took square root several times, now we need to multiply y (result of Taylor Serie) by 2^num_sqrt.
Finally because in Step 1. we factored out 2^exp, now we need to add ln(2) * exp to y. Here ln(2) is computed by just one recursive call to same function that implements whole algorithm.
Steps above come from sequence of formulas ln(x) = ln(d * 2^exp) = ln(d) + exp * ln(2) = ln(sqrt(...sqrt(d))) * num_sqrt + exp * ln(2).
My implementation automatically does timings (just once per program run) to figure out how many square roots is needed to balance out Taylor Serie computation. If you need to avoid timings then pass 3rd parameter sqrt_range to mpf_ln() equal to 0.001 instead of zero.
main() function contains examples of usage, testing of correctness (by comparing to lower precision std::log()), timings and output of different verbose information. Function is tested on first 1024 bits of Pi number.
Before call to my function mpf_ln() don't forget to setup needed precision of computation by calling mpf_set_default_prec(bits) with desired precision in bits.
Computational time of my mpf_ln() is about 40-90 micro-seconds for 1024 bit precision. Bigger precision will take more time, that is approximately linearly proportional to the amount of precision bits.
Very first run of a function takes considerably longer time becuse it does pre-computation of timings table and value of ln(2). So it is suggested to do first single computation at program start to avoid longer computation inside time critical region later in code.
To compile for example on Linux, you have to install GMP library and issue command:
clang++-14 -std=c++20 -O3 -lgmp -lgmpxx -o main main.cpp && ./main
Try it online!
#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;
}
Output:
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