Как стать автором
Обновить

Комментарии 184

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

Функция atof преобразует строку в значение типа double.
В моем алгоритме решается следующая задача. Имеется, допустим, двоичное число (float) y=1.00100111100111010011011*2^-21, значение которого равно десятичному представимому числу x=4.7731452512794022565370082855225e-7. Надо правильно округлить это число до N (произвольных) десятичных значащих цифр и представить полученное число в нормализованном двоичном коде. Например, для N=7, наше округленное число будет равно Xr=4.773145e-7. Его двоичный эквивалент равен y=1.00000000010000011001101e-21. Значение этого числа равно десятичному представимому числу X=4.77314472391299204900860786438E-7, которое является ближайшим к правильно округленному числу Xr.
Надо правильно округлить это число до N (произвольных) десятичных значащих цифр и представить полученное число в нормализованном двоичном коде.

Зачем? Объясните, ради какой практической надобности может понадобиться преобразовывать двоичное число в десятичное, потом округлять, а потом снова преобразовывать в двоичное?
Извините, я не совсем правильный привел пример. Вот правильный.
Функция atof преобразует строку в значение типа double.
В моем алгоритме решается следующая задача. Имеется, допустим, двоичное число (float) y=1.00100111100111010011100*2^-21, значение которого равно десятичному представимому числу x=5.50624235984287224709987640380859375E-7. Надо правильно округлить это число до N (произвольных) десятичных значащих цифр и представить полученное число в нормализованном двоичном коде. Например, для N=7, наше округленное число будет равно Xr=5.506242e-7. Его двоичный эквивалент равен y=1.00100111100111010011011*2^-21. Значение этого числа равно десятичному представимому числу X=5,5062417914086836390197277069092e-7E-7, которое является ближайшим к правильно округленному числу Xr. Заметьте, что x≠X.
наше округленное число будет равно Xr=5.506242e-7
X=5,5062417914086836390197277069092e-7, которое является ближайшим к правильно округленному числу Xr

Это неправда.


double a = atof("5.506242e-7");
printf("%.35e\n", a);
// 5.50624199999999978808254419426759796e-07
Вы упустили, что пример приведен для float, у которого p=24. Для такого случая в примере все правильно. Вы же использовали double.

А, да.


Так а чем вас стандартный способ не устраивает, который ниже предложили ("умножение числа и последующее после округления деление")?


float a = atof("5.50624235984287224709987640380859375e-7");
double multiplier = pow(10, 7 + 6);
float rounded = floor(a * multiplier + 0.49999997f) / multiplier;
printf("%.31e\n", rounded);

// 5.5062417914086836390197277069092e-07
Имеется, допустим, двоичное число (float)

Ок.

Надо правильно округлить это число до N (произвольных) десятичных значащих цифр


Округлением обычно занимаются при выводе результатов. printf обычно используют, или
велосипед такой формы:
Для случая, когда надо округлить число до определенного знака, в библиотеке <math.h> отдельной функции нет, потому обычно используют умножение числа и последующее после округления деление,


и представить полученное число в нормализованном двоичном коде.


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

Посчитайте в Excel следующее выражение:
1,12345678987658000000E56 + 4,12345678987654000000E56--7,92721801979018000000E+42 = -5,2469135797532E+56.
Вас ответ устраивает?
Если это окончательный результат, то на погрешность можно закрыть глаза, а если результат умножить на E+56, то ответ будет мало походить на ноль.
Я думаю, что в случае, когда в алгоритме присутствует вычитание, желательно аргументы перед вычитанием округлить в десятичном эквиваленте. К чему может привести катастрофическая отмена хорошо описано в многочисленных источниках.
Там, где применяются величины с плавающей запятой, алгоритмы работают с заданной точностью, а данные снабжены информацией о числе верных знаков. От того, что вы умножите результат на миллиарды, вы получите ответ с погрешностью, увеличенной в миллиарды раз.
а данные снабжены информацией о числе верных знаков

Где кроется эта информация о десятичных верных знаках в двоичном числе формата double?

От того, что вы умножите результат на миллиарды, вы получите ответ с погрешностью, увеличенной в миллиарды раз

О чем и речь. А если бы перед вычитанием числа были правильно округлены, мы, в нашем примере, получили бы 0. И какое бы число на 0 ни умножай, в результате будет 0.
Информация о погрешностях известна разработчику алгоритма и программы (иногда это разные люди).
Не всегда ее нужно носить и преобразовывать вместе с числами, так как эта информация просчитывается при доказательстве устойчивости и сходимости, например, той же разностной схемы.

К примеру, для МКР, которым решают классическую задачу — дифференциальное уравнение с частными производными — эллептическое уравление, заданное на квадрате, если разбить этот квадрат сеткой с шагом 0.1, полученное решение будет менее чем на 0.01 отличаться от точного. То есть точность — два знака после запятой.

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

Когда нужно, чтобы числа правильно считались в символьном смысле, арифметику с плавающей запятой просто не используют — используют либо арифметику с неограниченным числом знаков и рациональными дробями, как матлаб какой-нибудь,
либо арифметику с фиксированной запятой — там правильное округление уже заложено.
Я подозреваю, что при подобных операциях (суммировании массива float со слишком отличающимися порядками) массив нужно сначала сортировать.

Есть ли в этом какой-то практический смысл, в сложении чисел, отличающихся на 14 порядков? Пролететь 1 млрд км туда, потом еще 1 мм, потом 1 млрд км обратно —
перемещение будет 1 мм? ;)
А практическое-то применение какое? В какой задаче Вы столкнулись с этим вычислением?
Мне что-то подсказывает, что Вы с упорством, достойном лучшего применения, используется IEEE574 не по назначению.
А каково назначение IEEE754?
И какое упорство достойно лучшего применения, чем стремление сделать вычисления более точными и более быстрыми (читай простыми)? Вы не предложили ни одной работающей программы, решающей проблему правильного округления десятичных чисел в двоичном коде. Не могут предложить простого решения и разработчики стандарта. Хотя ломают над этим голову.
См., например, 1. Modern Computer Arithmetic Richard P. Brent and Paul Zimmermann 2. Correctly Rounded Floating-point Binary-to-Decimal and Decimal-to-Binary Conversion Routines in Standard ML
3. Hardest-to-Round Cases – Part 2 и другие. Все эти ребята входят в рабочую группу по ревизии стандарта IEEE754 и пока проблему не решили.
И потом, стандарт должен удовлетворять потребности всех пользователей решающих разнообразные задачи, а не только тех с которыми вы сталкиваетесь. Уж извините. Накипело. Поэтому, давайте по делу.

А каково назначение IEEE754

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

Вычисления не будут точнее, чем исходные данные. А в исходных данных у вас никогда не будет столько значимых цифр, сколько входит в мантиссу double.
И точность результата будет у вас уменьшаться при каждом вычислении, независимо от того, в каком формате вы храните данные.

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

Вы не объяснили, нахрена она нужна.
Ну, хорошо. Вот вам пример.
Рассмотрим разность двух десятичных чисел 9876543210988,06-9876543210988,04=0,02. В формате double эта разность будет 9.876543210988060546875E12-9.8765432109880390625E12=0.021484375
Ошибка вычисления в двоичном коде относительно точного значения будет |0.02-0.021484375|= 0.001484375. Если же округлить полученный результат до 2 знаков после запятой получим 0,02. Или в формате double 2.00000000000000004163336342344E-2. Ошибка после десятичного округления двоичного числа составляет |0.02-0.0200000000000000004163336342344|≈0,4*10^-17.

Попробуйте, в который раз предлагаю, сделать осмысленный эксперимент. Посчитать sin(pi) через ряд Тейлора по обычной методике и по вашей. Если по вашей методике ряд сойдется к нулю значительно быстрее — тогда ок, ее можно применять в прикладных расчетах.

двух десятичных чисел 9876543210988,06-9876543210988,04

Замечательно. С какой точностью они известны?
Абсолютно точно. Именно эти два числа.
Очень интересно.
Приведите пример физической величины, которую можно измерить с подобной точностью.
Это вопрос для другой темы обсуждения. Мы же обсуждаем влияние десятичного округления двоичных чисел на точность вычислений.
Что касается порядка чисел, приведенных в примере, то я думаю где-нибудь в астрономии, космологии, или в микромире, вы вполне можете столкнуться с подобными величинами.
От решения этого вопроса зависит целесообразность обсуждения данной темы. О чём Вам здесь уже сказал примерно каждый.

Если Вы лично работаете с абсолютно точными цифрами — вы не должны использовать тип с плавающей точкой, ни IEEE754, ни какой-то другой, какой вы выдумаете. Это как 2*2=4.
Я лично, работаю с изучением проблем двоичных вычислений. И пытаюсь как-то улучшить двоичную арифметику, чтобы она была точнее и быстрее. А мне все доказывают, что их устраивает статус-кво. Жаль. У нас разные цели.
Понимаете, существует хороший инструмент, который предназначен для работы с приближенными значениями.
Вы попытались его использовать, ладно бы для работы, а для баловства, с вроде как точными значениями, и у вас «неожиданно» получилось плохо. Вы вместо того, чтобы взять более подходящий для ваших задач инструмент, развели бурную деятельность по обсиранию указанного инструмента.
Если же округлить полученный результат до 2 знаков после запятой получим 0,02

У вас было 2 числа, известных с точностью до 2 знаков после запятой, которые при вычитании должны давать 0.02 с точностью до 2 знаков после запятой. После вычитания получилось 0.02 с точностью до 2 знаков после запятой (0.021484375). В чем проблема-то?

Неправильно, погрешности-то сложатся, погрешность алгебраической суммы равна сумме погрешностей слагаемых, так что допуск будет даже больше —
0.00(9) + 0.00(9), то есть в сумме верным будет уже один знак после запятой.

Это как складывать в терминах «плюс-минус лапоть» — в одном слагаемом у нас плюс лапоть, в другом — минус лапоть, в сумме получаем, что грешим уже на два лаптя.

А, ну формально да, я скорее про ожидания автора говорил.

Мы имеем 2 числа. Они абсолютно точные. Я ручаюсь, сам придумал:).
И мне захотелось узнать, чему равна разность этих чисел. Имею право? Имею. Ждать мне некогда, матлабы и прочие инструменты слишком долго считают. Решил посчитать в двоичной арифметике, используя Стандарт для чисел с плавающей точкой. Набираю на клаве свои абсолютно точные числа 9876543210988,06 и 9876543210988,04. Поскольку все 15 цифр в числах верные, то и результат хочу получить с такой же точностью, т.е. с точностью до 15 значащих цифр.
Напечатал я одни числа, а на вход моего двоичного калькулятора поступают другие числа 9.876543210988060546875E12 и 9.8765432109880390625E12. Смотрю на сколько они отличаются от моих точных. И вижу, что погрешность первого числа составила 0,000546875, а второго 0,0009375. Многовато конечно, ну да ладно. Назад пути нет, процесс необратимый.
Залил я свои числа в калькулятор и на выходе получил ответ: 0.021484375. Да, что-то не очень. Я калькулятору — Ты что ж, гад, мне выдал? А он мне — Ничего не знаю, что компилятор мне дал, то я и посчитал. Посчитал, кстати, точно. Т.к. 9.876543210988060546875E12-9.8765432109880390625E12=0.021484375. Как ни крути, результат точный. И погрешность, ну строго по теории образовалась: 0,000546875+0,0009375=0.000484375
Кто же, думаю, виноват? Компилятор свое дело сделал точно, но числа получились приблизительными. Калькулятор посчитал точно, но ответ еще больше далек от правды. Что же делать? Присмотрелся к ответу, а там две цифры верные. Эврика? Надо отсечь лишние. Как, да просто округлить надо! А как это сделать, это совсем другая история.
Мы имеем 2 числа. Они абсолютно точные
Число само по себе не может быть точным. Число 3.141592666 — точное или неточное?
Для вас оно может быть точное, а может быть и не точное. Все зависит от его происхождения. Для калькулятора — это точное число, т.к. он ничего не знает об истории его рождения. Его задача, максимально точно сделать вычисления, не внося в них, по возможности, больших искажений, а как их интерпретировать, не его забота.
Для калькулятора — это точное число

С чего Вы это взяли? Когда у нас есть число с плавающей точкой, логично считать, что его погрешность ±половина последнего известного разряда. Так во всех расчётах делают, почитайте хотя бы Брадиса.
Если число приближенное, то да, погрешность <=ulp. А если оно точное, то даже записанное в форме с плавающей точкой, оно остается быть точным. Ну, не должен знать калькулятор биографию этого числа.
Если оно точное, то его НЕ НАДО записывать в формате с плавающей точкой. Но уж если записал, то смирись с тем, что вычислитель будет полагать его приближенным.
максимально точно сделать вычисления, не внося в них, по возможности, больших искажений


Надо отсечь лишние. Как, да просто округлить надо!


Я вижу противоречие, или мне кажется? ;)
Вам кажется. Отсекая лишние цифры («мусор») точность повышается.
Ложь.
Отсекая лишние цифры («мусор») точность повышается.


Неплохо так ;) Двоемыслие айтишника?
Какие ваши доказательства?
— В.И, сколько будет 0.5 + 1/2?
— Нутром чую, Петька, будет 1 литр, до доказать не могу


Вы так и не привели пример задачи, которая плохо, неточно решается со стандартным округлением и хорошо, точно — с нестандартным.
Как, да просто округлить надо!

Так чем вас стандартное округление-то не устраивает?

Давайте, как-то определим круг обсуждаемых вопросов.
1. Предложен альтернативный известному алгоритм округления десятичных чисел в двоичном коде. Он работает.
Возражений не?
2. Десятичное округление десятичных чисел в двоичном коде может повысить точность вычислений. Приведен конкретный пример.
Обоснованных аргументов против этого факта пока высказано не было.
3. Вопрос -А на «хрена» это нужно?
См. п.2.
4. Чем меня не устраивает стандартная функция округления?
Если она вас устраивает, значит вы не сталкивались с задачами, где она облегчает жизнь. Пользуйтесь стандартной. Но реализовать стандартную функцию в железе затратно.

  1. Он не альтернативный. Математически он тот же самый. Именно поэтому и вопрос — почему вы говорите, что стандартное округление как-то неправильно работает.


  2. Это не доказано, пример не приведен. Я у вас его несколько раз уже просил, вы уходите от ответа. В примере выше вы пишете, что ответ должен быть 0.02, но он и так 0.02.


  3. См. п.2.


  4. Так я об этом и спрашиваю. И другие вас тоже об этом спрашивают. В каких конкретно задачах ваше округление "облегчает жизнь", а стандартное "не облегчает"?



Но реализовать стандартную функцию в железе затратно.

Почему "умножение + сложение + деление" вдруг стало затратнее "возведение в степень + умножение + условие + умножение + условие + деление + сложение + умножение"? Вычисление числа разрядов и возведение 10 в эту степень я, так и быть, не учитывал.

Может быть следующее пояснение расставит все по своим местам.
Согласно Стандарту обмен операндами в компьютере производится через внутренний формат обмена в 32- разрядном слове. Чтобы сохранить промежуточный результат в массиве, надо число double преобразовать в float.
Пусть у нас есть число в double:
1.11001101001010110010100 10111110110001000100110110001*2^40 и нам надо его запомнить с минимальной погрешностью.

Если округлить это число до 24 значащих двоичных цифр с погрешностью <0.5ulp, мы получим число 1.11001101001010110010101 *2^40
Десятичный эквивалент этого числа, который будет записан в формат обмена, будет:
11100110100101011001010100000000000000000.000=1980704096256.
Для лучшего восприятия я числа представляю в произвольном масштабе. При желании их можно нормализовать.
Нас интересует точность представления, равная 7 десятичным цифрам. Т.е., мы хотели бы, чтобы в памяти хранилось число 1980704000000. Тогда погрешность десятичного округления была бы <=0.5ulp, но уже десятичного ulp. Двоичный эквивалент числа 1980704000000 в формате double равен 111001101001010110010100 01000100000000000. Или округленное до 24 значащих цифр это число будет равно 111001101001010110010100*2^17. Это число ближайшее к правильно округленному числу 1980704000000 и как мы видим оно отлично от правильно округленного в двоичной арифметике первичного двоичного числа.
Таким образом, чтобы минимизировать потери, перед записью в формат обмена, двоичное число double должно быть правильно преобразовано к двоичному числу, которое должно являться представимым числом, ближайшим к правильно округленному десятичному числу.
При большом массиве обрабатываемых чисел использовать для округления функцию atof очень затратно. Конечно, лучше всего разделить число 1980704096256, в нашем случае на 10^6, округлить, а затем снова преобразовать во float для записи в память. Но как догадаться, что нужно разделить именно на 6? В моем алгоритме эта задача решена.
Вы не правы. Double прекрасно кочуют из оперативной памяти в процессор и обратно. Не надо ничего преобразовывать.
А вы загляните в IEEE754, там все описано.
Вопрос не в 64 или 32 словах обмена.
Процессор, как правило, вычисляет в расширенном формате, а затем пакует в более компактный. Для 64-х разрядных, это 80- битные и даже 128-битные операционные регистры. Но потом все равно приходится паковать. А именно здесь собака и порылась. Я тут выше уже давал ссылки на последние работы по этой проблеме. Пока то, что предлагается, ну крайне громоздко.
Согласно Стандарту обмен операндами в компьютере производится через внутренний формат обмена в 32- разрядном слове.


Что за стандарт? Как он применяется на 64-битной архитектуре? Про 16-битную и 8-битную (микроконтроллеры, ага) даже боюсь заикаться.

Чтобы сохранить промежуточный результат в массиве, надо число double преобразовать в float.


Не надо так делать.
Согласно Стандарту обмен операндами в компьютере производится через внутренний формат обмена в 32- разрядном слове. Чтобы сохранить промежуточный результат в массиве, надо число double преобразовать в float.

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


Я в этом сомневаюсь. Если бы это было так, тип double был бы бесполезен. Представьте, что они бы в integer преобразовывались посередине вычислений.


Поэтому дальнейшие ваши рассуждения ложны, к происходящему в программе они не имеют никакого отношения.


Если вы будете вручную конвертирвать из double во float и обратно с превышением разрядной сетки float, значит у вас плохо написанная программа, а плавающая точка тут ни при чем.


Т.е., мы хотели бы, чтобы в памяти хранилось число 1980704000000.

Значит вы неправильно выбрали инструмент, потому что там 30 значащих цифр, а во float влазит 24. А в double это значение нормально влазит. Значит при использовании double оно округлится так, как нужно.


При большом массиве обрабатываемых чисел использовать для округления функцию atof очень затратно.

Так и не надо ее использовать для округления, она в моих примерах используется для ввода данных. Округление это floor(a * multiplier + 0.499...) / multplier (константа зависит от используемого типа float или double).


Конечно, лучше всего разделить число 1980704096256, в нашем случае на 10^6, округлить, а затем снова преобразовать во float для записи в память. Но как догадаться, что нужно разделить именно на 6? В моем алгоритме эта задача решена.

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

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

IEEE Standard for Floating-Point Arithmetic (IEEE 754). Разделы: «Extended and extendable precision formats» и «Interchange formats».
Значит вы неправильно выбрали инструмент, потому что там 30 значащих цифр, а во float влазит 24. А в double это значение нормально влазит. Значит при использовании double оно округлится так, как нужно.

Ну, во-первых, в числе 1980704000000= 1.980704*10^13 всего 7 значащих десятичных цифр, которые гарантированно могут быть представлены в 24 разрядной мантиссе float с точностью <=0.5ulp (десятичной). А во-вторых, формат doable гарантированно может представить 15 десятичных цифр (где-то, кажется вы сами об этом упоминали). Так что число 1.980704*10^13 гарантированно «влазит» в float с указанной погрешностью и точно, как бы это вас ни коробило, в формат doable. Но если взять числа с большей экспонентой, например число 1*10^30, то оно уже и в doable не вместится.
Посчитать значащие цифры и отнять сколько надо.

У вас в формате double записано число 1.0110101111001100010000100100111111101011011110010111*2^140. Посчитайте сколько значащих десятичных цифр в этом числе и округлите до 3 значащих цифр. На основании того, что вы свои расчеты выполняете с точностью до 3 значащих цифр, а остальные для вас являются ложными, согласно теории приближенных вычислений.
Ваше достижение в том, что вы собрали 2 алгоритма вместе?

Что вы имеете ввиду? Какие 2 алгоритма я собрал вместе? Подскажите. Честное слово, я не нарочно:).
Согласно Стандарту обмен операндами в компьютере производится через внутренний формат обмена в 32- разрядном слове.


Там нет такого утверждения.
For the exchange of binary floating-point numbers, interchange formats of length 16 bits, 32 bits, 64 bits, and any multiple of 32 bits ≥128 are defined

For the exchange of decimal floating-point numbers, interchange formats of any multiple of 32 bits are defined.

— эти фразы переводится иначе.
Молодой человек! Если вы хотите постигнуть истину, а не самоутвердиться, пишите мне в личку или на мой блог. Я постараюсь найти время ответить вам.
Вопросы я (и не только я) уже задавал, но ответа не увидел. Публичный ответ приблизит к истине не только меня ;)
Разделы: «Extended and extendable precision formats» и «Interchange formats»

Там нигде не написано, что 64-битный double преобразуется в 32-битный float.


Ну, во-первых, в числе 1980704000000= 1.980704*10^13 всего 7 значащих десятичных цифр, которые гарантированно могут быть представлены в 24 разрядной мантиссе float

Не могут.


1        9        17       25   30  33       41
11100110 10010101 10010100 01000100 00000000 0
                                ^

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


А во-вторых, формат double гарантированно может представить 15 десятичных цифр (где-то, кажется вы сами об этом упоминали).

Ну я так и сказал — в double оно нормально влазит. Но никакого "гарантированно «влазит» в float" там нет (см. предыдущий пункт).


Посчитайте сколько значащих десятичных цифр в этом числе

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


Я там немного неправильно написал, да. Просто "отнять сколько надо" вряд ли получится.


Что вы имеете ввиду? Какие 2 алгоритма я собрал вместе?

Вы выдаете определение числа значащих цифр за преимущество своего алгоритма. Но это другой алгоритм, с алгоритмом округления он никак не связан. Кстати, тут вообще не очень понятно, что вы называете "задача решена". То, что число значащих цифр задается пользователем?

Там нигде не написано, что 64-битный double преобразуется в 32-битный float.

Я не могу здесь пересказывать вам стандарт IEEE754. Я дал ссылку на Wiki… Там можно найти нужную литературу.
Не могут.

Могут. Десятичное 1.980704*10^13 преобразуется в
1.980703965184E12, в котором первые 7 цифр с погрешностью <=0.5ulp дают число 1.980704.
У вас оно вообще вводится пользователем.

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

Да, по другому эта задача не решается так просто.
Кстати, тут вообще не очень понятно, что вы называете «задача решена»

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

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


Десятичное 1.980704*10^13 преобразуется в 1.980703965184E12

Вы писали "мы хотели бы, чтобы в памяти хранилось число 1980704000000". Если нас устраивает отклонение в некоторых пределах, то надо так и писать, но в этом случае подходят оба варианта.


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

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


Еще раз. Вы утверждали "Десятичное округление десятичных чисел в двоичном коде может повысить точность вычислений". Я попросил у вас пример.


Изначальное число у нас было 1980704062856.605712890625.
Вы его округлили до 1980704000000, тем самым внесли отклонение на 62856.
Далее вы предлагаете внести отклонение еще на 34816 (1980704000000-1980703965184).
Каким образом это увеличивает точность?


Если мы хотим увеличить точность, надо брать ближайшее к исходному числу. Во float это 1980704096256 (ошибка 33400).


Именно поэтому оно и "отлично от правильно округленного в двоичной арифметике первичного двоичного числа". Потому что первичное двоичное число было совсем не 1980704000000.


А вот если первичное число взять 1980704000000, то оно работает именно так, как вы и ожидаете.


float a = atof("1980704000000");
printf("%.6f\n", a);
// 1980703965184.000000
Надо привести одну цитату, подтверждающую ваши слова.

Я не могу привести одну цитату. Принцип двоичных вычислений представляет собой взаимосвязанный комплекс аппаратных и программных средств. Который базируется на принципах закрепленных в стандарте.
Стандарт определяет основные форматы ( 3.1.1 Formats ) представления двоичных и десятичных чисел. Сюда входит формат хранения и обмена данными (3.6 Interchange format parameters), который определяет разрядность компьютера. Для двоичных чисел приняты 3 основных формата 32, 64 и 128 разрядов. В этих словах могут храниться как целые числа, так и упакованные числа с плавающей точкой. Как двоичные так и десятичные. Определен также арифметический формат (arithmetic formats), который может как совпадать с базовыми форматами, так и иметь расширенный ( extended precision format) или расширяемый формат (extendable precision format). Последние форматы используются в арифметических вычислениях для повышения точности. Но после всех вычислений они снова упаковываются в формат обмена. Операционные регистры АЛУ всегда имеют бОльшую разрядность чем ячейки памяти, т.к. после распаковки восстанавливается виртуальная единица в нормализованном числе и добавляются сторожевые биты для повышения точности вычислений. Ну, действительно, развивать эту тему дальше, это еще одна статья в коментах.
Вы писали «мы хотели бы, чтобы в памяти хранилось число 1980704000000». Если нас устраивает отклонение в некоторых пределах, то надо так и писать, но в этом случае подходят оба варианта.


Как раз нас устроило бы если бы в память можно было записать точное значение 1.980704*10^13. Но, поскольку это число непредставимо в float, в результате мы получаем 1.980703965184E12.
А раз printf умеет ее делать, значит и в своем коде можно сделать так же.

А как работает printf вас устраивает? Тогда можно.
Изначальное число у нас было 1980704062856.605712890625.
Вы его округлили до 1980704000000, тем самым внесли отклонение на 62856.
Далее вы предлагаете внести отклонение еще на 34816 (1980704000000-1980703965184).
Каким образом это увеличивает точность?

Если при измерении вы получили результат 12.12345, а класс точности прибора 0.01, что нужно сделать с лишними цифрами? Округлить. В результате мы получаем более точное измерение или нет?
Также и в нашем случае, если мы хотим получить точность вычислений до 7 значащих цифр мы свое число должны округлить до 7 значащих цифр. Но, поскольку число не представимо в flooat, мы получаем ближайшее к точно округленному числу представимое число.
Если мы хотим увеличить точность, надо брать ближайшее к исходному числу. Во float это 1980704096256 (ошибка 33400).

Совершенно верно. Но в double хранится двоичное число с какой-то экспонентой и без преобразования в десятичное представление вы не можете точно сказать на что надо умножить и разделить это двоичное число. А моя программа может.
Операционные регистры АЛУ всегда имеют большую разрядность чем ячейки памяти

Эта фраза означает, что если есть 64-битная ячейка памяти типа double, то промежуточные вычисления будут в формате 128 бит. При чем тут 32 бита, которые имеют меньшую разрядность?


А как работает printf вас устраивает?

Прочитайте еще раз то, что я написал. И предыдущие сообщения этой части. Я писал про подсчет значащих цифр. После нормализации он отличается от числа после запятой на единицу. То есть если мы хотим округлить до 3 значащих цифр, это означает 2 цифры после запятой в нормализованном формате.


В результате мы получаем более точное измерение или нет?

Так это не точность вычислений, а точность измерений.
Я не уверен, что в метрологии это считается "более точным" значением, но теперь по крайней мере понятно, что вы имели в виду.


Также и в нашем случае, если мы хотим получить точность вычислений до 7 значащих цифр мы свое число должны округлить до 7 значащих цифр.

Вот исходное число 1980704062856 и было округлено до 7 значащих цифр. Никакой ошибки округлений здесь нет. Вы в одном случае приводите к float одно число, в другом другое, потому и результаты различаются.


Но в double хранится двоичное число с какой-то экспонентой и без преобразования в десятичное представление вы не можете точно сказать на что надо умножить и разделить это двоичное число. А моя программа может.

Ваша программа не может, она запрашивает эту информацию у пользователя.

Эта фраза означает, что если есть 64-битная ячейка памяти типа double, то промежуточные вычисления будут в формате 128 бит. При чем тут 32 бита, которые имеют меньшую разрядность?

Все промежуточные вычисления выполняются в более широком формате, а хранятся в памяти в базовом формате обмена. В 32-х разрядных машинах базовым является 32-х разрядное слово.
Прочитайте еще раз то, что я написал. И предыдущие сообщения этой части. Я писал про подсчет значащих цифр. После нормализации он отличается от числа после запятой на единицу. То есть если мы хотим округлить до 3 значащих цифр, это означает 2 цифры после запятой в нормализованном формате

О каком представлении числа вы говорите? О двоичном или десятеричном? Поскольку внутреннее представление это нормализованное двоичное, то посчитать цифры в нем, нет проблем. Но вам надо определить множитель для десятичного представления. Следовательно без printf вам не обойтись.
Вот исходное число 1980704062856 и было округлено до 7 значащих цифр. Никакой ошибки округлений здесь нет. Вы в одном случае приводите к float одно число, в другом другое, потому и результаты различаются.

Повторюсь еще раз. Число 1980704062856 является представимым в double числом. Представимым, значит точно представлено в выбранном формате. Но оно не представимо в формате float. В результате округления числа double до float мы получаем правильное округление двоичного числа, в соответствии с выбранным сценарием, прописанным в Стандарте. Мы получаем другое представимое число. И оно, не смотря на то, что двоичное округление было верным может не являться ближайшим.
Вот исходное число 1980704062856 и было округлено до 7 значащих цифр.

Вернемся к истокам. В результате неких вычислений вы получили число double:1.1100110100101011001010010111110110001000100110110001*2^40. Для нас это первичное число. Никакой информации кроме двоичной мантиссы и экспоненты мы не имеем. Чтобы посмотреть, что это в десятичном виде мы применяем printf. И видим 1980704062856.605712890625. Это первичное десятичное представимое число.
Но нас для расчетов вполне устраивает точность N=7 (или любая другая). Для этого первичное число надо округлить до 7 значащих цифр. В десятичной арифметике мы бы получили 1980704000000. Но оно непредставимо в float. Поэтому, максимум на что мы можем рассчитывать это на ближайшее к этому представимое число, которое можно сохранить в fljat.
А это 1.980703965184E12. В котором 7 первых цифр совпадают с погрешностью <=ulp с идеально округленным числом 1980704000000.
Ваша программа не может, она запрашивает эту информацию у пользователя

Еще раз повторюсь. Программа не знает с какой точностью вас интересуют вычисления. Эту точность надо задать.

а хранятся в памяти в базовом формате обмена

Вот есть 64-битный формат double, в нем значения в памяти и хранятся. Типы для этого и придумывают.


В 32-х разрядных машинах базовым является 32-х разрядное слово.

Из того, что 32-х разрядное слово базовое, никак не следует, что не-базовое 64-х разрядное использовать нельзя.
64-разрядные integer нормально считаются на 32-разрядной машине. А 32-разрядные прекрасно считались во времена 16-разрядного DOS. И 64-битные double тоже прекрасно считаются на 32-разрядной машине, с двойной точностью, без округления во float.


Вы возможно с компьютерной графикой путаете, там действительно оптимизировано для float. Даже если в коде будут double, матрицы графических преобразований все равно будут во float.
"Direct3D 10 supports several different floating-point representations. All floating-point computations operate under a defined subset of the IEEE 754 32-bit single precision floating-point behavior."


И оно, не смотря на то, что двоичное округление было верным может не являться ближайшим.

Не может. Исходное число 1980704062856, число 1980704096256 является ближайшим к нему (ошибка 33400).
Ваш вариант 1980703965184, у него отклонение от исходного 97672.


Следовательно без printf вам не обойтись.

printf работает не на магии, а по вполне конкретным алгоритмам. Эти же алгоритмы можно реализовать в своем коде без вызова printf.


А это 1.980703965184E12. В котором 7 первых цифр совпадают с погрешностью <=ulp с идеально округленным числом 1980704000000

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


Вот, берите, пользуйтесь.


Скрытый текст
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

double roundToSignificantDigits(double value, int nSignificantDigits)
{
    int normalizationDegree = (int)floor(log10(fabs(value)));  // or frexp * log2
    int nDigitsAfterDotNormalized = nSignificantDigits - 1;
    int nDigitsAfterDot = nDigitsAfterDotNormalized - normalizationDegree;

    double multiplier = pow(10, nDigitsAfterDot);
    value = floor(value * multiplier + 0.49999999999999994) / multiplier;

    return value;
}

int main()
{
    double d1 = atof("5.50624235984287224709987640380859375e-7");
    double d2 = atof("1980704062856.605712890625");

    printf("%.20f\n", d1);
    printf("%.20f\n", roundToSignificantDigits(d1, 7));
    printf("%.20f\n", (float)d1);
    printf("%.20f\n", (float)roundToSignificantDigits(d1, 7));

    printf("%.20f\n", d2);
    printf("%.20f\n", roundToSignificantDigits(d2, 7));
    printf("%.20f\n", (float)d2);
    printf("%.20f\n", (float)roundToSignificantDigits(d2, 7));

    // 0.00000055062423598429
    // 0.00000055062420000000
    // 0.00000055062423598429
    // 0.00000055062417914087

    // 1980704062856.60571289062500000000
    // 1980704000000.00000000000000000000
    // 1980704096256.00000000000000000000
    // 1980703965184.00000000000000000000

    return 0;
}
Спасибо. Только, поскольку я в программировании чайник, не могли бы вы эту программу расписать алгоритмически или на языке математики?

log10 возвращает степень десятки, что эквивалентно степени 10 для нормализации (и числу десятичных знаков в числе для значений больше 1). Вы это вычисляете через e*0.301. Округляем всегда в меньшую сторону, то есть отрицательные значения округляются в минус бесконечность (0.99 должно стать 9.9, то есть степень -1).
Нормализованное число имеет дробную часть от 1 до 10, значит до запятой всегда одна цифра, значит число знаков после запятой на 1 меньше.
Вычитаем и получаем необходимое число знаков после запятой в исходном ненормализованном числе.
Округляем до него — умножить на соответствующую степень, округлить до целых, поделить обратно.


К float всегда приводим в конце вычислений. Если везде заменить double на float, то результаты другие, но и ваша программа выдает такие же.

Спасибо за толковый ответ.
Округление десятичного числа с плавающей запятой — тривиальная задача, если известна его экспонента и что число — нормализованное. Проблема заключается в нахождении значения десятичной экспоненты. Функция frexp в моем алгоритме просто считывает значение двоичной экспоненты e в числе double. Не знаю, насколько это легко выполнить программно, но аппаратно это осуществляется за 1 такт. Для определения десятичной экспоненты осталось вычислить floor(e*double(value*0.301)). Думаю, это реализовать проще, чем вычислить log 10(value). А что вы думаете?

Другая проблема возникает при сохранении чисел из более широкого формата в более узкий. Например, double в float, расширенный doable в простой double. Последний случай сэмулировать сложно, поэтому я написал тестовую программу округления более широкого формата до более низкого с использованием double. Другие случаи просто масштабируются.
Так вот, проблема в том, что до одного и того же числа можно округлить разные числа. Например, два double — числа 5.50624245984287807511338305951E-17и 5.50624155984287824709987743381E-17, округленные до 14 и менее значащих цифр будут иметь одно и то же значение. (Я специально увеличил |значение| экспоненты). Так, округленное до 7 значащих цифр наше число, это 5.506242. Опять же, в десятичной арифметике, ни каких проблем здесь не возникает. Берем и округляем, если вычислили десятичную экспоненту. Но в двоичном представлении все гораздо сложнее.
Возьмем число 5.50624245984287807511338305951E-17. Его двоичное представление будет таким 1.1111101111011100011110010110010011010000110111101111*2^-55.
Мы знаем, что в мантиссу float можно записать 24 бита. Округляем наше double-число до 24 значащих цифр с погрешностью <=0.5ulp. Получим 1.11111011110111000111101*2^-55= 5,5062425601282510780228041102902e-17≈5,506243. В результате правильного округления двоичного числа double с погрешностью <0.5 ulp мы получили округление его десятичного эквивалента c погрешностью>1ulp. Ближайшим же к правильно округленному десятичному числу будет float-число 5.50624189838376103560066421316E-17.
Думаю, это реализовать проще, чем вычислить log 10(value). А что вы думаете?

Это проще, но я там и написал, что можно и так считать. Дело же в остальном алгоритме.


Ближайшим же к правильно округленному десятичному числу будет float-число 5.50624189838376103560066421316E-17

Смотрите, если вы в вашей программе будете использовать float, то она тоже округлит неправильно, будет 5.5062428910004961e-17.
Если же использовать double и округлять его до разрядности float, меняя тип на float после вычислений, то стандартные средства и так округляют правильно.


double v = atof("5.50624245984287807511338305951E-17");
printf("%.29e\n", (float)roundToSignificantDigits(v, 7));
// 5.50624189838376103560066421316e-17
Дело же в остальном алгоритме.

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

atof это аналог вашего cin>>x. Она используется только для ввода данных, к округлению она отношения не имеет.

Еще раз повторю. Речь идет не об округлении десятичных чисел, выведенных на консоль. А о десятичном округлении двоичных чисел doable, десятичный эквивалент которых вам неизвестен пока вы не преобразуете doable в десятичное число с помощью printf.

Еще раз повторю. В моей программе можно убрать atof и использовать вместо него cin>>v, а вместо printf использовать cout<<v. Функция округления roundToSignificantDigits() принимает и возвращает тип double.


Скрытый текст
#include <iostream>
#include <iomanip>
#include <math.h>

using namespace std;

double roundToSignificantDigits(double value, int nSignificantDigits)
{
    int e;
    frexp(value, &e);

    int normalizationDegree = (int)floor(e * 0.301);
    int nDigitsAfterDotNormalized = nSignificantDigits - 1;
    int nDigitsAfterDot = nDigitsAfterDotNormalized - normalizationDegree;

    double multiplier = pow(10, nDigitsAfterDot);
    value = floor(value * multiplier + 0.49999999999999994) / multiplier;

    return value;
}

int main()
{
    double v;

    cout << "Input value: ";
    cin >> v;

    cout << setprecision(30);
    cout << v << "\n";
    cout << roundToSignificantDigits(v, 7) << "\n";
    cout << (float)v << "\n";
    cout << (float)roundToSignificantDigits(v, 7) << "\n";

    // 5.50624245984287807511338305951e-17
    // 5.50624200000000031759065934773e-17
    // 5.50624256012825107802280411029e-17
    // 5.50624189838376103560066421316e-17

    return 0;
}
В результате некоторых вычислений вы получили какое-то double-число: 1.0011… 00011*2^123 (любое). Вам из него надо получить двоичное число, равное ближайшему к правильно округленному до 7 десятичных знаков десятичному числу.
Все, больше вам ничего не известно. Как это сделать без конвертации этого числа в десятичный код стандартной программой округления?

Умножение на степень десятки, округление до целого, деление обратно.

На какую степень десятки надо умножить двоичное число, приведенное мною?

Я же привел программу без atof, она работает, действия для вычисления степени там написаны, назначение переменных отражено в их названии.


Умножаем степень двойки на коэффициент 0.301..., получаем степень десятки самого числа. Отнимаем число значащих цифр минус 1, получаем на какую степень десятки надо разделить, чтобы использовать округление до целых. Если поменять слагаемые местами, получим, на какую степень десятки надо умножить.

Замечательно! Вы взяли мой алгоритм, один к одному его переписали и выдали его за свой. Может быть вы мне тогда объясните, зачем вы применили эту функцию -(int)floor(e * 0.301)? И дадите ссылку на стандартный алгоритм, где она используется. Буду очень благодарен.
Вы взяли мой алгоритм, один к одному его переписали и выдали его за свой.

Я написал алгоритм, который описан в этом комментарии ("умножение числа и последующее после округления деление") и встречается много где в интернете (1, 2, 3, 4).


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


Может быть вы мне тогда объясните, зачем вы применили эту функцию -(int)floor(e * 0.301)?

Я же написал — "получаем степень десятки самого числа". Можно через десятичный логарифм, можно через двоичный, умноженный на коэффициент. Математически они эквивалентны. Через десятичный логарифм в интернете встречается чаще, так как в других языках низкоуровневых функций типа frexp может не быть.


И дадите ссылочку на стандартный алгоритм, где она используется.

В дополнение к тем ссылкам, которые я уже привел — 1, 2.

Я написал алгоритм, который описан в этом комментарии

В этом коментарии нет алгоритма округления десятичного числа представленного двоичным кодом.
(«умножение числа и последующее после округления деление») и встречается много где в интернете (1, 2, 3, 4).

Вы правы, алгоритм «умножение числа и последующее после округления деление» много раз встречается в интернете. Только в ваших ссылках я насчитал более 20 алгоритмов округления. Но, «стандартного» не обнаружил.
что вас не устраивает в существующих средствах?

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

Вы мне дали ссылку на поисковую страничку google и предлагаете просмотреть все ссылки, чтобы убедиться, что «Через десятичный логарифм в интернете встречается чаще». Но почему-то на страницах, которые вы указали в предыдущих 3-х ссылках я не встретил ни одного алгоритма использующего этот способ.
Что касается «можно через двоичный, умноженный на коэффициент.» Но, что-то я тоже не нашел ни одного алгоритма, кроме моего и чудесным образом «вашего», где бы использовался такой способ определения коэффициента.
Я написал алгоритм… Если это то же самое, что написали вы, тогда непонятно, почему вы говорите, что что-то где-то неправильно округляется.

Вы написали программу по моему алгоритму. Но дьявол кроется в деталях.
Погодите, Вы пытаетесь сказать, что двоичный логарифм, умноженный на коэффициент — это не то же самое, что десятичный логарифм?
«можно через двоичный, умноженный на коэффициент.»
Вы эту фразу имеете ввиду?
Это высказывание моего оппонента. Здесь имелось ввиду, наверное, десятичный логарифм двойки. По крайней мере, именно в этом смысле я воспринял его вольное высказывание. Что же касается равенства lbX=logX, то однозначно это неверно.
Это не «вольное высказывание», а достаточно точное указание способа вычисления десятичного логарифма.

lg(x) = lb(x)/lg(2).

Это однозначно верно.
Для уменьшения объёма вычислений, целесообразно заранее вычислить коэффициент, равный 1/lg(2), чтобы потом умножать на него двоичный логарифм числа.
Речь идет об округлении, а не о вычислении логарифмов.
В этом коментарии нет алгоритма округления десятичного числа представленного двоичным кодом.

Нет такого термина "десятичное число, представленное двоичным кодом". Есть просто число, которое может быть представлено в десятичной, двоичной, римской, или еще какой-то системе счисления. В формате double используется двоичная система счисления, никакой десятичной там нет, поэтому и "округлять десятичное число" нельзя. Десятичная запись в приведенных в этом обсуждении программах присутствует только в строках, при вводе и выводе, в виде ASCII-символов, где цифра 1 представлена кодом 0x31.


Только в ваших ссылках я насчитал более 20 алгоритмов округления. Но, «стандартного» не обнаружил.

Нет там разных алгоритмов. Все реализации описываются выражением "умножить, округлить до целого, поделить". Потому он и стандартный.


Под средствами вы понимаете множество существующих алгоритмов или набор стандартных функций?

Я подразумеваю те алгоритмы и их реализации, которые решают задачу округления числа с плавающей запятой до N десятичных знаков после запятой либо значащих.


Вы мне дали ссылку на поисковую страничку google и предлагаете просмотреть все ссылки

Я дал ссылку на результаты со stackoverflow.com, чтобы показать, что задача округления встречается в разных языках, и не во всех из них есть функция frexp.


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

Потому что вычисление степени десятки и округление это разные алгоритмы. О чем я вам уже говорил. Можно вычислять степень десятки не для округления, или делать округление по степени десятки просто введенной пользователем.
1,2 и 4 ссылки относятся к округлению до целого, в 3 степень десятки задается константой в коде примеров.


А ссылка на страничку google относится именно к теме данной статьи — округление до N значащих цифр. И если у вас появилась такая задача, вам надо было сначала поискать информацию, проверить существующие способы, и только если они вас чем-то не устраивают, написать свой, и указать в статье, чем именно не устраивают.


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

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


Их не надо искать, я в комментарии специально дал еще 2 ссылки, в них используется константа 0.301.


Вы написали программу по моему алгоритму.

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

Нет такого термина «десятичное число, представленное двоичным кодом». Есть просто число, которое может быть представлено… двоичной или еще какой-то системе счисления.
Вы не находите, что «нет» и «есть» здесь взаимоисключающие утверждения?
Нет там разных алгоритмов. Все реализации описываются выражением «умножить, округлить до целого, поделить». Потому он и стандартный

Ни одна из этих операций (умножить, округлить...) не является стандартной. Каждая может быть реализована множеством способов (алгоритмами). Все зависит от процессора. Также и реализация «умножить, округлить до целого, поделить» имеет множество вариантов даже в рамках одного языка. А раз так, у каждого есть свои достоинства и недостатки. Стандартной реализации нет.
Я подразумеваю те алгоритмы и их реализации, которые решают задачу округления числа с плавающей запятой до N десятичных знаков после запятой либо значащих.

Как же так, если
В формате double используется двоичная система счисления, никакой десятичной там нет,

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

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

Приведу вам ваши же слова:

Надо привести одну цитату, подтверждающую ваши слова. Судя по тому, что вы уходите от ответа, вы сами ее найти не можете.

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

Вся информация для любого алгоритма естественно имеется, в том числе и в интернете. Вопрос, как распорядиться этой информацией. Вы, почему-то, для доказательства стандартности алгоритма округления, привели свою программу, в которой использовали frexp из моей программы, а не десятичный логарифм, который выполняет ту же задачу в широко известных стандартных алгоритмах округления. Хотелось бы увидеть хоть одну программу округления, где бы использовалась функция frexp.
Вы не находите, что «нет» и «есть» здесь взаимоисключающие утверждения?

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


Ни одна из этих операций (умножить, округлить...) не является стандартной.

При чем тут отдельные операции? Я говорю про их совокупность, выполнение в определенном порядке с определенными параметрами.


Каждая может быть реализована множеством способов (алгоритмами)
Стандартной реализации нет.

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


Как же так, если

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


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

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


Те, о которых идет речь в данном обсуждении, образуют "алгоритм округления числа до N значащих цифр при представлении этого числа в десятичной системе счисления". И этот алгоритм присутствует в разных реализациях на разных языках по той ссылке, которую вы назвали "страничка google". Также я указал ссылки, из которых взял отдельные части — округление до целого и округление до N десятичных знаков после запятой. Следовательно, ваше утверждение "вы переписали мой алгоритм" неверно. Я переписал то, что уже есть в интернете.


Надо привести одну цитату, подтверждающую ваши слова

Вы сказали "я не нашел ни одного алгоритма, где бы использовался такой способ определения коэффициента".
Я дал вам ссылку, где десятичный логарифм присутствует практически в каждом результате поиска.
Какую еще цитату вам надо? Что должно быть в этой цитате помимо того, что выдает Google?


Что является аргументом этих десятичных алгоритмов? И откуда он берется?

Извините, я не понял этот вопрос. Берется из ответов, потому что авторы ответов на stackoverflow его написали в своем коде. Что туда передается, можно посмотреть в этом же коде. А именно — абсолютное значение числа, которое надо округлить.


Вы, почему-то, для доказательства стандартности алгоритма округления, привели свою программу, в которой использовали frexp из моей программы, а не десятичный логарифм

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


А вот здесь вам указывают на то, что это математически эквивалентные вычисления (переход к новому основанию). Еще раз, log10(x) и frexp(x, &e); e * log10(2); вычисляют одно и то же, поэтому никаких вычислений, свойственных только вашей программе, тут нет.


Хотелось бы увидеть хоть одну программу округления, где бы использовалась функция frexp.

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

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

Изобретатель наворотил кучу наукообразного текста (даже сайт есть), цитирует не по делу сам себя и непонятные источники, и одновременно
путается в школьной математике и отказывается привести доказательства применимости изобретения в народном хозяйстве.
:)
Практика — критерий истины.
Предлагаю дуэль.Сражаться будем не языком, а цифрами. Согласны?
Если да — вот мои условия.
Вы выбираете одну, самую стандартную программу из тех, что вы здесь предлагали или из тех, что найдете в интернете. Но одну, и самую стандартную:)!
И мы будем (стреляться) округлять те числа, которые предложим друг-другу. Каждому дается три выстрела. Оружие в процессе дуэли не менять. Согласны?
Или и дальше будем соревноваться в словесной изящности?
Я, помнится, sin(pi) через ряд Тейлора предлагал посчитать. Почему-то вас это не устроило.
Задача должна быть, во первых, осмысленной. Само по себе округление бессмыссленно, но, вы утверждаете, что правильное (tm) округление повышает точность расчетов. Поэтому расчетов должно быть много, и результат должен быть верифицируемый. Ряд Тейлора не особо показателен, он слишком быстро сходится. Предлагаю двойное обращение матрицы, повторенное раз 1000 (чтобы погрешности расчетов накапливались) ;).
Берем стандартную картинку 512*512, конвертим в grayscale, заполняем матрицу. Прогоняем 1000 раундов обращения, считаем попиксельно среднеквадратическое отклонение от оригинальной матрицы.
Или балабол ;)
ЗЫ: Преобразование Фурье тоже должно подойти.
ЗЗЫ: А батл по спортивному округлению пусть проводится в более подходящем месте (4 класс средней школы) :D
:-)
Вы выбираете одну, самую стандартную программу из тех, что вы здесь предлагали
И мы будем (стреляться) округлять те числа, которые предложим друг-другу.

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


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

Давайте сначала уточним, какая из программ более ваша, которая коэффициент определяет через log, или та, где используется frexp. С какой будем сравнивать?

Ну одно да потому… Математически это одно и то же. Как выражения "2+2" и "4". Или "x2" и "x6/x4". Даже если у меня в программе где-то ошибка в вычислениях, и результаты различаются, это именно ошибка, а не намеренно написанный алгоритм.


Проверяйте ту, которая через log.

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

Выберите ту стандартную программу, которая широко используется


printf ;)
Округление обычно используют при выводе, чтобы не захламлять бумагу/дисплей незначащими символами. И printf с этим справляется ;)
Мы еще не начали, а вы уже оправдываетесь

Как это не начали? Зачем же вы тогда все это спрашиваете?
Я сообщил вам это заранее, на случай если вы найдете какое-то отклонение, так как вы, судя по всему, не разбираетесь ни в программировании, ни в математике, и не сможете правильно оценить сами, является ли это неправильной работой алгоритма округления или случайной ошибкой в программе, которую я набросал за несколько минут, не рассчитывая ни на какие соревнования, и потому досконально ее не проверял. Математическое равенство указанных выражений от этого не меняется.


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

"Программа и 2 числа в этом комментарии"
"Проверяйте ту, которая через log"

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

Ну, что же вы меня все время роняете? С вашей помощью, думаю, разберемся.
Округляет ваша программа правильно, но только до 14 значащих цифр. А моя до 15 и в разы быстрее. Она рассчитана на железо.
Вы правильно сделали, что выбрали вариант с логарифмом, он все же проверен многократно. А моя программа, в вашем исполнении, страдает изъянами. Уж, извините.

Нет уж, вы говорите, что что-то не так, вот и показывайте, как вы запускали, что вводили, какие результаты получили, и какие они должны быть. Раз еще и про производительность начали говорить, то показывайте как замеряли производительность. А то я так тоже могу: ваша программа округляет до 13 цифр, и в 10 раз медленнее.


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

Если чем обидел, простите. В вашей программе я ввел nSignificantDigits=15, но получил округление только до 14 цифр. Или что-то ни так?
Раз еще и про производительность начали говорить, то показывайте как замеряли производительность.
Я уже ранее говорил и вы, вроде, не возражали, что считать в регистр двоичную экспоненту, которая хранится в готовом виде в коде числа double, а затем умножить ее на коэффициент, значительно проще, чем выполнить итеративный алгоритм вычисления логарифма. Мне кажется это очевидно.
Кстати, а почему ваша программа выводит результат не в double, а в строку? Это что же, потом ее опять надо конвертировать в doable?

Почему не в double? Как, по-вашему, должно выглядеть "выводить результат в double"?


В вашей программе я ввел nSignificantDigits=15, но получил округление только до 14 цифр.

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

Почему не в double? Как, по-вашему, должно выглядеть «выводить результат в double»
Формат double, это формат двоичного числа с плавающей точкой. У него есть мантисса и экспонента. Так что это число должно выводиться — мантисса+экспонента, только в десятичном эквиваленте. А то, для экспоненты, скажем =1020, замучаешься искать значащие цифры на консоле. Или я не прав?
Я в вашей программе ничего не менял, кроме коэффициента nSignificantDigits=15, задающего точность. Поэтому ваше число double d1 = atof(«5.50624235984287224709987640380859375e-17»); если его округлить до 15 значащих цифр должно иметь 15 значащих цифр и должно быть равно 5.50624235984287. Но программа округляет его до 14 цифр и дает результат 5.5062423598429. Не спорю, правильный.
Так что это число должно выводиться

Подождите. Вы сказали "почему ваша программа выводит результат не в double, а в строку? Это что же, потом ее опять надо конвертировать в double?". Ваша программа тоже выводит результат в строку. cout<<xr преобразует число double в строку ASCII символов, потому что экран может выводить только строковые символы. cout<<setprecision(18)<<xr это аналог printf('%.17f', xr), о чем я уже говорил. Почему вашей программе можно выводить в строку, а моей нельзя?


Функция roundToSignificantDigits() принимает double и возвращает double. Результат типа double можно использовать потом в других вычислениях, или выводить на экран через printf или cout, или делать с ним что-то еще. Ни с какими строками она не работает, поэтому и конвертировать обратно в double ее результат не надо.


Но программа округляет его до 14 цифр и дает результат 5.5062423598429.

В моей программе, на которую я приводил ссылку, стоит вызов printf("%.20f\n", d1), поэтому выводить 13 знаков после запятой она ну никак не может.


Для вывода строки в виде "мантисса+экспонента", как вы хотите, там должно быть printf("%.20e\n", d1). На функцию округления числа это никак не влияет.


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

Вы дали конкретную оценку "в разы". Это не очевидно. В процессорах есть инструкции для вычисления логарифмов.

Итак, вскрытие показало следующее.
Для округления было взято число d1=5.50624235984287224709987640380859375e-17. Которое на консоль было выведено как 5.5062423598428724e-17.
Округленное до 15 значащих цифр это число должно быть равно 5.50624235984287e-17. Ближайшее к нему представимое число-double равно 5.5062423598428699579894231941E-17.
(Использовался калькулятор) На консоль выведено число 5.50624235984287E-17. Неправильно.
Число-double d1 преобразованное к float равно 5.50624222925600605681173416173E-17(См. на калькуляторе). На консоль выведено число 5.5062422292560061E-17. Правильно.
Округленное до 7 значащих цифр это число должно быть равно 5.062422E-17. Ближайшее к нему представимое число должно быть равно 5.06242211118537429118124926219E-17. На консоль выведено число 5.06241898383761E-17. Неправильно.

Вы решили в троллинг скатиться? Что вам непонятно во фразе "показывайте, как вы запускали"? Или во фразе "выводить 13 знаков после запятой моя программа не может"? Покажите конкретный код, который вы запускали. Полностью, который можно скомпилировать. Так же, как это делаю я. Потому что код это формальное доказательство.


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

Я внес изменения только те, что вы мне рекомендовали.


Ваша программа с изменениями
include <stdio.h>
include <stdlib.h>
include <math.h>

double roundToSignificantDigits(double value, int nSignificantDigits)
{
int normalizationDegree = (int)floor(log10(fabs(value))); // or frexp * log2
int nDigitsAfterDotNormalized = nSignificantDigits — 1;
int nDigitsAfterDot = nDigitsAfterDotNormalized — normalizationDegree;


double multiplier = pow(10, nDigitsAfterDot);
value = floor(value * multiplier + 0.49999999999999994) / multiplier;

return value;

}


int main()
{
double d1 = atof("5.50624235984287224709987640380859375e-17");
double d2 = atof("1980704062856.605712890625");


printf("%.20e\n", d1);
printf("%.20e\n", roundToSignificantDigits(d1, 15));
printf("%.20e\n", (float)d1);
printf("%.20e\n", (float)roundToSignificantDigits(d1, 7));

printf("%.20e\n", d2);
printf("%.20e\n", roundToSignificantDigits(d2, 15));
printf("%.20e\n", (float)d2);
printf("%.20e\n", (float)roundToSignificantDigits(d2, 15));

return 0;

}

Я вас немного разочарую, но atof в вашем примере некорректно работает. И, чтобы 2 раза не вставать, сколько десятичных цифр в double тупо не помещается.

Тут проверял
#include <stdio.h>

int main(void) {
 double d1 = atof("5.50624235984287224709987640380859375e-17");
 double d2 = 5.50624235984287224709987640380859375e-17;
 double d3 = 5.50624235984287224709987640380899999e-17;
 
  printf("%.30e\n", d1);
  printf("%.30e\n", d2);
  printf("%.30e\n", d3);
  if(d2 == d3) printf("Its a same!\n");
  return 0;
}


Думаете, d1 == d2? Или d2 отличается от d3?
./main
0.000000000000000000000000000000e+00
5.506242359842872423179752009763e-17
5.506242359842872423179752009763e-17
Its a same!

Согласен, в ВАШЕЙ программе atof используется неправомерно. Строку мы вводим не с консоли, а задаем прямо в теле программы константой. Зададим наше число правильно.


Ваша программа. Версия 2
include <stdio.h>
include <stdlib.h>
include <math.h>

double roundToSignificantDigits(double value, int nSignificantDigits)
{
int normalizationDegree = (int)floor(log10(fabs(value))); // or frexp * log2
int nDigitsAfterDotNormalized = nSignificantDigits — 1;
int nDigitsAfterDot = nDigitsAfterDotNormalized — normalizationDegree;


double multiplier = pow(10, nDigitsAfterDot);
value = floor(value * multiplier + 0.49999999999999994) / multiplier;

return value;

}


int main()
{
double d1 = 5.50624235984287224709987640380859375e-17;
double d2 = atof("1980704062856.605712890625");


printf("%.20e\n", d1);
printf("%.20e\n", roundToSignificantDigits(d1, 15));
printf("%.20e\n", (float)d1);
printf("%.20e\n", (float)roundToSignificantDigits(d1, 7));

printf("%.20e\n", d2);
printf("%.20e\n", roundToSignificantDigits(d2, 15));
printf("%.20e\n", (float)d2);
printf("%.20e\n", (float)roundToSignificantDigits(d2, 15));

return 0;

}


Но результат тот же. И виновата в этом не ваша программа, а двоичная арифметика.

А вывод программы где, который у вас получился после ее запуска? Слушайте, слово "конкретно" означает именно "конкретно". То есть полностью. От начала до конца. Без недоговорок, без додумываний, без описания измненений на словах. Полностью воспроизводимый пример, доказывающий ваши слова. Программный код, ввод и вывод.


Вы утверждали "На консоль выведено число 5.50624235984287E-17".


Указанная вами программа выводит на консоль такие строки:


5.50624235984287242318e-17
5.50624235984286995799e-17
5.50624222925600605681e-17
5.50624189838376103560e-17
1.98070406285660571289e+12
1.98070406285661010742e+12
1.98070409625600000000e+12
1.98070409625600000000e+12

Где среди них указанное вами число? Его там нет. Следовательно, вы врете. Или по ошибке запускали какой-то другой код. В любом случае, к программе, о которой я говорил, эти результаты отношения не имеют.


UPD: Код онлайн

Да не нервничайте вы так. И перестаньте бросаться словами — "вы врете". Мы просто разговариваем пока на разных языках. Я пытаюсь понять ваш язык. Попробуйте и вы понять о чем я говорю. Вы классный программист. Но речь идет о математике. Я запустил вашу программу, текст которой я вам привел выше. Могу повторить.


Ваша программа. Вариант 2
include <stdio.h>
include <stdlib.h>
include <math.h>

double roundToSignificantDigits(double value, int nSignificantDigits)
{
int normalizationDegree = (int)floor(log10(fabs(value))); // or frexp * log2
int nDigitsAfterDotNormalized = nSignificantDigits — 1;
int nDigitsAfterDot = nDigitsAfterDotNormalized — normalizationDegree;


double multiplier = pow(10, nDigitsAfterDot);
value = floor(value * multiplier + 0.49999999999999994) / multiplier;

return value;

}


int main()
{
double d1 = 5.50624235984287224709987640380859375e-17;
double d2 = atof("1980704062856.605712890625");


printf("%.20e\n", d1);
printf("%.20e\n", roundToSignificantDigits(d1, 15));
printf("%.20e\n", (float)d1);
printf("%.20e\n", (float)roundToSignificantDigits(d1, 7));

printf("%.20e\n", d2);
printf("%.20e\n", roundToSignificantDigits(d2, 15));
printf("%.20e\n", (float)d2);
printf("%.20e\n", (float)roundToSignificantDigits(d2, 15));

return 0;

}


Запустив программу, я на консоле получил те числа, которые и привел выше. Где я вас в чем обманул? Вывести их другим способом я не умею. Скриншот прикрепить тоже. Я выписал их с консоли.
Еще раз говорю, проблема не в программе, а в двоичных числах, которые используются как эквивалент десятичных. Если в программе я допустил ошибку, исправьте. Если вы готовы, мы можем обсудить те числа, которые вы привели в этом коменте.

Я тупо копирую вашу программу. Запускаю ее в онлайн-дебаггере. Получаю тот же результат что и у michael_vostrikov ;)

Округленные числа, приведенные вами выше: 5.50624235984287242318e-17
5.50624235984286995799e-17
Правильные. Есть числа, которые неправильно округляются и вашей программой и моей. Мы обсуждали это ранее.
Рассмотрим вторую группу чисел.
Округленное до 7 значащих цифр число 1980704062856.605712890625= 1.98070406285660571289e+12 равно 1.980704e+12. Запишите это число в doable и получите то же число 1.980704e+12. Т.е. это представимое число. В вашей программе оно округлено до 1.98070406285661010742e+12. Это правильно?

И перестаньте бросаться словами — "вы врете".

Это было намеренно. Чтобы показать один из вариантов, какие мысли вызывают ваши утверждения без доказательств.


Могу повторить.

Для оформления кода на C++ используются три обратные кавычки ```cpp если галочка Markdown установлена, и тег <source lang="cpp"> если нет. Закрываются они соответственно ``` и </source>.


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


Это на будущее. Для данного случая это уже не требуется, так как все равно уже понадобилось несколько сообщений для уточнения.


Запустив программу, я на консоле получил те числа, которые и привел выше. Где я вас в чем обманул?

Я это уже 2 раза повторил. В строке "5.50624235984287E-17" 14 знаков после запятой. В программе, которую вы привели, стоит printf("%.20e\n", d1) — вывод 20 знаков после запятой. Поэтому выводить 14 знаков этот код никак не может. Значит вы запускали какую-то другую программу.


Вывести их другим способом я не умею. Скриншот прикрепить тоже.

Я вам специально дал ссылку на онлайн редактор. Там именно тот код, который вы показываете. Нажмите там кнопку "Run". При запуске он выводит на консоль то, что говорю я. Раз вы не можете разобраться, что вы запускаете на своем компьютере, пишите пожалуйста код там, запускайте, проверяйте, и приводите ссылку здесь. Для получения ссылки там есть кнопка "Share".

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

Ну одно да потому. Вы не привели доказательства. В этих ваших якобы доказательствах вы утверждаете, что моя программа выводит некие числа с 14 знаками после запятой. Но моя программа не может их выводить, в ней задан вывод до 20 знаков. Значит вы запускали не мою программу, а какую-то другую. Возможно вы сделали это по ошибке, я не знаю, но факт в том, что 14 знаков после запятой она не выводит. А значит любые ваши доказательства касательно ее работы, где фигурируют числа с 14 знаками после запятой, неверны. Неверны, значит не относятся к ней, к ее работе, и ничего касательно нее не доказывают. Что вам здесь непонятно? Напишите, я еще подробнее объясню.


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

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

Я вам дал ссылку на онлайн редактор. Напишите пожалуйста там код, который подтверждает ваши слова.
Я ничего не менял в вашей программе. Как она вами была туда помещена, так я ее и запустил. На выходе программы получились те числа, которые вы привели в тексте вашего комента выше. Они полностью совпадают с теми, что получены в результате работы вашей программы. См. Скриншот
Именно эти числа я и приводил в качестве доказательства своей правоты. Что я сделал не так?
Именно эти числа я и приводил в качестве доказательства своей правоты. Что я сделал не так?

Вот в этом комментарии вы писали "На консоль выведено число 5.50624235984287E-17. Неправильно." Но на консоль не выводится такое число, и ваш скриншот это подтверждает. Почему вы утверждали, что оно выводится, если оно не выводится?

Я изменил параметры в вашей программе так И вот что получается. Округление double до 7 значащих цифр для d2 выполняется правильно. Но конвертация в float выполняется с погрешностью. Работая с float мы рассчитываем, что 7-ми значное число будет представлено с минимальной погрешностью. Поэтому, прежде чем конвертировать из double (или другого более широкого формата) в float оно должно быть сначала правильно округлено. Округление через log очень затратно, поэтому в процессоре просто округляют двоичное число с использованием любого известного сценария округления. Мой алгоритм позволяет перейти к float с правильным округлением с минимальными затратами.

Не уходите от ответа. Моя программа правильно выводит или нет?

Да, правильно. Десятичные числа ваша программа обрабатывает правильно. У меня задача стояла несколько по другому. Есть двоичное нормализованное число, допустим float (в double масштаб изменится), допустим 1.11100111011000011110011*2^-78. Нужно преобразовать его так, чтобы новое число было двоичным эквивалентом округленного до N значащих цифр десятичного числа. Вычисление lg очень затратно. Требует большого количества делений и сравнений. Поэтому разработал тот, который и представил. Он рабочий и не требует вычисления lg. Я не нашел в инете программ, где использовалась бы функция frexp для округления.

Я вам уже сказал причину, почему вы их не нашли — frexp есть не во всех языках. И другую причину вам тоже сказали — округление до десятичных знаков в середине вычислений обычно не требуется, это потеря точности, округляют при выводе. Единственное отличие вашего алгоритма от тех, которые есть в интернете — вы вычисляете десятичный логарифм через двоичный. Я верю, что вы его разработали самостоятельно, но ничего нового в нем нет.


Поэтому, прежде чем конвертировать из double (или другого более широкого формата) в float оно должно быть сначала правильно округлено.

По поводу этого. При округлении double во float округление до десятичных знаков не нужно. Максимально близкий результат дает двоичное округление до 23 знаков мантиссы, для этого достаточно проверить 24-й бит. Пример я уже приводил — там где ошибка 33400.

Вы куда-то не туда переобуваетесь ;)
Начали с округления double, теперь решаете проблему потери точности преобразования double to float. Точность float как раз в районе 7 значащих цифр; чем поможет предварительное округление?
Желательно увидеть это в виде программы в онлайн дебаггере.

В результате некоторых вычислений вы получили какое-то double-число: 1.0011… 00011*2^123 (любое). Вам из него надо получить двоичное число, равное ближайшему к правильно округленному до 7 десятичных знаков десятичному числу.


Округление double (~15 значащих десятичных знаков) до 7 знаков вносит некую погрешность (7 знаков, грубо говоря). Разница между правильным и обычным округлением значительно (на несколько десятичных порядков) меньше этой погрешности. Не могу представить задачу, где допустимо внесение погрешности размером в 7 младших десятичных разрядов, и одновременно недопустима погрешность в 1 младший разряд.

"Если при измерении вы получили результат 12.12345, а класс точности прибора 0.01, что нужно сделать с лишними цифрами? Округлить. В результате мы получаем более точное измерение или нет?"


Результатом измерения этим прибором может оказаться любое значение из диапазона 12.0… 12.2 (если грубо). 12.12345 ничем не лучше 12.1. Отбрасывание "лишних" цифр повлияет на скорость обработки данных? На объем занимаемой памяти? Каким образом?

Отбрасывание «лишних» цифр повлияет на скорость обработки данных? На объем занимаемой памяти? Каким образом?

На точность. См. Действия над приближенными числами

А отбрасывание цыфр повышает или понижает точность? Или под термином "точность" подразумевается что-то другое? ;)

Зря я задал 2 вопроса сразу, зря ;)
Отбрасывание цыфр повышает точность расчетов? (Да, Нет, Другой ответ)
Если вы ознакомились со статьей «Что такое точность?», то в рамках рассматриваемой там терминологии можно сказать. Если длинна мантиссы двоичного нормализованного числа не меняется, то любые арифметические преобразования в парадигме IEEE754 не приводят к изменению точности p (precision) представления числа. Если она меняется, например для субнормальных чисел, то точность (precision) уменьшается.
Для десятичных чисел в двоичном коде другая ситуация. Изменение длинны двоичной мантиссы может приводить к потере точности десятичного числа, может повышать точность, а может и не менять ее. Повышение точности в смысле accuracy, т.е. близости к теоретическому результату мы и обсуждали в комментах выше.
Ну и пример того, когда десятичная точность не меняется при сокращении длинны мантиссы. Если число точное (exact) и представимо в мантиссе с p разрядами, то оно представимо и точно (exact) в мантиссе с p+n разрядами. Отсюда, усечение n разрядов двоичной мантиссы приводит к уменьшению precision, но exact и accuracy не меняются. Так, число 0.125 можно представлять 64-мя,24-мя или 3-мя значащими цифрами, его значение не изменится.
Отбрасывание цыфр повышает точность расчетов?

Если длинна мантиссы двоичного нормализованного числа не меняется, то любые арифметические преобразования не приводят к изменению точности p


sizeof(double) == sizeof(double), следовательно, не меняет.

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


При операциях с плавающей запятой sizeof(double) == sizeof(double), следовательно, длина двоичной мантиссы не меняется.

… Так, число 0.125 можно представлять 64-мя,24-мя или 3-мя значащими цифрами, его значение не изменится.


Встретить подобное число при расчётах с floating point — великая удача. Но формату float (double, long double) все равно, сколько внутри двоичных нулей (хоть и все) — от этого он не становится ни меньше, ни быстрее. А выполните деление точного 0.125 на точное 3, то, что получилось, умножьте на точное 3 — и результат уже неточный, так что сильно озабочиваться в двоичное округление нет никакого смысла. Точности в вычислениях это не добавит.
Проверить это утверждение очень легко. Сосчитайте с использованием округления то, что легко проверяется аналитически (sin(pi) через ряд), или сравнением (дважды обращенная матрица равна сама себе).
Вы плохо усвоили, то, что написано мною выше. Т.к. не читали. П вотому что не написали ни одного возражения, кроме набора тривиальных фраз.
Проверить это утверждение очень легко. Сосчитайте с использованием округления то, что легко проверяется аналитически (sin(pi) через ряд), или сравнением (дважды обращенная матрица равна сама себе).

Вы жаждете, чтобы я сам себя «зарезал», а вы постояли в сторонке, а потом сказали — Ну, вот, что я говорил. Нет, батенька, потрудитесь сами. Получите результаты, правильно их интерпретируйте, а затем мы с вами их обсудим. ОК?
Увы, правильной имплементации вашей методики округления (в виде класса, функции, чего угодно) я не знаю :(

На обычном C это выглядит приблизительно так:
не стреляйте в пианиста
#include <stdio.h>
#include <math.h>
int main(void) {
  double x=M_PI;
  printf("calc sin(%.15e)\n",x);
  double s = x;
  int sign = 1;
  int power = 1;
  double c = x;
  double f = 1;

  for (power =3; power< 100; power = power +2)
  {
    sign = -sign; 
    c = c*x*x;
    f = f*(power-1)*power;
    s = s + sign*(c/f);
    printf("%d; %.15e\n",power, s);
  }
  return 0;
}


Запустил на repl.it/languages/c

clang version 7.0.0-3~ubuntu0.18.04.1 (tags/RELEASE_700/final)
 clang-7 -pthread -lm -o main main.c
 ./main
calc sin(3.141592653589793e+00)
3; -2.026120126460176e+00
5; 5.240439134171684e-01
7; -7.522061590362350e-02
9; 6.925270707504691e-03
11; -4.451602382096569e-04
13; 2.114256755795548e-05
15; -7.727858898747279e-07
17; 2.241951027282289e-08
19; -5.289187244469688e-10
21; 1.034774181384365e-11
23; -1.707299030884088e-13
25; 2.292021495202086e-15
27; -1.405396847913007e-16
29; -1.109726693627516e-16
31; -1.112864486590965e-16
33; -1.112835160099258e-16
35; -1.112835403326881e-16
37; -1.112835401524659e-16
39; -1.112835401536661e-16
41; -1.112835401536589e-16
43; -1.112835401536589e-16
45; -1.112835401536589e-16
47; -1.112835401536589e-16
49; -1.112835401536589e-16

Видно, что начиная с 41 степени ряда он сходится к -1.112835401536589e-16 (ожидалось 0, но погрешности округления мешают).


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

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

Ну да… :)
Про Рассела и его чайник слышали? Кто алгоритм придумал, тот и доказывает его работоспособность. Функции magick_round() в вашей программе я не вижу.

Я тоже.

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

Это как-то объяснит невозможность оформления вашей методики округления в виде функции на языке С? ;)

>Почитайте «Что такое точность?»

А Брадиса Вы читали?
Да, вы правы. В Багдаде не спокойно:).
У.Кэхэна читал, Д.Голдберга читал, Харрисона читал, Циммермана читал. Брадиса? Кажется в школе проходили. А в чем дело?
Кажется в школе проходили.

Не таблицы, а учебник.
Дело в том, что вопросы точности были разобраны лет за семьдесят до рождения хренов с горы, на которых Вы ссылаетесь.
Как-то вы неясно излагаете свою мысль. Что? Вопросы точности разработаны так давно, что уже не актуальны? Или вас возмущает факт «рождения хренов с горы»? Что мы обсуждаем то? И где здесь математика?
Меня удивляет, что вы ссылаетесь на какую-то лирику, вместо математики.
Для вас математика — учебник Брадиса. Для меня -Modern Computer Arithmetic Richard P. Brent and Paul Zimmermann
На том простом основании, что все компьютеры работают по теории, изложенной в этом учебнике, а не в учебнике Брадиса. Поэтому мы с вами на разных языках разговариваем.
Компьютеры предназначены для вычислений. Как изменяется точность приближенных чисел при вычислениях — известно очень давно. Компьютер должен работать так, чтобы не вносить дополнительного ухудшения точности. И он, что характерно, с этим справляется.
И он, что характерно, с этим справляется.

Да вот, оказывается, не очень. Зачем-то заменили 32-разрядные компьютеры на 64-разрядные, а теперь и на 128. А еще работают над 256 разрядными? Вам ни кажется это странным? Вы представляете себе это десятичное число с плавающей запятой? А все ради нее, ради точности стараются.
Зачем-то заменили 32-разрядные компьютеры на 64-разрядные, а теперь и на 128.

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

Нет, не представляю.
А все ради нее, ради точности стараются.

Чем докажете?
Производительность достигается распараллеливанием вычислений, а не перемалыванием чисел-монстров. Но, на эту тему я дальше спорить не буду. К моему алгоритму это отношения не имеет.
Производительность достигается распараллеливанием вычислений

А распараллеливание вычислений выполняется в том числе и за счёт большей ширины шины. Про SIMD слышали?
Они абсолютно точные
А если оно точное, то даже записанное в форме с плавающей точкой, оно остается быть точным

А, так вы не в курсе.
Это они в десятичной системе абсолютно точные. А в двоичной системе это периодическая дробь. Потому что в двоичной системе любые дроби периодические, кроме тех, у которых знаменатель степень двойки. И в десятичной любые дроби периодические, кроме тех, у которых знаменатель кратен только 2 или 5, то есть приводится к степени десятки умножением на соответствующий коэффициент.


Если вы возьмете дробь 1/3 и запишете ее в десятичной системе с точностью 53 знака после запятой, она округлится до 0.3333...333. И точно так же дробь 987654321098806/100 в двоичной системе с точностью 53 знака округляется до ближайшего числа. Округленные данные присутствуют изначально, это математическое ограничение, его нельзя обойти, потому что нельзя записать бесконечно много знаков в фиксированной разрядной сетке.

Для случая, когда надо округлить число до определенного знака, в библиотеке <math.h> отдельной функции нет


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

Это работает для случая нулевой экспоненты (без сдвига). Если экспонента двоичного числа существенно превышает количество разрядов двоичной мантиссы, или она отрицательная, определить значение коэффициента 10^E, на который надо разделить число, а потом его умножить, сложная задача. См. 1,2,3.
Статью невозможно читать. Я ожидаю от полемической статьи вполне определенную структуру — «вот так правильно, вот так не правильно, вот обоснование». В идеале можно привести справку с объяснением, почему же IEEE делают неправильно. Что-то в духе «написал им в твиттер, ответили так то».
Сейчас же мне приходится парсить наукообразный поток сознания, чтобы выяснить, что же автору так не понравилось в штатной округлялке. Ну и чтобы выяснить, не NIH ли это синдром.

МаркЁром странности также является то, что автор предлагает Один Единственно Верный Способ округления, хотя даже Википедия знает больше, как то «к ближайшему числу», «банковское», «округление вверх», «округление вниз», и так далее.

Код читать также невозможно. Вот эта карамба из однобуквенных переменных без комментариев, константности и инициализации радостно встречает нас в начале программы:
  double q,x,xr,X;
   unsigned long long int Xr;
   int N,p,E,e,k;

Что тут, куда тут — читатель умный, разберется.

А еще, мне понравилось вот это
X=x*pow(10,k);
— использование одной и той же буквы в обоих регистрах. Ну, чтобы читающему программу было совсем все понятно.
Статья про алгоритм, а не про программу. Эта простенькая программа призвана только продемонстрировать правильность алгоритма. Она самодельная, т.к. я не профессиональный программист. Если есть ошибки в алгоритме, давайте обсудим.
Input a binary precision p=111111
Input a decimal precision N=2

Input a number and press ENTER:
x= 0.011
X= 0.0109999999999999994

Xr= 11e-3
xr=0.0109999999999999994
sh: 1: pause: not found

Расскажите, пожалуйста, что означает сие? Зачем вы спрашиваете какую-нибудь двоичную точность, если далее в коде она нигде не используется? Что за Xr, xr, где собственнно округление?
Точность p, это десятичное число p<=53.
x= 0.011 — это число, которое вы хотите округлить до N=2
X= 0.0109999999999999994 — это десятичное представление двоичного числа, которое получилось после конвертации десятичного x в формат double.
Xr= 11e-3=0.011 — число, полученное округлением до N=2 числа x с целочисленной десятичной мантиссой.
xr=0.0109999999999999994 — двоичное представление числа Xr в формате double

Да, и еще. Округление ведется не до N знаков после точки/запятой, а до N значащих десятичных цифр.
Точность p, это десятичное число p<=53.

А почему не >=1000?
В коде вашего «алгоритма» это десятичное число все равно не используется.
Программа написана для проверки работы алгоритма с переменными типа double. Для этого формата p=53, так что неявно это число присутствует. Любой формат двоичного числа с p<=53, гарантированно даст правильный результат округления до N<=15 значащих десятичных цифр. Если принять p>=1000, то в результате нормализации двоичная мантисса все равно будет округлена до 53 цифр. Поэтому, поскольку программа написана для формата double, гарантировать правильного округления для p>53 мы не можем. Надо перейти к другому, более длинному формату. Но, насколько я знаю, операционных регистров с количеством разрядов p близким к 1000 пока не существует.
Программа написана для проверки работы алгоритма с переменными типа double. Для этого формата p=53, так что неявно это число присутствует.

Т.е. он не нужен.
А зачем тогда запрашивать его у пользователя?

Но, насколько я знаю, операционных регистров с количеством разрядов p близким к 1000 пока не существует.

Библиотеки дл работы с числами любой длины существуют уже много-много лет. Навскидку — GMP, ttmath, MPFR — последняя вообще позиционируется как «библиотека для больших чисел с плавающей точкой и с правильным округлением».
Т.е. он не нужен.
А зачем тогда запрашивать его у пользователя?

Программа тестовая. Она тестирует округление для любого формата в котором p<=53. Вы можете задать p=24 для формата single, или любое другое значение p.
Библиотеки дл работы с числами любой длины существуют уже много-много лет.

Эти программы и библиотеки существуют, но они очень тяжелые и потому используются в крайних случаях. Зачем покупать дорогой автомобиль, если до места — рукой подать?
Программа тестовая. Она тестирует округление для любого формата в котором p<=53. Вы можете задать p=24 для формата single, или любое другое значение p.

Правда, это ничего не изменит, потому что р в программе не используется.

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

Ну так это же не я сетовал на то, что в бинарном представлении как-то маловато разрядов. Это были вы.
Про тяжелость вы сами придумали или есть какие-то данные с полей? Вы вот в своем коде, на минуточку, вызваете библиотечные функции pow и frexp, которые не факт что легенькие.
Правда, это ничего не изменит, потому что р в программе не используется.

Согласен, что касается p, вы правы.
Ну так это же не я сетовал на то, что в бинарном представлении как-то маловато разрядов. Это были вы.

В каком месте нашего диалога я жаловался на малость разрядов в бинарном представлении?
Про тяжелость вы сами придумали или есть какие-то данные с полей?

Может я ошибаюсь, но длинная арифметика, в том числе для чисел с плавающей точкой реализована в десятичном формате с использованием BCD. А этот формат требует существенных программно-аппаратных затрат. Именно поэтому в железе он до сих пор мало где реализован.
pow и frexp, которые не факт что легенькие.

Если отвлечься от программной реализации этих функций, а посмотреть чисто на алгоритм, то frexp легко реализуется аппаратно, т.к. надо просто считать поле двоичной экспоненты, а pow легко реализуется в табличном виде.
длинная арифметика, в том числе для чисел с плавающей точкой реализована в десятичном формате с использованием BCD. А этот формат требует существенных программно-аппаратных затрат. Именно поэтому в железе он до сих пор мало где реализован.


Если некий софтовый функционал до сих пор не реализован в железе, с вероятностью в 146% можно предположить, что он в железе никому не нужен.
У вас же интернет под рукой. Могли бы и узнать, что та же GMP использует для хранения 64 битные целые. То есть, она хранит числа в системе счисления 2^64-1.
Потому что в BCD что-то хранить и обрабатывать расточительно.
И что? Это здесь причем? Мы же не о целых числах говорим, а о числах с плавающей точкой/запятой.
Возможно, при том, что на GMP построена MPFR (см. ссылки выше), а она работает с числами с плавающей запятой. Следовательно, работать числами с плавающей точкой/запятой можно и без BCD.
x=7.123456789098765321 e-89 — десятичное число, которое мы хотели бы округлить до 15 значащих цифр.

А чем вас округление при выводе-то не устраивает?


#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main()
{
    double a = atof("7.123456789098765321e-89");
    printf("%.18e\n", a);
    printf("%.14e\n", a);

    return 0;
}

// gcc 1.c -lm -o 1.bin && ./1.bin
// 7.123456789098765586e-89
// 7.12345678909877e-89
А чем вас округление при выводе-то не устраивает?

Потому, что округлять часто надо в процессе вычислений, которые выполняются над двоичными числами. Иначе десятичные ошибки могут быстро накапливаться.
Чтобы ошибки не накапливались, используют устойчивые вычислительные схемы. Для них можно формально доказать факт устойчивости.
Вот вам простой пример: (9876543210988,06-9876543210987,04)*10=102
Эта вычислительная схема устойчива? И знаков после запятой всего 2. Но посчитайте в формате double и получите 102,14844. (Проверено на Excel).

Зачем?
Почему они не будут накапливаться с округлением, если округление по определению отбрасывает часть разрядов, а значит увеличивает ошибку?

Иначе десятичные ошибки могут быстро накапливаться.

Ошибки вычислений не зависят от формата представления числа. Любое округление по определению не устраняет ошибки а наоборот, вносит их. Таким образом, ваш «правильный» алгоритм округления лишь вносит дополнительные ошибки. Вы можете убедиться в этом самостоятельно, промоделировав вычисления с накоплением ошибок и сравнив результаты.

В институте нам показывали замечательный пример. Численно решить систему из 2 линейных уравнений, используя какой то итерационный метод, и использовать только 3 значащих цифры после запятой. Так вот, решение отличалось от школьного варианта на единицы, то есть ошибка превысила погрешность расчета на 3 порядка.
Секрет был в том, что прямые пересекались под очень острым углом.

Вы правы, когда речь идет о вычислениях в арифметике одной базы. Когда вычисления ведутся только в десятичной или только двоичной арифметике. Но, когда мы десятичную арифметику реализуем в двоичных кодах, все становится не так очевидно.
Всё становится неочевидным, потому что вы используете не те инструменты для вычислений. Не используйте float, используйте decimal или rational. Не используйте Excel, используйте системы компьютерной алгебры.
Но нам же хочется, чтобы все было быстрее и точнее:)

Проверьте, делов то. Посчитайте sin(1) через ряд, или число pi. Элементы ряда сгрузите в массив, массив посортируйте рандомно (чтобы погрешности округления себя показали). 10000 элементов, думаю, хватит. Контрольный расчет повторите с обычными float. И точность, и быстродействие легко проверяется.

А еще лучше сначала pi, а потом sin(pi) ;)
Формулы рядов есть в википедии.
Решите что вам важнее — «быстрее» или «точнее», и на основе этого выбирайте инструмент. Так чтобы оба сразу и без подвохов — нельзя.
Но, когда мы десятичную арифметику реализуем в двоичных кодах, все становится не так очевидно.

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

Я предложил альтернативный вариант округления десятичных чисел, представленных в двоичном коде. И он работает. Это не значит, что стандартные средства эту задачу не решают. Выбор за программистом.
Попробуйте этот вариант.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main()
{
double a = atof(«1.506242359842875009e-27»);
printf("%.18e\n", a);
printf("%.14e\n", a);

return 0;
}
Мне Code::Bloks выдал: 1.50624235984287e-27

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


double a = atof("1.506242359842875009e-27");
double multiplier = pow(10, 27 + 14);
double rounded = floor(a * multiplier + 0.49999999999999994) / multiplier;
printf("%.18e\n", a);
printf("%.18e\n", rounded);
printf("%.14e\n", rounded);

// 1.506242359842874922e-27
// 1.506242359842879944e-27
// 1.50624235984288e-27
Так у double точность примерно 15 десятичных знаков после запятой, почему вы ждете, что он будет 18 знаков на входе правильно обрабатывать?

Поэтому и ожидаешь, что все 15 цифр обещают быть верными.

К сожалению, если округлять число 1.506242359842874900e-27 вашей программой, то мы получим ошибку в другую сторону. Округление даст 1.50624235984288e-27
Но это не проблема программы. Я думаю, это проблема округления 53-й значащей цифры в двоичном числе «бесконечной точности» после конвертации десятичного числа.
К сожалению, если округлять число 1.506242359842874900e-27 вашей программой, то мы получим ошибку в другую сторону. Округление даст 1.50624235984288e-27

Ну так и ваша даст:


Input a binary precision p=53
Input a decimal precision N=15

Input a number and press ENTER:
x= 1.506242359842874900e-27
X= 1.50624235984287492e-27

Xr= 150624235984288e-41
xr=1.50624235984287994e-27

Я думаю, это проблема округления 53-й значащей цифры

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

Спасибо за диалог. Алгоритм мною разрабатывался для аппаратной реализации. Тестовая программа является эмулятором работы железа. Мне было важно убедиться, что алгоритм работает правильно. Что наша дискуссия и подтвердила.
Что выдаст вам ваш компилятор, после
double a = atof(«1.50624235984287501e-17»);
У меня Code::Bloks выдал: 1.50624235984287e-17
Моя тестовая программа выдает 1.50624235984288e-17
Где правда?
Ну ок. А что должна показать эта программа?
Да, для сравнения точности лучше сосчитайте sin(pi) через ряд Тейлора. Комбинация atof-printf не показывает ничего полезного.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации