Одна из серьезных проблем работы с десятичными числами, представленными в двоичном коде, является проблема округления двоичного числа до значения представимого десятичного числа, ближайшего к правильно округленному десятичному числу. Ниже мы обсуждаем эту проблему и даем простой алгоритм правильного округления. Работа алгоритма проиллюстрирована тестовой программой на C++.
Напомним, что представимым десятичным числом является число, значение которого может быть точно представлено двоичным кодом. Так, число 0.125 точно равно двоичному числу 0.001. Можно также утверждать, что значение любого двоичного числа y равно некоторому представимому десятичному числу x. Например, значение двоичного числа y=1.001101*2^-3 равно значению десятичного представимого числа x= 0.150390625.
Будем говорить, что двоичное число y эквивалентно десятичному числу x. И наоборот, десятичное число x эквивалентно двоичному числу y.
Давайте посмотрим, что происходит с десятичным числом, когда оно вводится с консоли, или, когда оно встречается в программе в качестве константы.
Любое десятичное число компилятором преобразуется (конвертируется) в заданный программистом формат. Поскольку быстрее всего в компьютере обрабатываются двоичные числа, то входное десятичное число преобразуется, чаще всего, либо в формат с фиксированной точкой (в том числе в int), либо в один из форматов с плавающей точкой — single, double, или в другой двоичный формат.
Согласно стандарту IEEE754, вещественные числа во внутреннем формате машины представляются в нормализованном двоичном виде. Так, двоичное положительное вещественное число y можно представить как y=b0.b1...bp*2^e (b0?0).
Это же число можно представить как 0.b0b1...bp*2^(e+1) (b0?0), если e+1>0 и 0.b0b1...bp*2^e (b0?0), если e<0.
Далее мы будем придерживаться второго представления, т.к. в нашей, приведенной ниже, тестовой программе на C++ есть функция q=frexp (x, &e), позволяющая в двоичном числе, представленном в виде b0.b1...bp*2^e, определить значение экспоненты e.
Итак, десятичный эквивалент любого двоичного нормализованного числа y =0.b0b1...bp*2^e (b0?0) равен некоторому нормализованному десятичному числу x=0.d0d1...dN...dn*10^E (d0?0).
Например, число y=0.1001101*2^-2 эквивалентно представимому десятичному числу x=0.150390625.
Чтобы из x получить число Xr, равное числу, округленному до N значащих цифр, надо X умножить на коэффициент 10^k, где k=N-E. Это нужно для того, чтобы полученное число содержало целую часть с N значащими цифрами, которое затем можно было округлить до ближайшего целого. Округленное целое число затем надо привести к прежнему масштабу, умножив его на 10^-k. Математически это можно записать следующей формулой:
X=[x*10^k+0.5]*10^-k=[y*10^k+0.5]*10^-k, где []-целая часть числа.
Можно показать, что целое двоичное число B, содержащее m двоичных цифр, равно значению десятичного числа D, которое содержит n=?m*log2? десятичных разрядов, при условии, что B < 10^n. И равно n=n+1, при условии, что B ? 10^n. В расчетах можно принять log2?0.301.
Определим значение k на основе информации, имеющейся в двоичном представлении числа y. В формуле для k точность округления N известна, поэтому надо определить экспоненту E.
Экспоненту E определим из соотношения: E=?e*0.301?.
Осталось учесть следующее обстоятельство. Если x*10^k= X >10^N, то его нужно умножить на 10^(-1) и скорректировать коэффициент k. Получим X=X*10^(-1), k=k-1.
Округленное число будет равно Xr=X*10^(-k).
В результате мы получаем следующий простой алгоритм правильного десятичного округления двоичных вещественных чисел. Алгоритм пригоден для любого формата двоичных чисел с произвольно заданной точностью десятичного округления N.
На входе:
-Точность десятичного округления N;
— двоичное число в формате y= 0.b0b1...bp*2^e (b0?0).
На выходе: Округленное десятичное число X=0.d0d1...dN*10^E.
— 1. Определить экспоненту e двоичного числа y;
2. E=?e*0.3?;
3. k=N-E;
4. X=x*10^k;
5. Если X<10^N, то п.8;
6. X=X*10^-1;
7. k=k-1;
8. Xr=[X+0.5]*10^(-k);
End.
— В приведенном алгоритме число Xr представляет собой представимое десятичное число ближайшее к числу, которое является правильным округлением числа x, которое в свою очередь является десятичным эквивалентом числа y.
Поскольку мы привыкли работать с десятичными числами, то, как правило, входной поток представляет собой именно десятичные числа. На выходе мы хотим получить тоже десятичные числа. Например, как в Excel. Но, после конвертации десятичных чисел в двоичный код, они, как правило, необратимо трансформируются. В результате этого, округления, преобразованных в двоичный код чисел, могут не совпадать с правильным округлением чисел, напечатанных на консоле. Это касается, прежде всего, случаев, когда мы пытаемся округлить десятичное число до максимально возможного количества значащих цифр. Для single, это 7, а для double — 15.
Это хорошо можно исследовать на тестовой программе ниже, которую автор написал на C++.
В тестовой программе, по запросу, на консоль вводится:
-точность N десятичного округления числа X, которое является ближайшим представимым числом двоичного эквивалента числа x;
— число x в произвольной форме.
Под введенным произвольным десятичным числом x печатается число X, которое является ближайшим к x представимым числом (см. скриншот ниже).
Округление выполняется над числом X, т.к. точное значение числа x утеряно при двоичной конвертации.
Возвращается:
— десятичное число в формате Xr =M*10^(N+e), где M — целое десятичное число с N значащими цифрами;
— число xr, которое равно представимому числу, ближайшему к нормализованному двоичному эквиваленту числа X.
На скриншоте:
N=15 — количество десятичных значащих цифр, до которого округляется входное десятичное число.
x=7.123456789098765321 e-89 — десятичное число, которое мы хотели бы округлить до 15 значащих цифр.
X=7.12345678909876559 e-089 — представимое десятичное число, значение которого равно двоичному числу, которое получено в результате конвертации числа x в формат p=53.
Xr= x=712345678909877e-103 — число с целочисленной мантиссой, полученное в результате округления числа X.
xr= x=7.12345678909877e-089 — число, полученное в результате нормализации двоичного эквивалента десятичного числа Xr. Оно является ближайшим к Xr.
Ниже приводится код тестовой программы правильного округления десятичных чисел, представляемых в двоичном коде, на C++.
В программе используется довольно дорогая функция pow(10,k). Но, поскольку количество значений k невелико, вполне можно задействовать таблицу, заранее вычисленных значений 10^k, или лучше 5^k.
Напомним, что представимым десятичным числом является число, значение которого может быть точно представлено двоичным кодом. Так, число 0.125 точно равно двоичному числу 0.001. Можно также утверждать, что значение любого двоичного числа y равно некоторому представимому десятичному числу x. Например, значение двоичного числа y=1.001101*2^-3 равно значению десятичного представимого числа x= 0.150390625.
Будем говорить, что двоичное число y эквивалентно десятичному числу x. И наоборот, десятичное число x эквивалентно двоичному числу y.
Давайте посмотрим, что происходит с десятичным числом, когда оно вводится с консоли, или, когда оно встречается в программе в качестве константы.
Любое десятичное число компилятором преобразуется (конвертируется) в заданный программистом формат. Поскольку быстрее всего в компьютере обрабатываются двоичные числа, то входное десятичное число преобразуется, чаще всего, либо в формат с фиксированной точкой (в том числе в int), либо в один из форматов с плавающей точкой — single, double, или в другой двоичный формат.
Согласно стандарту IEEE754, вещественные числа во внутреннем формате машины представляются в нормализованном двоичном виде. Так, двоичное положительное вещественное число y можно представить как y=b0.b1...bp*2^e (b0?0).
Это же число можно представить как 0.b0b1...bp*2^(e+1) (b0?0), если e+1>0 и 0.b0b1...bp*2^e (b0?0), если e<0.
Далее мы будем придерживаться второго представления, т.к. в нашей, приведенной ниже, тестовой программе на C++ есть функция q=frexp (x, &e), позволяющая в двоичном числе, представленном в виде b0.b1...bp*2^e, определить значение экспоненты e.
Итак, десятичный эквивалент любого двоичного нормализованного числа y =0.b0b1...bp*2^e (b0?0) равен некоторому нормализованному десятичному числу x=0.d0d1...dN...dn*10^E (d0?0).
Например, число y=0.1001101*2^-2 эквивалентно представимому десятичному числу x=0.150390625.
Чтобы из x получить число Xr, равное числу, округленному до N значащих цифр, надо X умножить на коэффициент 10^k, где k=N-E. Это нужно для того, чтобы полученное число содержало целую часть с N значащими цифрами, которое затем можно было округлить до ближайшего целого. Округленное целое число затем надо привести к прежнему масштабу, умножив его на 10^-k. Математически это можно записать следующей формулой:
X=[x*10^k+0.5]*10^-k=[y*10^k+0.5]*10^-k, где []-целая часть числа.
Можно показать, что целое двоичное число B, содержащее m двоичных цифр, равно значению десятичного числа D, которое содержит n=?m*log2? десятичных разрядов, при условии, что B < 10^n. И равно n=n+1, при условии, что B ? 10^n. В расчетах можно принять log2?0.301.
Определим значение k на основе информации, имеющейся в двоичном представлении числа y. В формуле для k точность округления N известна, поэтому надо определить экспоненту E.
Экспоненту E определим из соотношения: E=?e*0.301?.
Осталось учесть следующее обстоятельство. Если x*10^k= X >10^N, то его нужно умножить на 10^(-1) и скорректировать коэффициент k. Получим X=X*10^(-1), k=k-1.
Округленное число будет равно Xr=X*10^(-k).
В результате мы получаем следующий простой алгоритм правильного десятичного округления двоичных вещественных чисел. Алгоритм пригоден для любого формата двоичных чисел с произвольно заданной точностью десятичного округления N.
На входе:
-Точность десятичного округления N;
— двоичное число в формате y= 0.b0b1...bp*2^e (b0?0).
На выходе: Округленное десятичное число X=0.d0d1...dN*10^E.
— 1. Определить экспоненту e двоичного числа y;
2. E=?e*0.3?;
3. k=N-E;
4. X=x*10^k;
5. Если X<10^N, то п.8;
6. X=X*10^-1;
7. k=k-1;
8. Xr=[X+0.5]*10^(-k);
End.
— В приведенном алгоритме число Xr представляет собой представимое десятичное число ближайшее к числу, которое является правильным округлением числа x, которое в свою очередь является десятичным эквивалентом числа y.
Поскольку мы привыкли работать с десятичными числами, то, как правило, входной поток представляет собой именно десятичные числа. На выходе мы хотим получить тоже десятичные числа. Например, как в Excel. Но, после конвертации десятичных чисел в двоичный код, они, как правило, необратимо трансформируются. В результате этого, округления, преобразованных в двоичный код чисел, могут не совпадать с правильным округлением чисел, напечатанных на консоле. Это касается, прежде всего, случаев, когда мы пытаемся округлить десятичное число до максимально возможного количества значащих цифр. Для single, это 7, а для double — 15.
Это хорошо можно исследовать на тестовой программе ниже, которую автор написал на C++.
В тестовой программе, по запросу, на консоль вводится:
-точность N десятичного округления числа X, которое является ближайшим представимым числом двоичного эквивалента числа x;
— число x в произвольной форме.
Под введенным произвольным десятичным числом x печатается число X, которое является ближайшим к x представимым числом (см. скриншот ниже).
Округление выполняется над числом X, т.к. точное значение числа x утеряно при двоичной конвертации.
Возвращается:
— десятичное число в формате Xr =M*10^(N+e), где M — целое десятичное число с N значащими цифрами;
— число xr, которое равно представимому числу, ближайшему к нормализованному двоичному эквиваленту числа X.
На скриншоте:
N=15 — количество десятичных значащих цифр, до которого округляется входное десятичное число.
x=7.123456789098765321 e-89 — десятичное число, которое мы хотели бы округлить до 15 значащих цифр.
X=7.12345678909876559 e-089 — представимое десятичное число, значение которого равно двоичному числу, которое получено в результате конвертации числа x в формат p=53.
Xr= x=712345678909877e-103 — число с целочисленной мантиссой, полученное в результате округления числа X.
xr= x=7.12345678909877e-089 — число, полученное в результате нормализации двоичного эквивалента десятичного числа Xr. Оно является ближайшим к Xr.
Ниже приводится код тестовой программы правильного округления десятичных чисел, представляемых в двоичном коде, на C++.
#include <iostream>
#include <math.h>
#include <stdlib.h>
#include <iomanip>
using namespace std;
int main()
{
double q,x,xr,X;
unsigned long long int Xr;
int N,p,E,e,k;
cout <<"Input a binary precision p=";
cin>>p;
cout <<"Input a decimal precision N=";
cin>>N;
cout <<endl<<"Input a number and press ENTER:"<<"\n"<<"x= ";
cin>>x;
cout<<"X= "<< setprecision(18)<<x << '\n';
q=frexp (x, &e);
E =static_cast <int> (e*0.301);
k=N-E;
if (E<0) //for format xr=d0.d1...dN*10^E (d0?0).
k=k+1;
X=x*pow(10,k);
if (X > pow (10,N)){
X=X/10;
k=k-1;
}
X=X+0.5;
Xr=static_cast <unsigned long long int> (X);
xr=Xr*pow(10,-k);
cout<<endl <<"Xr= "<<Xr<<"e"<<-k<<'\n';
cout<<"xr="<<xr<<'\n';
system("pause");
return 0;
}
В программе используется довольно дорогая функция pow(10,k). Но, поскольку количество значений k невелико, вполне можно задействовать таблицу, заранее вычисленных значений 10^k, или лучше 5^k.
iig
Если способ округления из статьи правильный, значит ли это, что в стандартной функции atof используется неправильный?
Innotor Автор
Функция 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.
Zenitchik
Зачем? Объясните, ради какой практической надобности может понадобиться преобразовывать двоичное число в десятичное, потом округлять, а потом снова преобразовывать в двоичное?
Innotor Автор
Извините, я не совсем правильный привел пример. Вот правильный.
Функция 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.
michael_vostrikov
Это неправда.
Innotor Автор
Вы упустили, что пример приведен для float, у которого p=24. Для такого случая в примере все правильно. Вы же использовали double.
michael_vostrikov
А, да.
Так а чем вас стандартный способ не устраивает, который ниже предложили ("умножение числа и последующее после округления деление")?
iig
Ок.
Округлением обычно занимаются при выводе результатов. printf обычно используют, или
Innotor Автор
Посчитайте в Excel следующее выражение:
1,12345678987658000000E56 + 4,12345678987654000000E56--7,92721801979018000000E+42 = -5,2469135797532E+56.
Вас ответ устраивает?
Если это окончательный результат, то на погрешность можно закрыть глаза, а если результат умножить на E+56, то ответ будет мало походить на ноль.
gbg
Там, где применяются величины с плавающей запятой, алгоритмы работают с заданной точностью, а данные снабжены информацией о числе верных знаков. От того, что вы умножите результат на миллиарды, вы получите ответ с погрешностью, увеличенной в миллиарды раз.
Innotor Автор
Где кроется эта информация о десятичных верных знаках в двоичном числе формата double?
О чем и речь. А если бы перед вычитанием числа были правильно округлены, мы, в нашем примере, получили бы 0. И какое бы число на 0 ни умножай, в результате будет 0.
gbg
Информация о погрешностях известна разработчику алгоритма и программы (иногда это разные люди).
Не всегда ее нужно носить и преобразовывать вместе с числами, так как эта информация просчитывается при доказательстве устойчивости и сходимости, например, той же разностной схемы.
К примеру, для МКР, которым решают классическую задачу — дифференциальное уравнение с частными производными — эллептическое уравление, заданное на квадрате, если разбить этот квадрат сеткой с шагом 0.1, полученное решение будет менее чем на 0.01 отличаться от точного. То есть точность — два знака после запятой.
Эта информация известна мне, как разработчику алгоритма, из теоретических выкладок, я ее в программу даже засовывать не буду, а в сопроводиловке напишу — «программа считает два знака точно, потому что использует схему второго порядка точности».
Когда нужно, чтобы числа правильно считались в символьном смысле, арифметику с плавающей запятой просто не используют — используют либо арифметику с неограниченным числом знаков и рациональными дробями, как матлаб какой-нибудь,
либо арифметику с фиксированной запятой — там правильное округление уже заложено.
iig
Я подозреваю, что при подобных операциях (суммировании массива float со слишком отличающимися порядками) массив нужно сначала сортировать.
Есть ли в этом какой-то практический смысл, в сложении чисел, отличающихся на 14 порядков? Пролететь 1 млрд км туда, потом еще 1 мм, потом 1 млрд км обратно —
перемещение будет 1 мм? ;)
Zenitchik
А практическое-то применение какое? В какой задаче Вы столкнулись с этим вычислением?
Мне что-то подсказывает, что Вы с упорством, достойном лучшего применения, используется IEEE574 не по назначению.
Innotor Автор
А каково назначение 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 и пока проблему не решили.
И потом, стандарт должен удовлетворять потребности всех пользователей решающих разнообразные задачи, а не только тех с которыми вы сталкиваетесь. Уж извините. Накипело. Поэтому, давайте по делу.
Zenitchik
Вычисления, производимые над результатами измерений, разумеется.
Вычисления не будут точнее, чем исходные данные. А в исходных данных у вас никогда не будет столько значимых цифр, сколько входит в мантиссу double.
И точность результата будет у вас уменьшаться при каждом вычислении, независимо от того, в каком формате вы храните данные.
Вы не объяснили, нахрена она нужна.
Innotor Автор
Ну, хорошо. Вот вам пример.
Рассмотрим разность двух десятичных чисел 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.
iig
Попробуйте, в который раз предлагаю, сделать осмысленный эксперимент. Посчитать sin(pi) через ряд Тейлора по обычной методике и по вашей. Если по вашей методике ряд сойдется к нулю значительно быстрее — тогда ок, ее можно применять в прикладных расчетах.
Zenitchik
Замечательно. С какой точностью они известны?
Innotor Автор
Абсолютно точно. Именно эти два числа.
Zenitchik
Очень интересно.
Приведите пример физической величины, которую можно измерить с подобной точностью.
Innotor Автор
Это вопрос для другой темы обсуждения. Мы же обсуждаем влияние десятичного округления двоичных чисел на точность вычислений.
Что касается порядка чисел, приведенных в примере, то я думаю где-нибудь в астрономии, космологии, или в микромире, вы вполне можете столкнуться с подобными величинами.
Zenitchik
От решения этого вопроса зависит целесообразность обсуждения данной темы. О чём Вам здесь уже сказал примерно каждый.
Если Вы лично работаете с абсолютно точными цифрами — вы не должны использовать тип с плавающей точкой, ни IEEE754, ни какой-то другой, какой вы выдумаете. Это как 2*2=4.
Innotor Автор
Я лично, работаю с изучением проблем двоичных вычислений. И пытаюсь как-то улучшить двоичную арифметику, чтобы она была точнее и быстрее. А мне все доказывают, что их устраивает статус-кво. Жаль. У нас разные цели.
Zenitchik
Понимаете, существует хороший инструмент, который предназначен для работы с приближенными значениями.
Вы попытались его использовать, ладно бы для работы, а для баловства, с вроде как точными значениями, и у вас «неожиданно» получилось плохо. Вы вместо того, чтобы взять более подходящий для ваших задач инструмент, развели бурную деятельность по обсиранию указанного инструмента.
michael_vostrikov
У вас было 2 числа, известных с точностью до 2 знаков после запятой, которые при вычитании должны давать 0.02 с точностью до 2 знаков после запятой. После вычитания получилось 0.02 с точностью до 2 знаков после запятой (0.021484375). В чем проблема-то?
gbg
Неправильно, погрешности-то сложатся, погрешность алгебраической суммы равна сумме погрешностей слагаемых, так что допуск будет даже больше —
0.00(9) + 0.00(9), то есть в сумме верным будет уже один знак после запятой.
Это как складывать в терминах «плюс-минус лапоть» — в одном слагаемом у нас плюс лапоть, в другом — минус лапоть, в сумме получаем, что грешим уже на два лаптя.
michael_vostrikov
А, ну формально да, я скорее про ожидания автора говорил.
Innotor Автор
Мы имеем 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
Кто же, думаю, виноват? Компилятор свое дело сделал точно, но числа получились приблизительными. Калькулятор посчитал точно, но ответ еще больше далек от правды. Что же делать? Присмотрелся к ответу, а там две цифры верные. Эврика? Надо отсечь лишние. Как, да просто округлить надо! А как это сделать, это совсем другая история.
Refridgerator
Innotor Автор
Для вас оно может быть точное, а может быть и не точное. Все зависит от его происхождения. Для калькулятора — это точное число, т.к. он ничего не знает об истории его рождения. Его задача, максимально точно сделать вычисления, не внося в них, по возможности, больших искажений, а как их интерпретировать, не его забота.
Zenitchik
С чего Вы это взяли? Когда у нас есть число с плавающей точкой, логично считать, что его погрешность ±половина последнего известного разряда. Так во всех расчётах делают, почитайте хотя бы Брадиса.
Innotor Автор
Если число приближенное, то да, погрешность <=ulp. А если оно точное, то даже записанное в форме с плавающей точкой, оно остается быть точным. Ну, не должен знать калькулятор биографию этого числа.
Zenitchik
Если оно точное, то его НЕ НАДО записывать в формате с плавающей точкой. Но уж если записал, то смирись с тем, что вычислитель будет полагать его приближенным.
iig
Я вижу противоречие, или мне кажется? ;)
Innotor Автор
Вам кажется. Отсекая лишние цифры («мусор») точность повышается.
Zenitchik
Ложь.
iig
Неплохо так ;) Двоемыслие айтишника?
— Нутром чую, Петька, будет 1 литр, до доказать не могу
michael_vostrikov
Так чем вас стандартное округление-то не устраивает?
Innotor Автор
Давайте, как-то определим круг обсуждаемых вопросов.
1. Предложен альтернативный известному алгоритм округления десятичных чисел в двоичном коде. Он работает.
Возражений не?
2. Десятичное округление десятичных чисел в двоичном коде может повысить точность вычислений. Приведен конкретный пример.
Обоснованных аргументов против этого факта пока высказано не было.
3. Вопрос -А на «хрена» это нужно?
См. п.2.
4. Чем меня не устраивает стандартная функция округления?
Если она вас устраивает, значит вы не сталкивались с задачами, где она облегчает жизнь. Пользуйтесь стандартной. Но реализовать стандартную функцию в железе затратно.
michael_vostrikov
Он не альтернативный. Математически он тот же самый. Именно поэтому и вопрос — почему вы говорите, что стандартное округление как-то неправильно работает.
Это не доказано, пример не приведен. Я у вас его несколько раз уже просил, вы уходите от ответа. В примере выше вы пишете, что ответ должен быть 0.02, но он и так 0.02.
См. п.2.
Так я об этом и спрашиваю. И другие вас тоже об этом спрашивают. В каких конкретно задачах ваше округление "облегчает жизнь", а стандартное "не облегчает"?
Почему "умножение + сложение + деление" вдруг стало затратнее "возведение в степень + умножение + условие + умножение + условие + деление + сложение + умножение"? Вычисление числа разрядов и возведение 10 в эту степень я, так и быть, не учитывал.
Innotor Автор
Может быть следующее пояснение расставит все по своим местам.
Согласно Стандарту обмен операндами в компьютере производится через внутренний формат обмена в 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? В моем алгоритме эта задача решена.
gbg
Вы не правы. Double прекрасно кочуют из оперативной памяти в процессор и обратно. Не надо ничего преобразовывать.
Innotor Автор
А вы загляните в IEEE754, там все описано.
Вопрос не в 64 или 32 словах обмена.
Процессор, как правило, вычисляет в расширенном формате, а затем пакует в более компактный. Для 64-х разрядных, это 80- битные и даже 128-битные операционные регистры. Но потом все равно приходится паковать. А именно здесь собака и порылась. Я тут выше уже давал ссылки на последние работы по этой проблеме. Пока то, что предлагается, ну крайне громоздко.
iig
Что за стандарт? Как он применяется на 64-битной архитектуре? Про 16-битную и 8-битную (микроконтроллеры, ага) даже боюсь заикаться.
Не надо так делать.
michael_vostrikov
В таких утверждениях надо приводить ссылки на тот стандарт, который вы имеете в виду, желательно с указанием цитаты.
Я в этом сомневаюсь. Если бы это было так, тип double был бы бесполезен. Представьте, что они бы в integer преобразовывались посередине вычислений.
Поэтому дальнейшие ваши рассуждения ложны, к происходящему в программе они не имеют никакого отношения.
Если вы будете вручную конвертирвать из double во float и обратно с превышением разрядной сетки float, значит у вас плохо написанная программа, а плавающая точка тут ни при чем.
Значит вы неправильно выбрали инструмент, потому что там 30 значащих цифр, а во float влазит 24. А в double это значение нормально влазит. Значит при использовании double оно округлится так, как нужно.
Так и не надо ее использовать для округления, она в моих примерах используется для ввода данных. Округление это
floor(a * multiplier + 0.499...) / multplier
(константа зависит от используемого типа float или double).Посчитать значащие цифры и отнять сколько надо. Алгоритм округления и алгоритм подсчета значащих цифр это разные алгоритмы. Ваше достижение в том, что вы собрали 2 алгоритма вместе? Ну поздравляю, другие программисты тоже так умеют.
Innotor Автор
IEEE Standard for Floating-Point Arithmetic (IEEE 754). Разделы: «Extended and extendable precision formats» и «Interchange formats».
Ну, во-первых, в числе 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 алгоритма я собрал вместе? Подскажите. Честное слово, я не нарочно:).
iig
Там нет такого утверждения.
— эти фразы переводится иначе.
Innotor Автор
Молодой человек! Если вы хотите постигнуть истину, а не самоутвердиться, пишите мне в личку или на мой блог. Я постараюсь найти время ответить вам.
iig
Вопросы я (и не только я) уже задавал, но ответа не увидел. Публичный ответ приблизит к истине не только меня ;)
michael_vostrikov
Там нигде не написано, что 64-битный double преобразуется в 32-битный float.
Не могут.
Там умножается на степень двойки, а не десятки, поэтому число ненулевых десятичных цифр нерелевантно.
Ну я так и сказал — в double оно нормально влазит. Но никакого "гарантированно «влазит» в float" там нет (см. предыдущий пункт).
Как вам уже сказали, число значащих цифр, которым можно доверять, определяется методикой измерений и передается вместе с данными. В одном случае в этом значении может быть погрешность в единицы, в другом в тысячные доли. Число значащих цифр результата, до которых имеет смысл округлять, вычисляется по этой дополнительной информации. У вас оно вообще вводится пользователем.
Я там немного неправильно написал, да. Просто "отнять сколько надо" вряд ли получится.
Вы выдаете определение числа значащих цифр за преимущество своего алгоритма. Но это другой алгоритм, с алгоритмом округления он никак не связан. Кстати, тут вообще не очень понятно, что вы называете "задача решена". То, что число значащих цифр задается пользователем?
Innotor Автор
Я не могу здесь пересказывать вам стандарт IEEE754. Я дал ссылку на Wiki… Там можно найти нужную литературу.
Могут. Десятичное 1.980704*10^13 преобразуется в
1.980703965184E12, в котором первые 7 цифр с погрешностью <=0.5ulp дают число 1.980704.
Естественно, кто кроме пользователя владеет этой информацией. Откуда машине знать до какого количества десятичных цифр надо округлять.
Да, по другому эта задача не решается так просто.
Решена, поскольку очень просто округляет любое десятичное число в двоичном коде до нужного пользователю количества десятичных цифр.
michael_vostrikov
Его не надо пересказывать. Надо привести одну цитату, подтверждающую ваши слова. Судя по тому, что вы уходите от ответа, вы сами ее найти не можете. В стандарте нет такого утверждения, потому что иначе вычисления в double не работали бы.
Посылать туда не знаю куда это неуважение к собеседнику.
Вы писали "мы хотели бы, чтобы в памяти хранилось число 1980704000000". Если нас устраивает отклонение в некоторых пределах, то надо так и писать, но в этом случае подходят оба варианта.
Но это задача и в моем примере решена. Пользователь введет число, программа вычислит степень 10 от этого числа. А после нормализации числа количество значащих цифр и цифр после запятой вообще отличается ровно на единицу. А раз printf умеет ее делать, значит и в своем коде можно сделать так же.
Еще раз. Вы утверждали "Десятичное округление десятичных чисел в двоичном коде может повысить точность вычислений". Я попросил у вас пример.
Изначальное число у нас было 1980704062856.605712890625.
Вы его округлили до 1980704000000, тем самым внесли отклонение на 62856.
Далее вы предлагаете внести отклонение еще на 34816 (1980704000000-1980703965184).
Каким образом это увеличивает точность?
Если мы хотим увеличить точность, надо брать ближайшее к исходному числу. Во float это 1980704096256 (ошибка 33400).
Именно поэтому оно и "отлично от правильно округленного в двоичной арифметике первичного двоичного числа". Потому что первичное двоичное число было совсем не 1980704000000.
А вот если первичное число взять 1980704000000, то оно работает именно так, как вы и ожидаете.
Innotor Автор
Я не могу привести одну цитату. Принцип двоичных вычислений представляет собой взаимосвязанный комплекс аппаратных и программных средств. Который базируется на принципах закрепленных в стандарте.
Стандарт определяет основные форматы ( 3.1.1 Formats ) представления двоичных и десятичных чисел. Сюда входит формат хранения и обмена данными (3.6 Interchange format parameters), который определяет разрядность компьютера. Для двоичных чисел приняты 3 основных формата 32, 64 и 128 разрядов. В этих словах могут храниться как целые числа, так и упакованные числа с плавающей точкой. Как двоичные так и десятичные. Определен также арифметический формат (arithmetic formats), который может как совпадать с базовыми форматами, так и иметь расширенный ( extended precision format) или расширяемый формат (extendable precision format). Последние форматы используются в арифметических вычислениях для повышения точности. Но после всех вычислений они снова упаковываются в формат обмена. Операционные регистры АЛУ всегда имеют бОльшую разрядность чем ячейки памяти, т.к. после распаковки восстанавливается виртуальная единица в нормализованном числе и добавляются сторожевые биты для повышения точности вычислений. Ну, действительно, развивать эту тему дальше, это еще одна статья в коментах.
Как раз нас устроило бы если бы в память можно было записать точное значение 1.980704*10^13. Но, поскольку это число непредставимо в float, в результате мы получаем 1.980703965184E12.
А как работает printf вас устраивает? Тогда можно.
Если при измерении вы получили результат 12.12345, а класс точности прибора 0.01, что нужно сделать с лишними цифрами? Округлить. В результате мы получаем более точное измерение или нет?
Также и в нашем случае, если мы хотим получить точность вычислений до 7 значащих цифр мы свое число должны округлить до 7 значащих цифр. Но, поскольку число не представимо в flooat, мы получаем ближайшее к точно округленному числу представимое число.
Совершенно верно. Но в double хранится двоичное число с какой-то экспонентой и без преобразования в десятичное представление вы не можете точно сказать на что надо умножить и разделить это двоичное число. А моя программа может.
michael_vostrikov
Эта фраза означает, что если есть 64-битная ячейка памяти типа double, то промежуточные вычисления будут в формате 128 бит. При чем тут 32 бита, которые имеют меньшую разрядность?
Прочитайте еще раз то, что я написал. И предыдущие сообщения этой части. Я писал про подсчет значащих цифр. После нормализации он отличается от числа после запятой на единицу. То есть если мы хотим округлить до 3 значащих цифр, это означает 2 цифры после запятой в нормализованном формате.
Так это не точность вычислений, а точность измерений.
Я не уверен, что в метрологии это считается "более точным" значением, но теперь по крайней мере понятно, что вы имели в виду.
Вот исходное число 1980704062856 и было округлено до 7 значащих цифр. Никакой ошибки округлений здесь нет. Вы в одном случае приводите к float одно число, в другом другое, потому и результаты различаются.
Ваша программа не может, она запрашивает эту информацию у пользователя.
Innotor Автор
Все промежуточные вычисления выполняются в более широком формате, а хранятся в памяти в базовом формате обмена. В 32-х разрядных машинах базовым является 32-х разрядное слово.
О каком представлении числа вы говорите? О двоичном или десятеричном? Поскольку внутреннее представление это нормализованное двоичное, то посчитать цифры в нем, нет проблем. Но вам надо определить множитель для десятичного представления. Следовательно без printf вам не обойтись.
Повторюсь еще раз. Число 1980704062856 является представимым в double числом. Представимым, значит точно представлено в выбранном формате. Но оно не представимо в формате float. В результате округления числа double до float мы получаем правильное округление двоичного числа, в соответствии с выбранным сценарием, прописанным в Стандарте. Мы получаем другое представимое число. И оно, не смотря на то, что двоичное округление было верным может не являться ближайшим.
Вернемся к истокам. В результате неких вычислений вы получили число double:1.1100110100101011001010010111110110001000100110110001*2^40. Для нас это первичное число. Никакой информации кроме двоичной мантиссы и экспоненты мы не имеем. Чтобы посмотреть, что это в десятичном виде мы применяем printf. И видим 1980704062856.605712890625. Это первичное десятичное представимое число.
Но нас для расчетов вполне устраивает точность N=7 (или любая другая). Для этого первичное число надо округлить до 7 значащих цифр. В десятичной арифметике мы бы получили 1980704000000. Но оно непредставимо в float. Поэтому, максимум на что мы можем рассчитывать это на ближайшее к этому представимое число, которое можно сохранить в fljat.
А это 1.980703965184E12. В котором 7 первых цифр совпадают с погрешностью <=ulp с идеально округленным числом 1980704000000.
Еще раз повторюсь. Программа не знает с какой точностью вас интересуют вычисления. Эту точность надо задать.
iig
"Если при измерении вы получили результат 12.12345, а класс точности прибора 0.01, что нужно сделать с лишними цифрами? Округлить. В результате мы получаем более точное измерение или нет?"
Результатом измерения этим прибором может оказаться любое значение из диапазона 12.0… 12.2 (если грубо). 12.12345 ничем не лучше 12.1. Отбрасывание "лишних" цифр повлияет на скорость обработки данных? На объем занимаемой памяти? Каким образом?
Innotor Автор
На точность. См. Действия над приближенными числами
iig
А отбрасывание цыфр повышает или понижает точность? Или под термином "точность" подразумевается что-то другое? ;)
Innotor Автор
Почитайте «Что такое точность?»
iig
Зря я задал 2 вопроса сразу, зря ;)
Отбрасывание цыфр повышает точность расчетов? (Да, Нет, Другой ответ)
michael_vostrikov
А, так вы не в курсе.
Это они в десятичной системе абсолютно точные. А в двоичной системе это периодическая дробь. Потому что в двоичной системе любые дроби периодические, кроме тех, у которых знаменатель степень двойки. И в десятичной любые дроби периодические, кроме тех, у которых знаменатель кратен только 2 или 5, то есть приводится к степени десятки умножением на соответствующий коэффициент.
Если вы возьмете дробь 1/3 и запишете ее в десятичной системе с точностью 53 знака после запятой, она округлится до 0.3333...333. И точно так же дробь 987654321098806/100 в двоичной системе с точностью 53 знака округляется до ближайшего числа. Округленные данные присутствуют изначально, это математическое ограничение, его нельзя обойти, потому что нельзя записать бесконечно много знаков в фиксированной разрядной сетке.
Innotor Автор
Потому и нет, что нет удовлетворительных алгоритмов.
Это работает для случая нулевой экспоненты (без сдвига). Если экспонента двоичного числа существенно превышает количество разрядов двоичной мантиссы, или она отрицательная, определить значение коэффициента 10^E, на который надо разделить число, а потом его умножить, сложная задача. См. 1,2,3.
gbg
Статью невозможно читать. Я ожидаю от полемической статьи вполне определенную структуру — «вот так правильно, вот так не правильно, вот обоснование». В идеале можно привести справку с объяснением, почему же IEEE делают неправильно. Что-то в духе «написал им в твиттер, ответили так то».
Сейчас же мне приходится парсить наукообразный поток сознания, чтобы выяснить, что же автору так не понравилось в штатной округлялке. Ну и чтобы выяснить, не NIH ли это синдром.
МаркЁром странности также является то, что автор предлагает Один Единственно Верный Способ округления, хотя даже Википедия знает больше, как то «к ближайшему числу», «банковское», «округление вверх», «округление вниз», и так далее.
Код читать также невозможно. Вот эта карамба из однобуквенных переменных без комментариев, константности и инициализации радостно встречает нас в начале программы:
Что тут, куда тут — читатель умный, разберется.
А еще, мне понравилось вот это — использование одной и той же буквы в обоих регистрах. Ну, чтобы читающему программу было совсем все понятно.
Innotor Автор
Статья про алгоритм, а не про программу. Эта простенькая программа призвана только продемонстрировать правильность алгоритма. Она самодельная, т.к. я не профессиональный программист. Если есть ошибки в алгоритме, давайте обсудим.
berez
Расскажите, пожалуйста, что означает сие? Зачем вы спрашиваете какую-нибудь двоичную точность, если далее в коде она нигде не используется? Что за Xr, xr, где собственнно округление?
Innotor Автор
Точность p, это десятичное число p<=53.
Innotor Автор
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 значащих десятичных цифр.
berez
А почему не >=1000?
В коде вашего «алгоритма» это десятичное число все равно не используется.
Innotor Автор
Программа написана для проверки работы алгоритма с переменными типа double. Для этого формата p=53, так что неявно это число присутствует. Любой формат двоичного числа с p<=53, гарантированно даст правильный результат округления до N<=15 значащих десятичных цифр. Если принять p>=1000, то в результате нормализации двоичная мантисса все равно будет округлена до 53 цифр. Поэтому, поскольку программа написана для формата double, гарантировать правильного округления для p>53 мы не можем. Надо перейти к другому, более длинному формату. Но, насколько я знаю, операционных регистров с количеством разрядов p близким к 1000 пока не существует.
berez
Т.е. он не нужен.
А зачем тогда запрашивать его у пользователя?
Библиотеки дл работы с числами любой длины существуют уже много-много лет. Навскидку — GMP, ttmath, MPFR — последняя вообще позиционируется как «библиотека для больших чисел с плавающей точкой и с правильным округлением».
Innotor Автор
Программа тестовая. Она тестирует округление для любого формата в котором p<=53. Вы можете задать p=24 для формата single, или любое другое значение p.
Эти программы и библиотеки существуют, но они очень тяжелые и потому используются в крайних случаях. Зачем покупать дорогой автомобиль, если до места — рукой подать?
berez
Правда, это ничего не изменит, потому что р в программе не используется.
Ну так это же не я сетовал на то, что в бинарном представлении как-то маловато разрядов. Это были вы.
Про тяжелость вы сами придумали или есть какие-то данные с полей? Вы вот в своем коде, на минуточку, вызваете библиотечные функции pow и frexp, которые не факт что легенькие.
Innotor Автор
Согласен, что касается p, вы правы.
В каком месте нашего диалога я жаловался на малость разрядов в бинарном представлении?
Может я ошибаюсь, но длинная арифметика, в том числе для чисел с плавающей точкой реализована в десятичном формате с использованием BCD. А этот формат требует существенных программно-аппаратных затрат. Именно поэтому в железе он до сих пор мало где реализован.
Если отвлечься от программной реализации этих функций, а посмотреть чисто на алгоритм, то frexp легко реализуется аппаратно, т.к. надо просто считать поле двоичной экспоненты, а pow легко реализуется в табличном виде.
iig
Если некий софтовый функционал до сих пор не реализован в железе, с вероятностью в 146% можно предположить, что он в железе никому не нужен.
gbg
У вас же интернет под рукой. Могли бы и узнать, что та же GMP использует для хранения 64 битные целые. То есть, она хранит числа в системе счисления 2^64-1.
Потому что в BCD что-то хранить и обрабатывать расточительно.
Innotor Автор
И что? Это здесь причем? Мы же не о целых числах говорим, а о числах с плавающей точкой/запятой.
berez
Возможно, при том, что на GMP построена MPFR (см. ссылки выше), а она работает с числами с плавающей запятой. Следовательно, работать числами с плавающей точкой/запятой можно и без BCD.
michael_vostrikov
А чем вас округление при выводе-то не устраивает?
Innotor Автор
Потому, что округлять часто надо в процессе вычислений, которые выполняются над двоичными числами. Иначе десятичные ошибки могут быстро накапливаться.
gbg
Чтобы ошибки не накапливались, используют устойчивые вычислительные схемы. Для них можно формально доказать факт устойчивости.
Innotor Автор
Вот вам простой пример: (9876543210988,06-9876543210987,04)*10=102
Эта вычислительная схема устойчива? И знаков после запятой всего 2. Но посчитайте в формате double и получите 102,14844. (Проверено на Excel).
michael_vostrikov
Зачем?
Почему они не будут накапливаться с округлением, если округление по определению отбрасывает часть разрядов, а значит увеличивает ошибку?
Refridgerator
Ошибки вычислений не зависят от формата представления числа. Любое округление по определению не устраняет ошибки а наоборот, вносит их. Таким образом, ваш «правильный» алгоритм округления лишь вносит дополнительные ошибки. Вы можете убедиться в этом самостоятельно, промоделировав вычисления с накоплением ошибок и сравнив результаты.
iig
В институте нам показывали замечательный пример. Численно решить систему из 2 линейных уравнений, используя какой то итерационный метод, и использовать только 3 значащих цифры после запятой. Так вот, решение отличалось от школьного варианта на единицы, то есть ошибка превысила погрешность расчета на 3 порядка.
Секрет был в том, что прямые пересекались под очень острым углом.
Innotor Автор
Вы правы, когда речь идет о вычислениях в арифметике одной базы. Когда вычисления ведутся только в десятичной или только двоичной арифметике. Но, когда мы десятичную арифметику реализуем в двоичных кодах, все становится не так очевидно.
Refridgerator
Всё становится неочевидным, потому что вы используете не те инструменты для вычислений. Не используйте float, используйте decimal или rational. Не используйте Excel, используйте системы компьютерной алгебры.
Innotor Автор
Но нам же хочется, чтобы все было быстрее и точнее:)
iig
Проверьте, делов то. Посчитайте sin(1) через ряд, или число pi. Элементы ряда сгрузите в массив, массив посортируйте рандомно (чтобы погрешности округления себя показали). 10000 элементов, думаю, хватит. Контрольный расчет повторите с обычными float. И точность, и быстродействие легко проверяется.
iig
А еще лучше сначала pi, а потом sin(pi) ;)
Формулы рядов есть в википедии.
playermet
Решите что вам важнее — «быстрее» или «точнее», и на основе этого выбирайте инструмент. Так чтобы оба сразу и без подвохов — нельзя.
michael_vostrikov
Покажите пример пожалуйста. Те, что вы приводили в статье и в комментариях, нормально считаются и округляются стандартными средствами.
Innotor Автор
Я предложил альтернативный вариант округления десятичных чисел, представленных в двоичном коде. И он работает. Это не значит, что стандартные средства эту задачу не решают. Выбор за программистом.
Innotor Автор
Попробуйте этот вариант.
#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
michael_vostrikov
Так у double точность примерно 15 десятичных знаков после запятой, почему вы ждете, что он будет 18 знаков на входе правильно обрабатывать?
Ближайшее число стандартными средствами округляется правильно.
Innotor Автор
Поэтому и ожидаешь, что все 15 цифр обещают быть верными.
К сожалению, если округлять число 1.506242359842874900e-27 вашей программой, то мы получим ошибку в другую сторону. Округление даст 1.50624235984288e-27
Innotor Автор
Но это не проблема программы. Я думаю, это проблема округления 53-й значащей цифры в двоичном числе «бесконечной точности» после конвертации десятичного числа.
michael_vostrikov
Ну так и ваша даст:
Дак это так и есть, и это именно то, что вам и пытаются тут объяснить. Вы делаете вычисления на границе точности, то есть используете инструменты не по назначению, и удивляетесь результатам. Если нужны вычисления с такой точностью, надо использовать соответствующие библиотеки.
Innotor Автор
Спасибо за диалог. Алгоритм мною разрабатывался для аппаратной реализации. Тестовая программа является эмулятором работы железа. Мне было важно убедиться, что алгоритм работает правильно. Что наша дискуссия и подтвердила.
Innotor Автор
Что выдаст вам ваш компилятор, после
double a = atof(«1.50624235984287501e-17»);
У меня Code::Bloks выдал: 1.50624235984287e-17
Моя тестовая программа выдает 1.50624235984288e-17
Где правда?
iig
Ну ок. А что должна показать эта программа?
Да, для сравнения точности лучше сосчитайте sin(pi) через ряд Тейлора. Комбинация atof-printf не показывает ничего полезного.