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)
Ответ на: комментарий от dva20

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

pinus_nigra
()

Всё у тебя правильно считается, просто float имеет точность в 6 знаков, а ты пытаешься вывести 10 знаков вот и получаешь то что получаешь выходя за границы точности. Вместо %.10f ты не хотел написать %.1f ?

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

Да, в Блендере есть Python, но мне нужно решить задачу исключительно шейдерами. Текстовое представление числа вывести на экран «крутилкой» (слайдером) в шейдере. В шейдере ввел 12345 в текстовое поле и на экране появилось это же число. Это значительно сложнее чем реализовать в Пайтоне, так как в шейдерах нет таких возможностей как в Пайтон.

https://ibb.co/bdkSwGp

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

Возможно, проще и эффективнее преобразовать число в строку

Да. Но. В шейдерах такой возможности нет - https://ibb.co/bdkSwGp

Есть только узлы (ноды) математики которые полностью полагаются на C++ код

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

Если короткий ответ: так надо для инженерных и научных вычислений, чтобы минимизировать ошибки вычислений и одновременно получать модели, более-менее адекватные окружающему нас миру, сложному и до конца непознаваемому. Если нужна абсолютная точность, не используй стандартные типы данных. Используй, например, алгебраические (символьные), в которых численная оценка происходит один раз в самом конце вычислений. Будет что-то типа решения школьной задачки на бумаге, но только выполняться будет компом.

seiken ★★★★★
()
Ответ на: комментарий от dva20
float value = 0.56f; //тут у тебя float
    value = value * 100.0; // а тут у тебя 100 это double ты f забыл. 

Выводит 56.00000000000000000000000000000000000000000000000000

Тебе никогда в жизни такие числа не пригодятся. Не лезь в крайности, хочешь точности не суйся дальше дианазона точности типа для float 6 знаков для double 15 знаков (всех и до и после запятой) дальше лезешь лови приведение числа к наибольшему чётному при округлении (если не путаю) и то будешь всё равно ловить ошибки точности ибо ибо. Используй библиотеки для больших чисел числа там вообще как строки представляются и можешь хоть каждую песчинку в млечном пути пронумеровать

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

это не округление числа, а преобразование числа в строку

Да, спасибо всем огромное! Я это знал 15 лет назад, но уже всё это позабыл. Так уж устроен мозг )) Но вы мне все очень помогли. Я благодарен вам!

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

value = value * 100.0; // а тут у тебя 100 это double ты f забыл.

Угу, точно-точно. Ошибка вышла у меня ))

dva20
() автор топика

Я тебе на это отвечу 4 буквы и 3 цифры: IEEE 754

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

Не лезь в крайности, хочешь точности не суйся дальше дианазона точности типа для float 6

Ок. Начинаю это понимать ))) Спасибо. Буду искать обходные пути в моей задаче https://ibb.co/bdkSwGp

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

Да я вообще маленький ребёнок, с математикой туго. Так что… так..

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

картинка у меня не открывается. Вот https://youtu.be/U0U8Ddx4TgE думаю такой формат тебе будет «роднее». Есть стандарт для вещественных чисел он и указывает как и что будет считаться. Хочешь бесконечной точности используй библиотеки для длинных вычислений так и гугли

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

Текстовое представление числа вывести на экран «крутилкой» (слайдером) в шейдере. В шейдере ввел 12345 в текстовое поле и на экране появилось это же число.

Так объясни подробнее. Ты хочешь ввести 12345 и в зависимости от крутилки вывести 0.12345 0.012345 0.0012345?

AlexVR ★★★★★
()

Есть ли вообще в C++ возможность получить математикой 0.56 * 10.0 = 5.6 без округления?

Можно воспользоваться рациональными дробями. Ну, на пример, из буста (https://www.boost.org/doc/libs/1_78_0/libs/rational/rational.html)

rational<int> A(58,100), C = 10 *A;
anonymous
()
Ответ на: комментарий от LINUX-ORG-RU

Кстати, я устранил ошибку https://godbolt.org/z/soEGa8Maz

Но stdout показывает 56.00000000000000000000000000000000000000000000000000 при умножении 0.56 * 100.0

В Java такой же результат https://godbolt.org/z/sYqh1TPzx

Но уже стало понятно, что в двоичном представлении 100 и 10 это не простой сдвиг «запятой» и это ответ на мой вопрос - https://www.h-schmidt.net/FloatConverter/IEEE754.html

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

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

Я по заголовку сразу заподозрил, что речь про плавающую арифметику. Так оно и оказалось. Она не для этого, совсем.

Бери инты и рассматривай их как дроби с неким фиксированным знаменателем. В Форте, например, так и делали, там плавающей арифметики не было вообще. И ничего, астрономию на этом считали.

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

Затем, чтобы вводимое значение разобрать в «цикле» на составляющие - https://ibb.co/2N0jcQJ

Как можно видеть, там нет привычных циклов, строк, других типов данных и так далее. Есть система нодов и простейшая математика. Больше ничего нету. Всё бы ничего, всё классно получается, но не могу вывести точно число на экран. Вводим 59, на экране получаем 58 и это при двух разрядах, а на 6 разрядах уже совсем другие числа выходят.

dva20
() автор топика
Ответ на: комментарий от anonymous
int main()                                                                      
{                                                                               
    cpp_dec_float_100 b("0.56");                                             
    b *= 10;                                                                 
    cout << fixed << setprecision(50) << b << endl;                          
 }

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

А теперь в другую сторону

ну семен семеныч…

#include <stdio.h>

int main() {
int n = 1234;

int k[12] = {0};

int i = 0;
while(n >= 10){
    k[i] = n % 10; 
    n /= 10;
    i++;
}
k[i] = n;

for(i = 11; i>= 0; i--){
        printf("%d ", k[i]);
}

return 0;
}
0 0 0 0 0 0 0 0 1 2 3 4 
olelookoe ★★★
()
Ответ на: комментарий от alysnix

и экспонента-степень двойки

Вот и ответ автору :)

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

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

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

Мя знаю, как работает блендер =(
Подход неправильный, но при такой простой задаче проблему спрячет round, как уже говорил.
https://ibb.co/x3hBS1W

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

Для таких тредов надо раздел на форуме сделать - «Горе от ума»

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

На самом деле нет, тут подсказали уже, что дело было в округлении при принтах на экране. Если 0,56 * 10, то это не будет 5,6 в Пайтоне:

>>> a = 0.56
>>> a * 10
5.6000000000000005
>>> a * 10 == 5.6
False
>>>

Но в Бленедере, в шейдерах, будет 5.6!!!! при определённых условиях, но если добавить ещё одно любое вычисление в поток, то уже не будет выходить тоже значение, будет 5.59999999 и это не даёт возможности для сравнения https://developer.blender.org/T95164

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

при такой простой задаче проблему спрячет round

Спрячет то спрячет, но при этом и спрячет от меня то число, которое нужно вывести на экран ))) Хочешь вывести на экран 12345? А получишь 129999 )))))

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

Ты не поверишь, там можно от одного входа несколько связей протянуть. Одну округляем, вторая остаётся нетронутой.

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

в Пайтоне:

Всё верно, но и там есть в библиотеках decimal/number типы - используются очень широко.

https://docs.python.org/3/library/decimal.html

- в Цэ это гораздо более специальные случаи.

Но с питоньей точностью в float всё равно легче не париться, чем с вечно потерянными копейками в 1С.

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

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

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

Вообще, мне всё больше кажется, что никому решение задачи нафиг не сдалось - ТС вбрасывает, а остальным только дай повод посраться.

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

пока не вскрылась проблема с точностью при умножении на «целое» число.

Так у тебя основная проблема при делении, а не при умножении. У тебя 0.56 как получалось?

Если это строка, то нужно переходить на целочисленную арифметику.

Если это из 56.0/100, то надо менять порядок умножения и деления.

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

короче когда точность теряется, такие вот цепочки вычислений надо делать как можно короче

Да, но мне казалось, что 0,56 (короткое же?) можно легко преобразовать в 5,6 умножением на 10. Но оказалось без округления это невозможно, так как когда я ввожу 0,56 «ручками», где-то глубоко в системе оно записалось как 0,559999999999985.

А в итоге вон что оказалось:

>>> a = 0.56
>>> a
0.56
>>> a == 0.56
True
>>> a * 10 / 10
0.56
>>> a * 10
5.6000000000000005
>>>

Умножение и деление возвращает число в изначально введённое, а если просто умножение, то уже идёт «искажение» исходного числа.

Так тут, в Пайтоне число становится 5.6000000000000005, а в C++ совсем другое, меньше 5,6.

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

Кстати да, в реальности вообще можно потерять саму реальность ))) Коперфильду и им подобным фокусникам удаётся же как-то это делать ))

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

Спасибо за ссылку на интересный материал. Я уже всё понял в чём проблема.

dva20
() автор топика

Потому что некоторые десятичные вещественные числа представимы только бесконечными рядами двоичных дробей, а в остальном как вам и рекомендовали - смотрите айайай-754. Для полного понимания от себя рекомендую разобраться с тем как представляются числа, в т.ч. дробные в ВМ, а также глянуть первую главу книги Essential Mathematics for Games - в ней прям всё по теме представления чисел на ВМ. Ситуация типична для всех языков программирования для цифровых вычислительных машин, т.к. работают только с конечными формами двоичных чисел.

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

Да, это было бы отличным решением! Но проблема в том, что нет такой возможности в системе нодов Блендера, есть только «математика»

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

Вместо %.10f ты не хотел написать %.1f ?

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

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