Обновить
3
16
Сергей Леонтьев@Serge3leo

Пользователь

Отправить сообщение

P.S.

Что касается cmp_ulp() и cmp_nulp(), то они, как и родительский тип с плавающей точкой, обладают частичной транзитивностью. Т.е. еслиstd:is_lteq(cmp_ulp(a, b)) и std::is_lteq(cmp_ulp(b, c)), то std::is_lteq(cmp_ulp(a, c)).

Строго говоря, по сути, все варианты:

  • округлить, потом сравнить;

  • сравнить, с заданной точностью;

  • просто сравнить.

Имеют одинаковые свойства частичного порядка, но немного разные области применения. 😉

Хм. У чисел c плавающей точкой (по ISO/IEC 60559 или иных), по жизни, частичный порядок (к примеру, по С++ это std::partial_ordering). Они, и по жизни, и по определению, иные. 😉

Один из примеров, скажем, не только одинаковые элементы могут не иметь отношения равенства, но и наоборот, различные элементы могут равняться друг другу ( https://godbolt.org/z/Y8c541T33 ):

    double a = 0.;
    double b = -0.;
    assert(a == b);
    assert(!(1./a == 1./b));
    double c = NAN;
    assert(!(c == c));

Что уж говорить за < и >? Там ещё забавнее. 😉

P.S.

По-моему, fuzzy_float_eq(INFINITY, INFINITY) тоже не очень, но в остальном - более менее. Хотя, конечно, формализма abs и tol не хватает.

Кроме того, есть такие D, которые D != D. Так и да, частичный порядок

Во-первых, для всех широко известных систем: 1.0000009901f == 1.0000009902f
https://godbolt.org/z/3YxWzrTEr

Систему с FLT_DECIMAL_DIG >= 10 найти будет затруднительно, так что, пример, не очень (некоторые его назовут, в некотором смысле, ошибочным или вводящим в заблуждение 😉).

Во-вторых, можете добавить в свою в копилку алгоритмов:

// Сравнение чисел с точностью 1 младший разряд (ε), грубо говоря:
// 0: |a - b| <= ε
// 1: a > b + ε
// -1: a < b - ε
// unordered: для NaN
template <class F>
constexpr auto cmp_ulp(F a, F b) {
	return std::nextafter(a, b) <=> b;
}
// Сравнение чисел с точностью nulp младших разрядов (ε)
template <class F>
constexpr auto cmp_nulp(F a, F b, unsigned nulp) {
	F x = a;
	while (nulp--) {
		x = std::nextafter(x, b)
		// x ∈ [a, b] и |x - b| уменьшилось на ε*x, если ещё не было равно 0
	}
	return x <=> b;
}

Наиболее популярные сценарии того, что раньше формально было UB, которое теперь уже точно не UB:

void s_zero(str_t *s, size_t n) {
  s->len = n;
  s->ptr = malloc(s->len);  // Для n == 0 может возвращать NULL
  memset(s->ptr, 0, s->len);
}
void s_str(str_t *s, const char *str) {
  s->len = strlen(str);
  s->ptr = malloc(s->len);  // Для s->len == 0 может возвращать NULL
  memcpy(s->ptr, str, s->len);
}

Раньше в преамбуле стандарта языка C, грубо говоря, было сказано: "аргумент библиотечной функции NULL - UB". Но, по сути, если он не использовался - это не UB. Поэтому в C23 добавили разъяснение того, что имелось ввиду, в сноске.

С делением на 0 аналогично, если его результат не влияет на выполнение программы, то это не UB (наверное, не уверен, неточно).

Кроме того, у компиляторов свои тараканы. К примеру, Clang:

struct {} a[1];  // В расширении языка C, 0 == sizeof(a)
// В этой строке - всё чисто
size_t la = (sizeof(a[0]) ? sizeof(a)/sizeof(a[0]) : 0);
// А в такой - ошибка xyz и предупреждение о делении на 0
size_t la = (sizeof(a[0]) ? sizeof(a)/sizeof(a[0]) : xyz);

...почему компилятор в компил тайме не может проверить на что деление.

В первом случае, может. И даже, некоторые, если попросят, могут выдать ошибку. Однако, не всегда можно выяснить, используется ли переменная foo реально. Если не используется, то это, вроде как, и не UB.

Кроме того, как известно, "Не спеши выполнить команду, ибо вскоре последует команда отставить!".

К примеру, memset(NULL, 0, 0) - это UB или нет? Долгое время, считалось, что формально - UB, правда, всем было по барабану. Но не так давно, как только кто-то реально попытался это интерпретировать не формально, а реально как UB. Тут же, в C23 вышло разъяснение: "...or using any valid pointer with a size of 0". Говорят, с указанием, что уточнение имеет обратную силу. 😉

P.S.

Как говорит, один мой мудрый товарищ: "Не надо мне связывать руки. Это бесполезно, я всё равно умнее всех этих компиляторов, к тому же и теорема Тьюринга на моей стороне."

Честно говоря, я сравнивал интерфейсы стандарта C++. Грубо говоря, стандарт у нас один, а разных компиляторов или инструментов много.

Что касается, конкретно, ThreadSanitizer, вроде как, он поддерживает обнаружение проблем в том числе исходя из первых принципов, т.е. исходя из отношения Happens Before. Но, как у всякого инструмента, есть сложности, ограничения и прочие несовершенства. Но на нём Свет клином не сошёлся же.

А так, да, для блокировок (мьютексы, спин-локи) есть более простые и быстрые алгоритмы. А всё, что связано с wait()/notify_all() немного сложнее в настройке и несколько медленнее, и не важно это std::atomic или std::condition_variable .

P.S.

Хотя std::atomic, да, возможно сложнее инструментировать и больше проблем с разнообразием компиляторов и/или библиотек C++.

зачем-то дали аж в стандарте wait/notify

Чисто по интерфейсу, std::condition_variable в паре, скажем, с std::mutex:

  • Может использоваться только в одном адресном пространстве;

  • В описании интерфейса явно указывается на блокировку потока;

  • UB, в случае завершения потока владельца std::mutex без его освобождения;

  • Размеры не специфицированы;

  • Имеют методы native_handle().

std::atomic:

  • При наличии макроса ATOMIC_..._LOCK_FREE может использоваться в памяти, отображаемой в пространство нескольких процессов, смотрите [atomic.lockfree] (5);

  • В описании интерфейса есть только слова, что более эффективно, чем простой опрос;

  • Формально, при завершении потока или процесса, без освобождения, нет UB, но нет и никаких гарантий, как, скажем, при использовании PTHREAD_PROCESS_SHARED и ошибки EOWNERDEAD;

  • Рекомендованный размер совпадает с размером базового типа, но это лишь рекомендация;

В целом, std::atomic может быть реализован, к примеру, на основе инструкций MONITOR/MWAIT или чего-то подобного, если они доступны.

Это к тому, что анализ используемой в тесте C++ библиотеки и компилятора излишне краток.

Честно говоря, так себе ответ. К примеру:

int foo = 1/0;

UB, часто, вызывает предупреждение компилятора, но при использовании ключей вида -Werror=div-by-zero может вызвать ошибку компиляции.

volatile int boo = 0;
int baz = 1/boo;

Тоже UB, вероятно, если какое-либо НЛО не изменит boo.

Но если первый вариант UB, ещё где-то, как-то присутствует в ответе "chatgpt", то второй вариант даже и не упомянут.

...Это не “дыра по недосмотру”, а часть дизайна языка...

Тоже ошибка, вызваная наивным прекраснодушием. К примеру, из предполагаемых нововведений C2Y: N3349: Abs Without Undefined Behavior. Что-бы хотя бы создать возможность устранения UB надо потрудиться. И таких поправок за тридцать с лишним лет, накопилось... Ни вагонов, ни тележек, не хватит.

...либо обязательных runtime-проверок...

Тоже так себе. Дело не в проверке, а в возможности/невозможности/отсутствия необходимости определить гарантированное состояние на момент ошибки. На примере деления на 0: как именно проистекает ошибка, какие операции с памятью/потоками выполнятся, а какие нет и т.п.

В общем, ИМХО, это не ответ, а иллюзия ответа. Наверное, тех, кто им недоволен, понять можно.

Хм, я точно не проверял, но на первый взгляд кажется, что до Glibc 2.34 (2021-08-01) это было бы непросто.

Колебания вызваны свойствами матрицы Вандермонда

Строго говоря, есть свойства идеальной матрицы Вандермонда и свойства округлённой до заданной точности матрицы Вандермонда, и они отличаются. Отдельный вопрос - сетка, который тоже имеет много гититк, ну пусть останется равномерной:

from mpmath import mp
import matplotlib.pyplot as plt
%config InlineBackend.figure_format='retina'

def vandermonde_matrix(n):
    A = mp.zeros(n+1)
    for i in range(n+1):
        for j in range(n+1):
            A[i, j] = (mp.mpf(i)/mp.mpf(n))**j if n else 1
    return A

fig, ax1 = plt.subplots(nrows=1)

for prec, hiprec in (24, 113), (53, 237), (113, 489):
    mp.prec = prec
    cond_vandermonde = mp.zeros(1, 101)
    cond_vandermonde_hi = mp.zeros(1, 101)
    rng = range(1, len(cond_vandermonde))
    for i in rng:
        mp.prec = prec
        dps = mp.dps
        matrix = vandermonde_matrix(i)
        S = mp.svd_r(matrix, compute_uv = False)
        cond_vandermonde[i]=max(S)/min(S)
        mp.prec = hiprec
        hidps = mp.dps
        S = mp.svd_r(matrix, compute_uv = False)
        cond_vandermonde_hi[i]=max(S)/min(S)

    ax1.semilogy(rng, cond_vandermonde[1:],
                 label=f"vm={dps} cnd={dps}")
    ax1.semilogy(rng, cond_vandermonde_hi[1:],
                 label=f"vm={dps} cnd={hidps}")

ax1.set_title('''Зависимость числа обусловленности от n и от
точности представления матрицы Вандермонда (vm)''')
ax1.set_ylabel('Число обусловленности')
ax1.legend()
ax1.set_xlabel('n')
plt.show()

ошибок в вычислениях нет.

Конечно же, есть. Во-первых, метод вычисления числа обусловленности выбран не самый точный, в NumPy есть быстрая функция np.linalg.cond(matrix), а есть более точное разложение np.linalg.svd(matrix, compute_uv=False).

Легко видеть, что число обусловленности идеальной матрицы Вандермонда неограниченно растёт по одному закону, округленной матрицы Вандермонда тоже растёт неограниченно, но по другому закону точка перегиба: \frac{1}{\epsilon}. Ну, а вычисленная оценка числа обусловленности ограничена точностью её вычисления.

Попробуйте поменять тип данных в матрице на np.float32 (по умолчанию там float64).

Это да, точность входной матрицы влияет.

Точности вещественных числел тупо не хватает для вычисления обратной матрицы.

А в этом деле, тест с использованием np.float32 практически бесполезен, поскольку NumPy всё равно накапливает скалярное произведение на np.float64.

import numpy as np
import matplotlib.pyplot as plt

def vandermonde_matrix(n, dtype=float, htype=np.longdouble):
    A = np.zeros((n+1,n+1), dtype=dtype)
    for i in range(n+1):
        for j in range(n+1):
            A[i][j] = (htype(i)/htype(n))**htype(j) if n != 0 else 1
    return A

fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)

for dt in np.float32, np.float64:
    for ht in np.float32, np.longdouble:
        cond_vandermonde = np.zeros(101, dtype=dt)
        underflow = np.zeros_like(cond_vandermonde)
        rng = range(1, len(cond_vandermonde))
        for i in rng:
            matrix = vandermonde_matrix(i, dtype=dt, htype=ht)
            #cond_vandermonde[i] = np.linalg.cond(matrix)
            U, S, V = np.linalg.svd(matrix)
            cond_vandermonde[i]=np.max(S)/np.min(S)
            #np.testing.assert_approx_equal(np.linalg.cond(U), 1.0)
            #np.testing.assert_approx_equal(np.linalg.cond(V), 1.0)
            #np.testing.assert_approx_equal(np.max(S)/np.min(S),
            #                               np.linalg.cond(np.diag(S)))
            #if cond_vandermonde[i] < 1/np.finfo(dt).eps:
            #    np.testing.assert_approx_equal(np.max(S)/np.min(S),
            #                                   np.linalg.cond(matrix))
            underflow[i] = (i + 1)**2 - i - np.count_nonzero(matrix)
        ax1.semilogy(rng, cond_vandermonde[1:],
                     label=f"m={np.finfo(ht).precision} c={np.finfo(dt).precision}")
        ax2.plot(rng, underflow[1:],
                 label=f"m={np.finfo(ht).precision} c={np.finfo(dt).precision}")

ax1.set_title('Зависимость числа обусловленности от n')
ax1.set_ylabel('Число Обусловленности')
ax1.legend()
ax2.set_ylabel('Число антипереполнений')
ax2.set_xlabel('n')
ax2.legend()
plt.show()

придется писать руками методом Гаусса, да и вычисление нормы тоже.

Я Вас умоляю...

from mpmath import mp
import matplotlib.pyplot as plt

def vandermonde_matrix(n):
    A = mp.zeros(n+1)
    for i in range(n+1):
        for j in range(n+1):
            A[i, j] = (mp.mpf(i)/mp.mpf(n))**j if n else 1
    return A

fig, ax1 = plt.subplots(nrows=1)

for dps in 6, 15, 33:
    mp.dps = dps
    cond_vandermonde = mp.zeros(1, 61)
    rng = range(1, len(cond_vandermonde))
    for i in rng:
        matrix = vandermonde_matrix(i)
        S = mp.svd_r(matrix, compute_uv = False)
        cond_vandermonde[i]=max(S)/min(S)

    ax1.semilogy(rng, cond_vandermonde[1:],
                 label=f"dps={S[0].context.dps}")

ax1.set_title('Зависимость числа обусловленности от n')
ax1.set_ylabel('Число Обусловленности')
ax1.legend()
ax1.set_xlabel('n')
plt.show()

Погуглите свойства этой самой матрицы. Уверен, для числа обусловленности есть какая-нибудь аж замкнутая формула.

Как бы, есть, только она всё равно от сетки зависит. Впрочем, для данной конкретной сетки, на графике и так всё видно.

Интересно, есть ли тому объяснение, что precomit.sh должен начинаться не с обычного шебанг #!, а с какого-нибудь непустого, к примеру, с эквивалента пустого шебанга: #!/usr/bin/env sh ?

Замечательно, после установки окружения по вашим рецептам, `cmake`/`ctest` отработали как положено со следующими ключами:

cmake -DCMAKE_C_COMPILER=lcc -DCMAKE_CXX_COMPILER=l++ \
      -DCMAKE_CROSSCOMPILING=ON -DCMAKE_SYSTEM_NAME=Generic-ELF \
      -DCMAKE_CROSSCOMPILING_EMULATOR=e2k \
      ../..
make
ctest .

Все тесты собрались, в количестве, запустились, коды возврата и выдача проверилась, хорь, и это неплохо.

#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L

Прошу прощения, но режет глаз. Разрешите напомнить современное, более менее строгое, определение, параграф 13 раздел 6.10.1 Conditional inclusion, C23:

...After all replacements due to macro expansion and evaluations of defined macro expressions, has_include expressions, has_embed expressions, and has_c_attribute expressions have been performed, all remaining identifiers other than true (including those lexically identical to keywords such as false) are replaced with the pp-number 0, true is replaced with pp-number 1, and then each preprocessing token is converted into a token...

И так было всегда, например, параграф 6 (примерно) раздел 3.8.1 Conditional inclusion, C88 Third review X3J11 88-090 May 13 1988:

...After all replacements are finished, the resulting preprocessing tokens are converted into tokens, and then all remaining identifiers are replaced with 0 ...

В первой редакции книги K&R, вроде как, это свойство забыли упомянуть, но аксакалы помнят, помнят, как работал их компилятор.

Правильно писать коротко и ясно:

#if __STDC_VERSION__ >= 202311L
    /* C23 compatible source code. */
#elif __STDC_VERSION__ >= 201710L
    /* C17 compatible source code. */
#elif __STDC_VERSION__ >= 201112L
    /* C11 compatible source code. */
#elif __STDC_VERSION__ >= 199901L
    /* C99 compatible source code. */
#elif __STDC_VERSION__ >= 199409L
    /* C95 compatible source code. */
#elif __STDC__
    /* C89 compatible source code. */
#else
    /* K&R C compatible source code. */
#endif

Это как посмотреть, код if (index < 0 || index >= forecast.Forecast.Count) проблемный - не работает с NaN, а если написать if (0 <= index && index < forecast.Forecast.Count), то при работе по IEC 60559, всё будет корректно.

А так, да, конечно, лучше, когда всё вместе, и интерфейс надёжный, и код надёжный. Хотя, NaN может просочиться и через интерфейс без деления, например, ввиду недостатка данных.

... хранить... бояться... потерять...

Честно говоря, модель Mail.Ru (Яндекс, и др.) не предусматривает такового "хранения", переустановка или новое устройство - новый ключ. А "хранение" - это лишнее, необязательное и небезопасное действие.

Впрочем, если, лично Вам, оно нравится и у Вас есть специальный шифрблокнот для этого, почему нет?

Как что? Так сразу правительство. Берите выше.

Мне кажется, данные вообще нерелевантные ни для какого вывода, кроме "кто лежит на одном из кладбищ в одном из небольших городков"...

На первом году жизни умирало очень много людей из нашей выборки, на втором тоже нмало. Но дальше цифры вполне равномерные, не кажется?

Возрастные коэффициенты смертности мирных лет (1896-1897), смертей на 1000 данного возраста:

0.: 370.0 (от рождения до года)
0-1.: 132.5 (от рождения до двух лет)
5-9.: 11.9
10-14.: 5.4
15-19.: 5.8
20-24.: 7.5
25-29.: 8.1
30-34.: 8.7
35-39.: 10.3
40-44.: 11.8
45-49.: 15.6
50-54.: 18.5
55-59.: 29.4
60-64.: 34.4
65-74.: 64.5
75-...: 111.4

А здесь война. Надо только правильно сообразить, как эти числа преобразовать, что бы можно было бы их сравнить. Думаю, видно не только детскую смертность, но и войну тоже.

1
23 ...

Информация

В рейтинге
478-й
Откуда
Москва, Москва и Московская обл., Россия
Дата рождения
Зарегистрирован
Активность

Специализация

Инженер по производительности, Архитектор программного обеспечения
Старший
От 600 000 ₽
C
C++
C++ boost
Оптимизация кода
Unix
Git
Shell