[arm] Почему nextafterf(0.0f, 1.0f) == 0.0f?
От: Went  
Дата: 19.11.21 09:59
Оценка: 3 (1)
Здравствуйте. Есть такой код:
float next = nextafterf(0.0f, 1.0f);
bool crazy = next == 0.0f;

На всех платформах, с которыми я имел дело ранее, равенство ожидаемо было ложным. Но когда я собираю свежим XCode на довольно древний iPad (arm v7, по-моему), то здесь это сравнение даёт истину! В чем ошибка? Может, какие-то опции компиляции нужно покрутить? Дебаг-конфигурация, оптимизации отключены. Или число 1.40129846E-45 какое-то неправильное и процессор имеет право трактовать его как угодно?
Re: [arm] Почему nextafterf(0.0f, 1.0f) == 0.0f?
От: Максим Россия  
Дата: 19.11.21 10:14
Оценка: 2 (1)
W>На всех платформах, с которыми я имел дело ранее, равенство ожидаемо было ложным. Но когда я собираю свежим XCode на довольно древний iPad (arm v7, по-моему), то здесь это сравнение даёт истину! В чем ошибка? Может, какие-то опции компиляции нужно покрутить? Дебаг-конфигурация, оптимизации отключены. Или число 1.40129846E-45 какое-то неправильное и процессор имеет право трактовать его как угодно?

А что если попробовать проверить ошибки через https://en.cppreference.com/w/c/numeric/math/math_errhandling ? Может вылезет чего...
Errare humanum est
Re: [arm] Почему nextafterf(0.0f, 1.0f) == 0.0f?
От: Zhendos  
Дата: 19.11.21 10:19
Оценка: 12 (2)
Здравствуйте, Went, Вы писали:

W>Здравствуйте. Есть такой код:

W>
W>float next = nextafterf(0.0f, 1.0f);
W>bool crazy = next == 0.0f;
W>

W>На всех платформах, с которыми я имел дело ранее, равенство ожидаемо было ложным. Но когда я собираю свежим XCode на довольно древний iPad (arm v7, по-моему), то здесь это сравнение даёт истину! В чем ошибка? Может, какие-то опции компиляции нужно покрутить? Дебаг-конфигурация, оптимизации отключены. Или число 1.40129846E-45 какое-то неправильное и процессор имеет право трактовать его как угодно?

Если взять такой код и собрать его с помощью clang/gcc с флагом -ffast-math,
то напечатает 'crazy 1'
#include <cstdio>
#include <cmath>

int main() 
{
    float next = nextafterf(0.0f, 1.0f);
    bool crazy = next == 0.0f;
    printf("crazy %d\n", crazy);
    return 0;
}
Re[2]: [arm] Почему nextafterf(0.0f, 1.0f) == 0.0f?
От: Максим Россия  
Дата: 19.11.21 10:34
Оценка:
Z>Если взять такой код и собрать его с помощью clang/gcc с флагом -ffast-math,


Да, очень похоже, что ffast-math включает режим "трюкачи"

https://stackoverflow.com/questions/7420665/what-does-gccs-ffast-math-actually-do

Further, it disables signed zero (code assumes signed zero does not exist, even if the target supports it) and rounding math, which enables among other things constant folding at compile-time.

Errare humanum est
Re[2]: [arm] Почему nextafterf(0.0f, 1.0f) == 0.0f?
От: Went  
Дата: 19.11.21 11:31
Оценка:
Здравствуйте, Zhendos, Вы писали:
Z>Если взять такой код и собрать его с помощью clang/gcc с флагом -ffast-math,
Но никаких таких флагов я не устанавливал... В XCode есть такая настройка Relax IEEE Compliance, которая вроде бы этим и заведует, обозначается как GCC_FAST_MATH, но она стоит No. Так же, как и любые оптимизации вообще. Это Debug-сборка. Если кто-то понимает в ARM-ассемблере, могу скинуть получающийся из кода:
  float next = nextafterf(0.0f, 1.0f);
  bool crazy = next == 0.0f;

ассемблер:
    0x2f63ac <+256>: vmov   r0, s0
    0x2f63b0 <+260>: vmov.f32 s0, #1.000000e+00
    0x2f63b4 <+264>: vmov   r1, s0
    0x2f63b8 <+268>: blx    0x11e424c                 ; symbol stub for: nextafterf
    0x2f63bc <+272>: ldr    r1, [sp, #0x14]
    0x2f63be <+274>: vmov   s0, r0
    0x2f63c2 <+278>: vstr   s0, [sp, #28]
    0x2f63c6 <+282>: vldr   s0, [sp, #28]
    0x2f63ca <+286>: vcmp.f32 s0, #0
    0x2f63ce <+290>: vmrs   APSR_nzcv, fpscr
    0x2f63d2 <+294>: movw   r0, #0x0
    0x2f63d6 <+298>: it     eq
    0x2f63d8 <+300>: moveq  r0, #0x1
    0x2f63da <+302>: and    r0, r0, #0x1
    0x2f63de <+306>: strb.w r0, [sp, #0x1b]
Re: [arm] Почему nextafterf(0.0f, 1.0f) == 0.0f?
От: Went  
Дата: 19.11.21 12:08
Оценка:
Здравствуйте.
Поразбиравшись далее, я выяснил, что результат выполнения nextafterf(0.0f, 1.0f) (число 1.40129846E-45) — денормализированное, и каждая конкретная аппаратная реализация плавающих чисел может поддерживать, а может и не поддерживать корректную работу с ними. Отсюда возникает вопрос: что курили разработчики iOS, когда написали реализацию функции, которая явно, в простейшем случае, возвращает число, с которым их процессор работает некорректно?
Re[2]: [arm] Почему nextafterf(0.0f, 1.0f) == 0.0f?
От: Went  
Дата: 19.11.21 12:32
Оценка:
Здравствуйте снова.
По-моему, дело в том, что ARM использует математический сопроцессор NEON, который просто срезает все денормализированные числа в ноль. Чудненько.
Re[3]: [arm] Почему nextafterf(0.0f, 1.0f) == 0.0f?
От: Zhendos  
Дата: 19.11.21 13:01
Оценка: 4 (1) +1
Здравствуйте, Went, Вы писали:

W>Здравствуйте снова.

W>По-моему, дело в том, что ARM использует математический сопроцессор NEON, который просто срезает все денормализированные числа в ноль. Чудненько.

Возможно кто-то выставляет флаг "flushing denormals/underflow to zero"?

Например с gcc/linux/amd64 вот так работает и без -ffast-math:

#include <cstdio>
#include <cmath>
#include <pmmintrin.h>

int main() 
{
    auto mxcsr = _mm_getcsr();
    // Denormals & underflows are flushed to zero
    mxcsr |= (1 << 15) | (1 << 6);
    // All exceptions are masked
    mxcsr |= ((1 << 6) - 1) << 7;
    _mm_setcsr(mxcsr);

    float next = nextafterf(0.0f, 1.0f);
    bool crazy = next == 0.0f;
    printf("crazy %d\n", crazy);
    return 0;
}


Это конечно для amd64, но для ARM есть https://developer.arm.com/documentation/dui0473/c/neon-and-vfp-programming/fpscr--the-floating-point-status-and-control-register

И там тоже есть подобный регистр, достаточно кому-то в него что-то записать и все будет печально.
Re[4]: [arm] Почему nextafterf(0.0f, 1.0f) == 0.0f?
От: Went  
Дата: 19.11.21 14:10
Оценка:
Здравствуйте, Zhendos, Вы писали:

Z>Возможно кто-то выставляет флаг "flushing denormals/underflow to zero"?

Z>И там тоже есть подобный регистр, достаточно кому-то в него что-то записать и все будет печально.
Да, именно так. Но тут я уже ничего изменить не смогу. Видимо, нужно делать свою реализацию nextafterf, которая в случае ARM проверяет аргумент на ноль и вместо денормализированого числа возвращает std::numeric_limits<float>::min(). Или, вообще, делать так для всех архитектур?
Re[5]: [arm] Почему nextafterf(0.0f, 1.0f) == 0.0f?
От: Zhendos  
Дата: 19.11.21 15:53
Оценка: 4 (1)
Здравствуйте, Went, Вы писали:

Z>>И там тоже есть подобный регистр, достаточно кому-то в него что-то записать и все будет печально.

W>Да, именно так. Но тут я уже ничего изменить не смогу. Видимо, нужно делать свою реализацию nextafterf, которая в случае ARM проверяет аргумент на ноль и вместо денормализированого числа возвращает std::numeric_limits<float>::min(). Или, вообще, делать так для всех архитектур?

А почему просто не вызвать "DisableFZ" в начале работы программы?

https://stackoverflow.com/questions/7346521/subnormal-ieee-754-floating-point-numbers-support-on-ios-arm-devices-iphone-4
Re[6]: [arm] Почему nextafterf(0.0f, 1.0f) == 0.0f?
От: Went  
Дата: 19.11.21 16:29
Оценка:
Здравствуйте, Zhendos, Вы писали:
Z>А почему просто не вызвать "DisableFZ" в начале работы программы?
Ну, во-первых, могу ли я быть уверен, что его потом кто-то не включит внезапно? А во-вторых, не приведет ли это к просадкам производительности, ведь мне эти все денормалы, по сути, не нужны? Мне лишь нужна гарантия, что nextafterf(x) > x.

Z>https://stackoverflow.com/questions/7346521/subnormal-ieee-754-floating-point-numbers-support-on-ios-arm-devices-iphone-4

Спасибо за наводку!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.