LINUX.ORG.RU

Числа с плавающей запятой. Откуда взялся минус и как его убрать?

 , , ,


0

3

Тестовый пример, показывающий суть явления:

#include <stdio.h>
int main()
{
    float right=-2.0f, left=2.0f; 
    float res = (right+left)/(right-left);
    printf("%5.12e\n", res);

    float test = -0.00000000000000424f;
    printf("%3.3f\n", test);
}

Результат:

-0.000000000000e+00
-0.000

Понятно, что после right-left знаковый бит становится 1. Но выглядит (когда выводишь на экран с %3.3f) так как будто есть где-то далеко после нулей числа, например -0.00000000000000424. А на самом деле их нет. Это ноль, только со знаком (IEEE754 это допускает). Но с математической точки зрения и при выводе матрицы нам хотелось бы видеть настоящий ноль (в математике он один) без знака. Кто-нибудь сталкивался с такой «проблемой»? Как красиво сделать в программе, чтобы ноль был единственным, без знака. Есть ли изящные решения? Просто не хочется чтобы в матрице было -0.000, это может сбить с толка того, кто будет смотреть вывод на экран или распечатку, по уже выше описанной причине.



Последнее исправление: Gyros (всего исправлений: 1)

Но с математической точки зрения…

С математической точки зрения придётся разработать тип, который будет хранить числа как, например, числитель и знаменатель и поддержвать операции между ними.
Но будет это очень неэффективно ни по памяти ни по производительности.

Либо заранее ограничиться фиксированной точностью, и опять разработать этот тип (или списать у C++) - будет лучше, чем в первом случае.

fixed для плюсов:
https://github.com/eteran/cpp-utilities/blob/master/fixed/include/cpp-utilities/fixed.h

her_s_gory
()
Ответ на: комментарий от her_s_gory

[quote] С математической точки зрения придётся разработать тип, который будет хранить числа как, например, числитель и знаменатель и поддержвать операции между ними. Но будет это очень неэффективно ни по памяти ни по производительности. [/quote]

Да, есть это давно в языках, еще со времен царя Гороха: хаскель, лисп, смолток, жабка, скала, котлин. С фиксированной точкой тоже есть встроенные типы в некоторых реализациях этих языков.

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

anonymous
()
Ответ на: комментарий от beastie

Супер! Спасибо! Вот это наверное самое лучшее решение. Добавить его в потенциально проблемных местах.

PS А что в комментарии в коде написать? Зачем прибавляю ноль? «Прибавляем ноль, чтобы не получить ноль со знаком минус» или оставить // turn -0.0 to +0.0

Gyros
() автор топика
Последнее исправление: Gyros (всего исправлений: 2)

Пардон, оффтоп, конечно. Но забавно по-моему.

=>SELECT 
         ((r+l)/(r-l))::float AS res_float
        ,((r::float+l)/(r-l)) AS r_float
        ,((r+l)/(r-l)) AS all_numeric
FROM
        (VALUES(-2.0, 2.0)) x(r,l)
;
 res_float | r_float |      all_numeric       
-----------+---------+------------------------
         0 |      -0 | 0.00000000000000000000
(1 строка)

На Си такой фокус не прошел.

Toxo2 ★★★★
()
Ответ на: комментарий от Gyros

Это уже на твоё усмотрение. Но какой-нибудь комментарий я бы оставил. А то придёт следующий оптимизатор через какое-то время и рационализирует этот маленький хак «за ненадобностью». ;)

beastie ★★★★★
()
Ответ на: комментарий от beastie

Вывод не меняется, то я для -O3 не вижу вычислений в ассемблерном коде, видимо все заранее посчитано на этапе компиляции.

Теоретически, тут +0.0 не гарантируется, может быть и -0.0. Надо взять пример, где вычисления для -0.0 компилятор не сделает на этапе компиляции.

PS:

видимо для float оптимизации нету, и ноль прибавляется, в целом это может быть даже логично, если есть несколько нулей по стандарту…

для int прибавление нуля компилятор выкидывает:

https://godbolt.org/z/b9E1TnY16

soomrack ★★★★★
()
Последнее исправление: soomrack (всего исправлений: 1)
Ответ на: комментарий от Gyros

Лучшее решение это проверка на эпсилон окрестность нуля, которую тебе посоветовали выше. А res = res + 0.0 это лютый говнокод, работающий только если компилятор не заоптимизирут этот вызов.

PRN
()
Ответ на: комментарий от LINUX-ORG-RU

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

#include <stdio.h>
#include <stdint.h>

float force_positive_only_zero(float f)
{
    union { float f; uint32_t i; }v = { f = f };
    v.i = ((v.i << 1) >> 1);
    if (v.i == 0) /* но не уверен тут может быть подвох? Вроде не должно*/
    {
       return v.f;
    }
    return f;
}

int main()
{
    float right=-2.0f, left=2.0f;
    float res = (right+left)/(right-left);

    res = force_positive_only_zero(res);

    printf("%5.12e \n", res);

    float test = -0.00000000000000424f;
    printf("%3.3f\n", test);
}
LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 2)

Я офигеваю с треда. Вроде на лор-е, вроде сишку и кресты тут любят, а знать не знают от слова совсем. Я на крестах писал в 2018 году последний раз. https://en.cppreference.com/w/c/numeric/math/fabs для разных типов разная функция

https://en.cppreference.com/w/cpp/numeric/math/fabs

Для крестиков

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 2)
Ответ на: комментарий от peregrine

Тут вишенька на торте в том, что из -3.141 надо элегантно оставить -3.141, +2.718 оставить как +2.718, не трогать +0.0, и только -0.0 починить в +0.0.

Т.ч. fabs тут немного мимо кассы.

Если уж совсем по феншую, то альтернативно можно и так:

float fixNegZero(float x) {
    return x == 0.0 && signbit(x) ? -x : x;
}

PS: -0.0 == 0.0, поэтому простым сравнением negZero не отловишь.

beastie ★★★★★
()
Последнее исправление: beastie (всего исправлений: 1)
Ответ на: комментарий от PRN

лютый говнокод, работающий только если компилятор не заоптимизирут этот вызов

Подозрение такое может возникнуть, да, но эксперимент говорит обратное.

Работает кстати и в Go. Другие языки ещё не проверял, но подозреваю, что и там тоже.

beastie ★★★★★
()
Последнее исправление: beastie (всего исправлений: 1)
Ответ на: комментарий от beastie

Вот определиться с epsilon которую считать 0 и незначимой погрешностью и всё что меньше этой epsilon обрабатывать fabs и его аналогами на счёт того что эта epsilon 0.0 я не был бы строго уверен на 100%.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 1)
Ответ на: комментарий от beastie

Зачем эти велосипеды?

Ради велафипедав!

signbit и copysign же!

А ему всё равно надо проверять там реально ноль, или не нереально ноль. Ведь если он знает что где то реально ноль, то почему просто не вписать константу в виде нуля? Это я к тому что нужно раз уж так пошло на вывод все данные прогонять через типа такой фильтр который выявит именно отрицательный ноль и уберёт у него знак, а всё остальное пропустит как есть. А так как для 100% уверенности что это реально ноль надо привести к 32 битному инту и глянуть там реально всё по нулям или нет то вызов этих функций будет оверхедом (хотя не факт, может вызывать эти функции будет дешевле так как там наверняка продумали всё и gcc сменит знаковый бит максимально эффективно) так как у нас уже всё под рукой, дрыгни биты влево/вправо и готово.

Вот поэтому вот так. Конечно может я перемудрил. Но мне кажется не перемудрил. А так, прикольно же! Ну и ладно :)

Буду рад словить помидор в лоб если реально что-то не так. Поругай, я только за! Может что упускаю

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 2)
Ответ на: комментарий от anonymous

Бред предлагать рациональные числа для этого!

Человек акцентируется на «математически точно». Я ему показал что это такое, дабы мотивировать на понимание того, когда какя точность и когда нужна и сколько это стоит.

her_s_gory
()
Ответ на: комментарий от beastie

лайк, подписка.

но я бы на месте тс не парился, так как практической разницы между отрицательным и положительным нулём нет.

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

anonymous
()
Ответ на: комментарий от beastie

эксперимент говорит обратное

В «эксперементе» по сути все является константым хардкодом. И ничто не мешает компилятору вычислить все во время компиляции. Нужно переменные хотябы волотайлом объявить, это убъет вычисления в момент компиляции. К тому же оптимизации для вычислений не выставлены. Эксперимент под стать коду, такой же кусок г*вна)))

PRN
()
Ответ на: комментарий от peregrine

Вот определиться с epsilon которую считать 0 и незначимой погрешностью и всё что меньше этой epsilon обрабатывать fabs и его аналогами на счёт того что эта epsilon 0.0 я не был бы строго уверен на 100%.

А std::numeric_limits<T>::epsilon не?

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от hateyoufeel

Как вариант. Хотя на самом деле зависит от задачи эпсилон, т.к. незначительная величина на самом деле может быть и большой в абсолютных числах. В физике вообще зачастую 2 или 3 знака после запятой достаточно. Тут больше от сходимости решения зависит. От него зависит идёт ли накопление ошибки или нет.

peregrine ★★★★★
()
Ответ на: комментарий от beastie

Самоуправства компиляторов не заметил. Работает.

Поправил твои экскремен… эксперементы с учетом комментов:

#include <stdio.h>

int main() {
        volatile float right = -2.0, left = 2.0;
        float res = (right + left) / (right - left);
        printf("%5.12e\n", res);
        res = res + 0.0;
        printf("%5.12e\n", res);
	return 0;
}
gcc -O1 -ffast-math main.cpp
./a.out
-0.000000000000e+00
-0.000000000000e+00

Не работает.

PRN
()
Ответ на: комментарий от PRN

Ну да. Дай дураку стеклянный х*й, он и его разобьёт, и руки порежит.

-ffast-math does a lot more than just break strict IEEE compliance.

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

PS: бородатая поговорка 30+ летней давности: те кто используют не-умолчальные опции gcc – сами себе Злобрые Буратино. Ибо багов там… чудо что вообще что-то кое-как работает.

PPS: в общем, что я хотел сказать: с --ffast-math мы покидаем царство IEEE. Там тогда возможно уже всё что угодно.

beastie ★★★★★
()
Последнее исправление: beastie (всего исправлений: 6)
Ответ на: комментарий от beastie

Ну да. Дай дураку стеклянный х*й, он и его разобьёт, и руки порежит.

Это самая умная вещь, которую ты ляпнул в этом треде)). Хоть и с ошибками. Действительно, держись от программирования подальше, а то руки порежешь.

те кто используют не-умолчальные опции gcc – сами себе Злобрые Буратино. Ибо багов там… чудо что вообще что-то кое-как работает.

Ага, опцию -Ox тоже выставлять не надо (а она не дефолтная), а то багов там… Вроде что-то умное начал писать, а тут опять какой-то бред пошел.

Там тогда возможно уже всё что угодно.

О чем тебе и писали, у тебя нет никаких гарантий работоспособности твоего г*внокода.

PRN
()
Ответ на: комментарий от PRN

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

В частности для этого всякие юнит и регрешион тесты и придумали.

Ага, опцию -Ox тоже выставлять не надо

С -O2 собирается достаточно кода, что бы там большинство багов уже выловили. За пределами начинается уже минное поле.

у тебя нет никаких гарантий работоспособности твоего г*внокода.

Г*мноумничаешь как раз тут ты. Про гарантии кроме тебе никто, кроме тебя, и не заикался.

beastie ★★★★★
()
Ответ на: комментарий от beastie

Не понятно, что ты пытаешься доказать

Мне ничего не надо доказывать, тем более кому-то что-то в интернете).

То что тебе никто никогда никаких гарантий не давал?

Я описываю, почему твой код в общем случае не работает. И причина - это совсем не баги в компиляторе.

За пределами начинается уже минное поле.

Баги в компиляторах конечно есть, но до минного поля там далеко. Плохо будет только если писать всякое г*внище в стиле res = res + 0.0; без осознания того, что это может заоптимизировать компилятор, процессор посчитать не так как планировал автор и хз еще что. анонимус уже осознал работу компилятора, но еще не дошел до Rounding-direction в IEEE и не понял, что его «фикс» тоже не работает в общем случае. Пожелаем ему удачи в его пути XD

Про гарантии кроме тебе никто, кроме тебя, и не заикался.

Весь тред не читал, но мне уже достаточно))

UP: И твой подход «я что-то наэкскрементировал наэксперементировал - у меня работет» может работать в питоне или где-то еще. Но в C/C++ это не работает от слова совсем.

PRN
()
Последнее исправление: PRN (всего исправлений: 1)
Ответ на: комментарий от PRN

не дошел до Rounding-direction в IEEE и не понял, что его «фикс» тоже не работает в общем случае.

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

anonymous
()