LINUX.ORG.RU

Почему C++ не может без потери данных сдвинуть запятую во float типе данных?

 ,


2

2

Привет всем!

Столкнулся с проблемой, простейшее умножение числа 0.56 на 10.0 не даёт точного результата. C++ просто не в состоянии перенести знак справа налево когда я хочу перенести разряд. Но при этом, 0.56 * 100.0 даёт точный ответ, точное число 56.0! Lol! ))))

Многие ответят скорее всего, что - «округли, да и всё!». Нет, округление не подходит, так как задача выполняется не в традиционных языках программирования, а в нодовой системе шейдеров Blender где ноды математики полагаются полностью на логику C++ в отношении математики и я не могу ничего с этим поделать кроме того, что доступно из математики в Блендер.

Да, в нодах есть операции округления, но мне это не подходит, потому что мне нужно получать из большого целого числа отдельные части разрядов, т.е. из числа 12345 получать отдельно число 1,2,3,4 и 5. При этом у меня нет никаких переменных, циклов и т.д.как в традиционных языках программирования. Есть только нодовый поток. Я научился это делать умножением и делением, получать отдельные разряды в нодовом потоке, но столкнулся со странной математикой в C++ на которые эти ноды опираются (полагаются).

Почему C++ не может просто сдвинуть запятую справа налево при умножении на 10, а при умножении на 100 может? Это баг какой-то или фича?

В других языках, которых я немного знаю, Java и Python (да, я понимаю, что это интерпретируемые языки) такого нет, результат всегда ожидаемый: 0.56 * 10.0 = 5.6 - P.S. Как оказалось - нет, см. комметарии.

https://godbolt.org/z/ErnbfePhf



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

А целочесленные типы там есть? И преобразования из чисел с плавающей точкой в целочисленные?

rumgot ★★★★★
()
>>> a = 0.56
>>> print("{:.{}f}".format(a, 16))
0.5600000000000001

>>> a == 0.56
True

Когда константу 0.56 ввели, Python запишет это как 0.5600000000000001, поэтому сравнение работает, то есть по сути происходит вот что:

a == 0.5600000000000001

И поэтому здесь True, а здесь False:

>>> a * 10
5.6000000000000005

>>> b = 5.6
>>> print("{:.{}f}".format(b, 16))
5.5999999999999996

>>> a * 10
5.6000000000000005

>>> a * 10 == b
False

Вот такая вот «математика» по итогу. Умножением получаешь 5.6, а вводом константы - НЕТ, 5.6 не получаешь! Получаешь МЕНЬШЕЕ ЧИСЛО - 5.5999999999999996, меньше чем 5.6000000000000005! В C++ как выяснилось похожее происходит, но там немного другие результаты.

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

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

dva20
() автор топика
Ответ на: комментарий от izzholtik

Ну во-первых это круто. Накинул материал и покрутил слайдером который выдаст значения. Я так реализовал Часы и Табло. Когда крутишь слайдер в NodeGroup, который как понимаешь можно анимировать, то шейдер преобразует минуты в часы, учитывается формат 24-х или 12 часов на выбор и т.д. Т.е. вся логика часов реализована. Это работает динамически и очень быстро, требует минимальное количество ресурсов.

Реализовывать подобное на уровне Geometry Nodes это будет совершенно другое, да и GN очень сырое поделие ещё. Его с нуля второй раз переписывают и полностью обратно это несовместимая штука.

И как предлагаешь реализовать такое? Мешем, объектами + задействовать Python? Зачем такие сложности? Эта задача простая и требует простых вычислительных ресурсов. Зачем танком по воробьям стрелять? ))) Да и выйдет это совсем другое, появятся другие сложности и трудности. Ну например как спроецировать тикающие часики на любой объект?

https://ibb.co/X4kRM3R

Обрати внимание на подложку под номерами имитирующую отключенные элементы. Это всё как сделать по другому, не шейдерами? При этом нужно, чтобы это было ещё кастомно, нужно красить в нужный цвет.

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

Си и Си++ не подходят для умножения. Для умножения нужно брать Fortran, R или Julia

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

Если нужна абсолютная точность, не используй стандартные типы данных

Да, это супер идея, но это я не могу изменить, поскольку в шейдерах Blender используются стандартные типы данных для математических вычислений.

По сути мне нужно перебрать целое число по разрядам математикой. Для этого у меня есть небольшой набор операций (+, -, *, /, power() и еще несколько других) и округлений (trunc, round, ceil, floor). Больше по сути ничего нет из инструментов.

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

С ними всё точно также, вот пример на шарпах:

https://dotnetfiddle.net/Widget/Preview?url=/Widget/ttqVvB

Выше всё объяснили откуда и почему так получается. Так что на чём-бы ваш блендер не был написан, точных значений вы никак не получите.

Есть способы уменьшить ошибки, но это тема для отдельных лекций. И блендеру это никак не поможет.

DawnCaster ★★
()

В других языках, которых я немного знаю, Java и Python (да, я понимаю, что это интерпретируемые языки) такого нет, результат всегда ожидаемый: 0.56 * 10.0 = 5.6 - P.S. Как оказалось - нет, см. комметарии.

Очевидную проверку сравнением уже предлагали? Вот, тот же питон:

>>> 0.56 * 10.0 == 5.6
False

Вот ява:

System.out.println(0.56*10.0==5.6);
...
false

Ну как-то же надо хотя бы проверять свои безапеляционные утверждения, что-ли.

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

Не то отцитировал (PS зацепил), но и так сойдёт.

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

Но да, тогда не было бы кины ОП.

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

Вообщем, вычисления на компе весьма приблизительные и не соответствуют реальной математике из-за двоичной системы и преобразований «туда-сюда».

Нет. Целые числа компьютер считает точно. Системы счисления тут не причем. Будь компьютер десятичным, 1/9 он бы точно хранить все равно не смог.

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

Будь компьютер десятичным, 1/9 он бы точно хранить все равно не смог.

да хоть четырнадцадь восемдесятседьмых - лехко, вводишь rational тип и хранишь, один хрен в процессоре разные инструкции для разных типов

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

Выше всё объяснили откуда и почему так получается. Так что на чём-бы ваш блендер не был написан, точных значений вы никак не получите.

Да я уже всё понял, благодаря уважаемому сообществу, они дали точную и верную инфу. Спасибо и вам.

Для меня это было конечно открытием, что число нельзя вернуть обратной мат. операцией, оно «теряется», при этом, не совсем понятно для чего некоторые компиляторы на этапе компиляции вычисляют, например, 100/10 = 10.00000000000005 (к примеру), а в рантайме это будет 9.999999999999995 (примерно). Зачем было вносить ещё одну ложь, чтобы прикрыть другую ложь? )) Ну оставили бы везде 9.999999999999995, на всех уровнях, так и вопросов было меньше и было бы понятно даже чайникам. Но так как правду тщательно скрывают, особенно всякие printf, пока им не укажешь размер ёмкости типа данных который нужно вывести на экран, поэтому неизбежно появляются ошибки и вопросы.

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

Да уже проверил и вписал в шапку отметку об ошибках, но предыдущий текст в шапке с ошибкой оставил, чтобы он коррелировал с ответами, чтобы было понятно, что я ошибался. У меня есть право ошибаться ))) Я не Бог.

Я проверял несколько раз, но так бывает, что когда проверяешь быстро, то в одном месте, то в другом в браузере, то на другом рабочем столе, то неизбежно появляются ошибки. Ну вот так вот работает мозг, ничего с этим не поделать. Иногда смотришь на экран и видишь одно число, а другой видит другое. И вообще реальность в реальности не реальность ))) То что видит один, не увидит другой. Картинка строится в мозгу, а не за пределами глаз, поэтому тут всё индивидуально )) Ну ладно, это уже философия.

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

Я лишь о том, что проверка же элементарная. Раз делаются такие утверждения.

Да проверял я, проверял… И код кидал на онлайн компилятор. Я же не Столман какой-то, чтобы заявлять, а все должны будут прислушаться ко мне )) Ну вы хоть немного разделяйте чайников и Гуру. Нельзя от чайника требовать мастерства Гуру ))

dva20
() автор топика
Ответ на: комментарий от untitl3d

В java флоатинг-поит арифметика такая же всратая как и везде, но там есть BigDecimal, который мастюз, если работаешь с нецелыми числами. Т.е. видишь в java коде float/double - писатель дятел.

BD толстый и медленный. Если цифр много придётся юзать double. В принципе, если у тебя нет каких-то астрономических величин, можно и на double нормально сделать, чтобы адекватно округлялось и не плавали копейки при разделении-суммировании.

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

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

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

double x = 5.6;
double y = 0.56 * 10.0;
static const double epsilon = 0.0001;

if (x > y - epsilon && x < y + epsilon) {
    ...
}

Сравнивать числа с плавающей точкой оператором == почти всегда ошибка.

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

EDIT: правильнее будет так:

if (fabs(x - y) < epsilon) {

}

Первый вариант будет неправльно работать, если порядок x или y гораздо больше epsilon.

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

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

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

Числа с плавающей точкой не десятичные, а двоичные, они округляются в момент превращения из строки 5.6 во внутреннее представление, при этом они из 5.6 превращаются в какое-нибудь 5.60000001, потому что двоичного числа, точно представляющего 5.6, в двоичной системе, в формате представления с плавающей точкой просто-напросто нет и рантайм выбирает ближайшее. Далее C++ их правильно умножает. И получается какое-нибудь 56.00000001. Дальше ты их печатаешь. Если в каком-то языке печать тоже округляет, тебе может показаться, что получилось 56.0. С++ видимо не округляет на твоих примерах.

Возможно, тебе нужны binary coded decimal (двоично-десятичный код), но скорее всего они тебе не нужны и нужно просто смириться. Ты же не рубли считаешь, а точки в пространстве.

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

Нельзя от чайника требовать мастерства Гуру

Заголовок темы очень категоричный. Не «почему результат операции умножения в Си отличается от…», а «почему Си не может выполнить простейшую операцию умножения». Вот первая форма подразумевает чайника, вторая — чайника с замашками гуру.

А всем кто рассуждает о программировании в такой форме следовало бы знать о неточностях при работе с вещественными числами. Что, грубо говоря, результат 0.1+0.2+0.3 не обязательно равен результату 0.3+0.2+0.1 и от языка программирования это не зависит. И если один язык 3*0.1 выводит как 0.3, а другой 0.30000000000000004, то велика вероятность, что дело в точности вывода.

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

результат 0.1+0.2+0.3 не обязательно равен результату 0.3+0.2+0.1

Строго говоря, операции с вещественными числами (на ЭВМ) не ассоциативны.

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

Нужно не округлять, а сравнивать с заданной тобой погрешностью - epsilon.

Точно! Это уже выше в комментариях подсказали, но я не придал тогда этому значение, а точнее не успел разобраться с этим. А сейчас уже использую это, понял для чего это. Спасибо!

dva20
() автор топика
Ответ на: комментарий от ergo

Информатику в школе прогуливал, вот поэтому такие вопросы и появляются

Всё верно, про Epsilon узнал только сейчас )))

dva20
() автор топика
Ответ на: комментарий от den73

Ты же не рубли считаешь, а точки в пространстве.

Дак я не знал как перебрать всего-то 6 знаков (порядков) у целого значения с ведущими нолями по другому как-то, нежели разделить на нужную ёмкость, например:

  1. Дано на вход целое - 123456 и capacity = 6
  2. Нужно разобрать число на числа из разрядов, получить отдельно 1,2,3,4,5,6. Если Capacity больше чем длина целого, то перед перечислением нужно добавить нули, то есть при Capacity = 8 нужно получить 0,0,1,2,3,4,5,6

Для этого, я просто делил исходный int на power(10, capacity) - примерно так, по памяти, может убавлял степень на 1 при возведении, точно сейчас не могу сказать., но в общем получал float = 0.00123456

Далее, в «цикле» (которого на самом деле нет, там где я это реализовывал, ну сейчас опустим это), я умножал 0.123456 на 10, чтобы справа налево получить одно целое из правой стороны - 1.2345600000000000000000000000001112212

Затем, я удалял целое, также математикой, и двигал следующее целое из правой в левую стороны. и так пока не закончится capacity.

Казалось бы, всё отлично, алгоритм прост, задача и её решение радует, но… НО!

У нас нет никаких циклов, переменных, есть только мат.операции + небольшие логические операции. Никаких If’ов нет. If это эквивалент умножения на 0. Есть только голая математика и поток от одного значения к другому и всё!

Так вот, меня не интересовали вот такие вот точности - 1.2345600000000000000000000000001112212

Я был удивлён тому, что даже 2 знака после запятой, уже не могут быть стабильны в вычислениях! То есть 0,56 * 10 - как бы «теряет» шестёрку! А округлить я не могу, так как нету в системе оператора округления Round to! Мне плевать на точные вычисления с миллиардной точности, мне всего-то хотелось оставить в исходном float 2 значения после запятой стабильными! Ну пусть оно (число) плавает там в миллиардных долях и плевать на это, но оно то в реальности плавает то в меньшую, то в большую сторону и в этом проблема.

Но я сделал это всё уже по другому, уже работает и ошибок не выдаёт.

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

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

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

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

в любой финансовой сфере флоатами не пользуются.

В школе меня учили, что 0,6 * 10 будет равным 6. И вот здесь вот, к примеру, до сих пор учат, что

0,15 = 0,15 · 100% = 15%.

Информатику в школе прогуливал, вот поэтому такие вопросы и появляются

Так что прогуливай, не прогуливай, а с неточным ПК человек обязательно столкнётся и будет задавать подобные вопросы. Это вообще вопрос к системе образования.

Я вам так скажу, лучше бы я туда вообще ни дня не ходил в школу и быть может не задал бы вопрос в шапке.

dva20
() автор топика
Ответ на: комментарий от hobbit

А можно ещё взять какую-нибудь реализацию арифметики для чисел с произвольной точности для своего ЯП, если требования к производительности позволяют.

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

в Blender math есть Modulo, если верить справочнику

Есть, и как он мне поможет перебрать значения разрядов?

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

Заголовок темы очень категоричный

Да это для хайпа так сказать, чтобы обратить внимание по «ярче». Техника современных маркетологов ))))))

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

В школе меня учили, что 0,6 * 10 будет равным 6 с неточным ПК человек обязательно столкнётся и будет задавать подобные вопросы

Ээээ… То есть даже так, реальный мир с его физическими ограничениями путаем с математическими абстракциями. Хорошо.

Может быть ну его, это гадкое программирование?

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

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

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

в двоичной системе, в формате представления с плавающей точкой просто-напросто нет

Да, это было понятно мне и до этого вопроса, только я не думал, что эта проблема будет уменьшать более большую часть дроби, т.е. ну пусть оно там в миллиардных долях плавает и 0,56 будет в реальности 0,5600000000000123. На это плевать. Но оно то оказалось (для меня), что в реальности плавает и в меньшую сторону, хотя это как бы тип данных подсказывает, что он плавающий (float), но я не мог предположить, что умножение на введённое «целое» в дробях 10.0 убавит значение из исходного дробного числа, т.е шестёрка в дроби уменьшится и станет пятёркой - 5,59999999999985 (примерно)!

При делениях, как казалось, такое вполне допустимо, но чтобы при умножении на «круглую» цифру (я же ввел с клавиатуры 10.0, а не 9,99999999998), я конечно такого не знал.

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

реальный мир с его физическими ограничениями

Это кто установил, что есть ограничения? Для кого-то они есть, для кого-то ограничений нет. Мир вообще полностью фантастический, никакой стабильности, ни одна мат. система не устойчива, адронные коллайдеры вон запускают, где же тут ограничения-то!?? )))

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

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

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

Есть ли ответ попроще для чайников так сказать? ))

Для чайников: работа с десятичными числами всегда в той или иной степени приводит к потерям точности. Компьютер не умеет хранить их именно в десятичном виде, поэтому используются стандартизированные костыли.

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

Компьютер не умеет хранить их именно в десятичном виде

Да, это было как бы понятно и до этого вопроса, вопрос в другом был, почему перенос запятой (умножение на 10.0 введенных с клавы) не может быть без потери правой части исходного числа. Ну пусть бы оно там плавало в большую сторону, и при умножении 0.56 * 10.0 выдавало бы 5.600000000001, но в реальности плавает в меньшую сторону и результат может быть 5.599999999998.

То есть, по сути то я ввёл 10.0, а не 9,9999999. Вот в чём вопрос. Куда C++ дел мои доли? Или почему он их УКРАЛ зараза такой?? Это что за комиссии такие!? ))))))

Если бы я ввел 9.99999999 при умножении 0.56 × 9.99999999, то я бы и получил результат ожидаемый как 5.5999999944. Так работает KCalc в KDE.

Но я ввёл 10.0, а С++ записал это в двоичной системе как 9,999999999…. Почему он не записал это как например 10,000000000000011 ? Вот вопрос.

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

Рубли интами надо считать почти всегда (ну или оборачивают хитро те же инты). В копейках. И избегать округлений делением, поскольку что-то не может стоить 375 рублей 28,8 копеек.

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

А что при этом в памяти компьютера?

Но судя потому, что я получаю, а это 5,59…., то когда я ввожу 10.0, то в памяти C++ записывает видимо как 9,99999999….

dva20
() автор топика
Ответ на: комментарий от greenman

Куда ты ввёл?

Да с устройства, с клавиатуры например и это записалось во float значение. Только при «записывании» снялась комиссия ))))

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

В школе меня учили, что 0,6 * 10 будет равным 6

в школе этому учат на уроках математики, а работа с флоат числами в ПК - на уроках информатики.

Это вообще вопрос к системе образования

не-не-не… это как раз таки вопрос к ученику. простая житейская мудрость «нельзя научить, можно научиться» лишь подтвержает это. особенно сейчас, когда материала для самостоятельного обучения более чем достаточно.

лучше бы я туда вообще ни дня не ходил в школу и быть может не задал бы вопрос в шапке

все верно, не задал бы. ибо эти знания не особо нужны на кассе в магазине или на разгрузке вагона цемента ))

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

а работа с вещественными числами в ПК - на уроках информатики.

В моей сельской школе, 35 лет назад никакой информатики не было. Не все рождаются в «резиновых».

не-не-не… это как раз таки вопрос к ученику

Если так, то надо ставить вопрос по-другому немного, почему не всех рождают в Москве и почему не у всех равные возможности доступа к обучению? Так что не надо винить ученика, он не выбирал рождаться в таких ограничениях.

dva20
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.