跳转至

2022

二进制小数精度

计算机使用2进制存储数据,而2进制可以表示所有的10进制整数,这样在整数范围内不存在问题,但导致小数的精度不够。

例如表示整数时,二进制不存在问题: |十进制|二进制| |--|--| |0| 0| |1|2^0| |2|2^1| |3|2^2 + 2^1|

而表示小数时,二进制只能有0.5, 0.25, 0.125...。 |二进制|十进制| |--|--| |2^-1| 0.5| |2^-2|0.25| |2^-3|0.125|

所以二进制表示10进制的小数颗粒比较大,若想表示0.6: 就要使用二进制小数 * 2^n来表示,有些数始终无法相等

浮点型和双精度

计算机显然没有无穷大的位置来存储”高阶的X“,只能折衷一下。故在c/c++中,有两种类型来表示小数,对应不同精度:
内存分布,两种类型的内存分布都为: 符号位(sign)+偏移位(exponent)+尾数位(mantissa), 其值则为 (-1)^sign * mantissa * 2^exponent

float: 浮点类型

占用4字节(32位),精度为10^-6, 即第7位小数开始可能四舍五入或者直接忽略

内存分布: 第一位是符号位,后8位为偏移位,最后23位为尾数位,则有23+1位来表示小数,10^7 < 2^23+1 < 10^8,所以可以精确到第7位, 但最后以为可能会4四舍五入,精确到第6位

double: 双精度类型

占用8字节(64位),精度为10^-15, 即第16位小数开始可能四舍五入或者直接忽略

内存分布: 第一位是符号位,后11位为偏移位,最后52位为尾数位,则有52+1位来表示小数,10^16 < 2^52+1 < 10^17,所以可以精确到第16位, 但最后以为可能会4四舍五入,精确到第15位

计算问题

由于精度问题,精度之外的小数就会被忽略掉,导致出现下面的相等

double i = 0.1;
double j = 0.2;
i+j == 0.3 + 4e-17;// 后面的数被忽略了
注意如果要使用float类型,需要显式使用'0.1f', 因为默认以double处理

如何比较两个浮点数

浮点数比较不可能100%正确,但下面都是错的

float a = 0.15 + 0.15
float b = 0.1 + 0.2
if(a == b) // can be false!
if(a >= b) // can also be false!
if( Math.abs(a-b) < 0.00001) // wrong - don't do this
if( Math.abs((a-b)/b) < 0.00001 ) // still not right!

下面的才是大部分正确

float absA = fabs(a);
float absB = fabs(b);
float diff = fabs(a - b);
if (a == b) { // shortcut, handles infinities
    return true;
} else if (a == 0 || b == 0 || (absA + absB < 1e-16)) {
            // a or b is zero or both are extremely close to it
            // relative error is less meaningful here
    return diff < (epsilon * Float.MIN_NORMAL);
} else { // use relative error
    return diff / Math.min((absA + absB), Float.MAX_VALUE) < epsilon;
}

但如果知道精度,直接将其乘以10^n变成整数来比较是最佳的方法

CPP原子操作

无锁队列

atomic

      void
      store(__pointer_type __p,
        memory_order __m = memory_order_seq_cst) noexcept
      { return _M_b.store(__p, __m); }

      void
      store(__pointer_type __p,
        memory_order __m = memory_order_seq_cst) volatile noexcept
      { return _M_b.store(__p, __m); }

      __pointer_type
      load(memory_order __m = memory_order_seq_cst) const noexcept
      { return _M_b.load(__m); }

      __pointer_type
      load(memory_order __m = memory_order_seq_cst) const volatile noexcept
      { return _M_b.load(__m); }

      __pointer_type
      exchange(__pointer_type __p,
           memory_order __m = memory_order_seq_cst) noexcept
      { return _M_b.exchange(__p, __m); }

cpp实现

atomic::compare_exchange_weak

memory_order

  • memory_order_relaxed
  • memory_order_seq_cst

cmake预处理头文件

预处理头文件是cmake 3.16添加的新功能,可以将头文件先

Precompiling header files can speed up compilation by creating a partially processed version of some header files, and then using that version during compilations rather than repeatedly parsing the original headers.

前言

记一次单元测试用例时失败,且出现奇怪的报错

tpp.c:63: __pthread_tpp_change_priority: Assertion `new_prio == -1 ||(new_prio >= __sched_fifo_min_prio && new_prio <=__sched_fifo_max_prio)' failed

原因

函数定义如下,原因是下面的第一个assert条件不成立,new_prio != -1。这个函数的注释说明了需要使用者确保调用前已初始化。

/* We only want to initialize __sched_fifo_min_prio and __sched_fifo_max_prio
   once.  The standard solution would be similar to pthread_once, but then
   readers would need to use an acquire fence.  In this specific case,
   initialization is comprised of just idempotent writes to two variables
   that have an initial value of -1.  Therefore, we can treat each variable as
   a separate, at-least-once initialized value.  This enables using just
   relaxed MO loads and stores, but requires that consumers check for
   initialization of each value that is to be used; see
   __pthread_tpp_change_priority for an example.
 */
int
__pthread_tpp_change_priority (int previous_prio, int new_prio)
{
  struct pthread *self = THREAD_SELF;
  struct priority_protection_data *tpp = THREAD_GETMEM (self, tpp);
  int fifo_min_prio = atomic_load_relaxed (&__sched_fifo_min_prio);
  int fifo_max_prio = atomic_load_relaxed (&__sched_fifo_max_prio);
  if (tpp == NULL)
    {
      /* See __init_sched_fifo_prio.  We need both the min and max prio,
         so need to check both, and run initialization if either one is
         not initialized.  The memory model's write-read coherence rule
         makes this work.  */
      if (fifo_min_prio == -1 || fifo_max_prio == -1)
        {
          __init_sched_fifo_prio ();
          fifo_min_prio = atomic_load_relaxed (&__sched_fifo_min_prio);
          fifo_max_prio = atomic_load_relaxed (&__sched_fifo_max_prio);
        }
      size_t size = sizeof *tpp;
      size += (fifo_max_prio - fifo_min_prio + 1)
              * sizeof (tpp->priomap[0]);
      tpp = calloc (size, 1);
      if (tpp == NULL)
        return ENOMEM;
      tpp->priomax = fifo_min_prio - 1;
      THREAD_SETMEM (self, tpp, tpp);
    }
  assert (new_prio == -1
          || (new_prio >= fifo_min_prio
              && new_prio <= fifo_max_prio));
  assert (previous_prio == -1
          || (previous_prio >= fifo_min_prio
              && previous_prio <= fifo_max_prio));

网上帖子的原因是给初始化mutex并设置RECURSIVE类型时,忘了对mutexattr进行初始化,导致使用了脏内存 即pthread_mutexattr_init(&mutexattr)

pthread_mutexattr_t mutexattr;

// Set the mutex as a recursive mutex
pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE_NP);

// create the mutex with the attributes set
pthread_mutex_init(&CritSec, &mutexattr);

// After initializing the mutex, the thread attribute can be destroyed
pthread_mutexattr_destroy(&mutexattr);

总之,要在线程启动前初始化好mutex

其他问题

这个问题其实是c++代码抛出来的,多线程执行了下面的代码,导致了上面的问题。

AppCache中有个小粒度的锁,但这个锁还未初始化就被其他线程去lock了,导致出现问题。所以要mutex最好要在线程启动前初始化

AppCache{
std::mutex
};
  auto app = m_app_cache.find(app_path);
  if (app == m_app_cache.end()) {
    // create a new monitor element
    m_app_cache.insert_or_assign(app_path, std::make_shared<AppCache>());
  }
  std::lock_guard<std::mutex> lk(app->second->cache_mutex);

引用

https://sourceware.org/legacy-ml/libc-help/2008-05/msg00072.html?utm_source=pocket_mylist