Преобразовать System.Double в обыкновенную дробь
От: Mak_71_rus Россия  
Дата: 03.09.10 14:32
Оценка:
Здравствуйте!
Помогите пожалуйста решить задачу: нужно преобразовать число типа System.Double в обыкновенную дробь, т.е. найти числитель и знаменатель этой дроби (они имеют тип System.Numerics.BigInteger).

У меня есть только такая мысль:
1) выделить целую часть и записать её во временной переменной типа System.Numerics.BigInteger;
2) дробную часть умножить на 10^38 (значок ^ обозначает возведение в степень) и записать полученное значение в числитель;
3) знаменателю присвоить значение 10^38;
4) сократить полученную дробь;
5) временную переменную умножить на знаменатель, потом прибавить числитель и результат записать в числитель.

Мне кажется, что можно сделать более рационально.
Re: Преобразовать System.Double в обыкновенную дробь
От: Пельмешко Россия blog
Дата: 03.09.10 14:43
Оценка: 19 (3)
Здравствуйте, Mak_71_rus, Вы писали:

M__>Мне кажется, что можно сделать более рационально.


Хз, я бы сделал так:

Re[2]: Преобразовать System.Double в обыкновенную дробь
От: bornkiller  
Дата: 03.09.10 15:36
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>Здравствуйте, Mak_71_rus, Вы писали:


M__>>Мне кажется, что можно сделать более рационально.


П>Хз, я бы сделал так:



П>n = 5559693739988877 / 117813617286336735328947744983549524942387739291963003550715137987531686415893111198651827698013002

П>806801277832312516350875264462890216077716912492143885762152213966634919844430677422637872640242124772443478429380665770
П>43117995647400274369612403653814737339068225047641453182709824206687753689912418253153056587776

Что-то приведенное число не выглядит большим единицы

Если задача учебная, то я бы посмотрел сюда.
Re[2]: Преобразовать System.Double в обыкновенную дробь
От: Mak_71_rus Россия  
Дата: 03.09.10 15:41
Оценка:
Спасибо! Очень хороший пример. Только возник вопрос: зачем выделять специальные поля

private static readonly BigRational s_brZero = new BigRational(BigInteger.Zero);
private static readonly BigRational s_brOne = new BigRational(BigInteger.One);
private static readonly BigRational s_brMinusOne = new BigRational(BigInteger.MinusOne);


ведь эти значения могут быть просто представлены с помощью числителя и знаменателя.
Re: Преобразовать System.Double в обыкновенную дробь
От: Neco  
Дата: 03.09.10 15:50
Оценка:
Здравствуйте, Mak_71_rus, Вы писали:

M__>Помогите пожалуйста решить задачу: нужно преобразовать число типа System.Double в обыкновенную дробь, т.е. найти числитель и знаменатель этой дроби (они имеют тип System.Numerics.BigInteger).

Вероятно я чего-то не понимаю, но десятичную дробь всегда можно преобразовать в обыкновенную, разве нет?
ну например 1.234 будет 1234/1000. Можно начать сокращать, но это уже обыкновенная дробь и в принципе для условия задачи этого хватает.
Соот-но и алгоритм элементарный — надо только посчитать количество знаков после нуля. Сделать это можно конвертнув в строку и найдя точку, либо умножая на десять пока dbl != (int)dbl. Может ещё как-то, но это просто навскидку.
Сложность я вижу только в том, что double он неточный и в связи с этим разные косяки будут возникать. Но это уже другой вопрос, кажется.
всю ночь не ем, весь день не сплю — устаю
Re[2]: Преобразовать System.Double в обыкновенную дробь
От: Mak_71_rus Россия  
Дата: 03.09.10 16:31
Оценка:
Здравствуйте, Neco, Вы писали:

N>Здравствуйте, Mak_71_rus, Вы писали:


M__>>Помогите пожалуйста решить задачу: нужно преобразовать число типа System.Double в обыкновенную дробь, т.е. найти числитель и знаменатель этой дроби (они имеют тип System.Numerics.BigInteger).

N>Вероятно я чего-то не понимаю, но десятичную дробь всегда можно преобразовать в обыкновенную, разве нет?
N>ну например 1.234 будет 1234/1000. Можно начать сокращать, но это уже обыкновенная дробь и в принципе для условия задачи этого хватает.
N>Соот-но и алгоритм элементарный — надо только посчитать количество знаков после нуля. Сделать это можно конвертнув в строку и найдя точку, либо умножая на десять пока dbl != (int)dbl. Может ещё как-то, но это просто навскидку.
N>Сложность я вижу только в том, что double он неточный и в связи с этим разные косяки будут возникать. Но это уже другой вопрос, кажется.

Ну а если это 1,237684534567*10^-308 ? Просто умножение на 10^308 длится долго, вот я думаю как бы эффективнее выполнить преобразование.
Re[2]: Преобразовать System.Double в обыкновенную дробь
От: Пельмешко Россия blog
Дата: 03.09.10 21:25
Оценка:
Здравствуйте, Neco, Вы писали:

N>Вероятно я чего-то не понимаю, но десятичную дробь всегда можно преобразовать в обыкновенную, разве нет?


Десятичную — без проблем. Но дело в том, что double хранит не десятичные дроби, а двоичные.
Re[3]: Преобразовать System.Double в обыкновенную дробь
От: Пельмешко Россия blog
Дата: 03.09.10 21:45
Оценка:
Здравствуйте, bornkiller, Вы писали:

B>Что-то приведенное число не выглядит большим единицы


А слона то я и не заметил...
Однако и глючные либы выкладывают ребята из BCL:

var a = new Numerics.BigRational(0.5D);
var b = new Numerics.BigRational(0.5M);

Console.WriteLine("a = {0}", a);
Console.WriteLine("a = {0}D", (double)a);
Console.WriteLine("a = {0}M", (decimal) a);
Console.WriteLine("b = {0}", b);
Console.WriteLine("b = {0}D", (double)b);
Console.WriteLine("b = {0}M", (decimal)b);

Результат:
a = 1/2743062034396844341627968125593604635037196317966166035056000994228098690879836473582587849768
1813968066423626689360558724790919313723239516120518591228351498072493503550031322677950988959670123
2075627063117989759579697696445408449514637925019572810613022629828775479492107003690307184303032465
1025760256
a = 3,6455610097782E-304D
a = 0M
b = 1/2
b = 0,5D
b = 0,5M

То есть импорт из double, мягко говоря, некорректный ни разу...
Прошу прощения, дерьмо посоветовал...
Re[4]: Преобразовать System.Double в обыкновенную дробь
От: Mak_71_rus Россия  
Дата: 04.09.10 05:11
Оценка:
Здравствуйте, Пельмешко, Вы писали:


П>Однако и глючные либы выкладывают ребята из BCL:


П>То есть импорт из double, мягко говоря, некорректный ни разу...

П>Прошу прощения, дерьмо посоветовал...

Да ладно, главное идея, а она там такова: сначала нужно расчленить Double на мантиссу, знак и степень, а потом на основании этих значений получить числитель и знаменатель дроби. Вопрос в том, имеет ли это смысл, ведь степень всё равно может оказаться -308. Тут по любому на 10^308 придётся умножать. А расчленять double — тоже затратно. Не эффективнее ли работать по алгоритму из первого поста?
Re: Преобразовать System.Double в обыкновенную дробь
От: Васильич  
Дата: 04.09.10 08:25
Оценка: 34 (6)
Здравствуйте, Mak_71_rus, Вы писали:

M__>Здравствуйте!

M__>Помогите пожалуйста решить задачу: нужно преобразовать число типа System.Double в обыкновенную дробь, т.е. найти числитель и знаменатель этой дроби (они имеют тип System.Numerics.BigInteger).

M__>У меня есть только такая мысль:

M__>1) выделить целую часть и записать её во временной переменной типа System.Numerics.BigInteger;
M__>2) дробную часть умножить на 10^38 (значок ^ обозначает возведение в степень) и записать полученное значение в числитель;
M__>3) знаменателю присвоить значение 10^38;
M__>4) сократить полученную дробь;
M__>5) временную переменную умножить на знаменатель, потом прибавить числитель и результат записать в числитель.

Есть очень простой алгоритм, позволяющий приблизить десятичную дробь простой дробью с нужной точностью. Он в свое время публиковался еще в "Науке и жизни" с программой для ПМК. Итак (псевдокод):

d = 0.12345; // Исходная дробь, может быть любой
a0 = 0; a1 = 1;
b0 = 1; b1 = 0;

while(не_достигнута_нужная точность)
{
  N = Floor(d);
  a = N * a1 + a0
  b = N * b1 + b0;
  printf("%d / %d = %f\n", a, b, a/b);
  a0 = a1;
  a1 = a;
  b0 = b1;
  b1 = b;
  d = 1/(d - N);
}


Образец вывода для 0.12345:
0/1 = 0
1/8 = 0.125
9/73 = 0.1232876712328767
10/81 = 0.12345679012345678
219/1774 = 0.12344983089064261
229/1855 = 0.1234501347708895
448/3629 = 0.12344998622209975
2021/16371 = 0.12345000305418118
2469/20000 = 0.12345
Re[2]: Преобразовать System.Double в обыкновенную дробь
От: Mak_71_rus Россия  
Дата: 05.09.10 08:30
Оценка:
Здравствуйте, Васильич, Вы писали:

В>Есть очень простой алгоритм, позволяющий приблизить десятичную дробь простой дробью с нужной точностью. Он в свое время публиковался еще в "Науке и жизни" с программой для ПМК. Итак (псевдокод):


В>
В>d = 0.12345; // Исходная дробь, может быть любой
В>a0 = 0; a1 = 1;
В>b0 = 1; b1 = 0;

В>while(не_достигнута_нужная точность)
В>{
В>  N = Floor(d);
В>  a = N * a1 + a0
В>  b = N * b1 + b0;
В>  printf("%d / %d = %f\n", a, b, a/b);
В>  a0 = a1;
В>  a1 = a;
В>  b0 = b1;
В>  b1 = b;
В>  d = 1/(d - N);
В>}
В>


Алгоритм хорош, только если нужно приближать с небольшой точностью. А double может хранить числа порядка 10^-308 Если в алгоритме задать такую точность, он будет выполняться несколько часов.
Re[5]: Преобразовать System.Double в обыкновенную дробь
От: Pavel Dvorkin Россия  
Дата: 05.09.10 11:30
Оценка:
Здравствуйте, Mak_71_rus, Вы писали:

M__>Да ладно, главное идея, а она там такова: сначала нужно расчленить Double на мантиссу, знак и степень, а потом на основании этих значений получить числитель и знаменатель дроби. Вопрос в том, имеет ли это смысл, ведь степень всё равно может оказаться -308. Тут по любому на 10^308 придётся умножать. А расчленять double — тоже затратно.


Не очень затратно. Правда, на С++

frexp

Gets the mantissa and exponent of a floating-point number.

double frexp(
double x,
int *expptr
);
float frexp(
float x,
int * expptr
); // C++ only
long double frexp(
long double x,
int * expptr
); // C++ only



frexp returns the mantissa. If x is 0, the function returns 0 for both the mantissa and the exponent. If expptr is NULL, the invalid parameter handler is invoked as described in Parameter Validation. If execution is allowed to continue, this function sets errno to EINVAL and returns 0.

Remarks
The frexp function breaks down the floating-point value (x) into a mantissa (m) and an exponent (n), such that the absolute value of m is greater than or equal to 0.5 and less than 1.0, and x = m*2n. The integer exponent n is stored at the location pointed to by expptr.

Example
// crt_frexp.c
// This program calculates frexp( 16.4, &n )
// then displays y and n.


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

int main( void )
{
double x, y;
int n;

x = 16.4;
y = frexp( x, &n );
printf( "frexp( %f, &n ) = %f, n = %d\n", x, y, n );
}

frexp( 16.400000, &n ) = 0.512500, n = 5

Нахождение знака оставляю в качестве самостоятельного упражнения
With best regards
Pavel Dvorkin
Re[3]: Преобразовать System.Double в обыкновенную дробь
От: Аноним  
Дата: 05.09.10 16:28
Оценка:
Здравствуйте, Mak_71_rus, Вы писали:

M__>Алгоритм хорош, только если нужно приближать с небольшой точностью. А double может хранить числа порядка 10^-308 Если в алгоритме задать такую точность, он будет выполняться несколько часов.


Такая точность практически никогда не требуется. Хуже того, если попробовать разложить 0.12344 методом умножения на 10^308 и последующим нахождением НОД, то будет получена дробь вида 12344000000...0000001/100000000...000000, так как 0.12344 не представляется в double прямо в таком виде. Кроме того защищаемый вами алгоритм более затратен по памяти и по процессорному времени. Стоит оно того?
Re[4]: Преобразовать System.Double в обыкновенную дробь
От: Mak_71_rus Россия  
Дата: 08.09.10 14:04
Оценка:
Здравствуйте, Аноним, Вы писали:

12344000000...0000001/100000000...000000, так как 0.12344 не представляется в double прямо в таком виде. Кроме того защищаемый вами алгоритм более затратен по памяти и по процессорному времени. Стоит оно того?

Да нет же!!! Не защищаю я его не в коем случае, это первое, что пришло в голову, я понимаю, что он крайне не рационален. Дело в том, что тип обыкновенная дробь шире типа Double и поэтому хотелось бы чтобы он умел представлять любое значение, которое может представлять Double, как Int32 представляет любое значение Int16.
Re: Преобразовать System.Double в обыкновенную дробь
От: ZAMUNDA Земля для жалоб и предложений
Дата: 09.09.10 09:34
Оценка:
Здравствуйте, Mak_71_rus, Вы писали:

M__>Здравствуйте!

M__>Помогите пожалуйста решить задачу: нужно преобразовать число типа System.Double в обыкновенную дробь, т.е. найти числитель и знаменатель этой дроби (они имеют тип System.Numerics.BigInteger).
Так она и есть простая дробь m*2^(e-1023), +/-m = [1.1125369292536006915451163586662e-308;1.797693134862315907729305190789e+308], e = [0;2047] (насчёт m точно не помню), m, e целые числа.

M__>Мне кажется, что можно сделать более рационально.

дели или умножай на 2 вот пример на VB.Net:

    Sub Main()
        Dim d As Double, dbl As Double, num As Int64, den As Int64

        d = (New Random()).NextDouble() * 1.0E+16 ' задаёшь число.
        dbl = d
        den = 1

        If d < 18014398509481984.0 Then
            Do Until System.Math.Floor(dbl) = dbl
                den *= 2
                dbl *= 2
            Loop
        Else
            Do Until (dbl / 2) - System.Math.Floor(dbl / 2) > 0
                den *= 2
                dbl /= 2
            Loop
        End If

        num = System.Math.Floor(dbl)

        ' Тут можно посчитать НОД и сократить дробь.
        Console.WriteLine(d & "=" & num & "/" & den)
        Console.ReadKey()
    End Sub
Наука изощряет ум; ученье вострит память.
(c) Козьма Прутков
Re[2]: опечатк
От: ZAMUNDA Земля для жалоб и предложений
Дата: 09.09.10 09:38
Оценка:
Про m я намудрил: оно m = [1; 18014398509481983].
Наука изощряет ум; ученье вострит память.
(c) Козьма Прутков
Re[6]: Преобразовать System.Double в обыкновенную дробь
От: Mak_71_rus Россия  
Дата: 09.09.10 17:01
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Здравствуйте, Mak_71_rus, Вы писали:


M__>>Да ладно, главное идея, а она там такова: сначала нужно расчленить Double на мантиссу, знак и степень, а потом на основании этих значений получить числитель и знаменатель дроби. Вопрос в том, имеет ли это смысл, ведь степень всё равно может оказаться -308. Тут по любому на 10^308 придётся умножать. А расчленять double — тоже затратно.


PD>Не очень затратно. Правда, на С++


PD>frexp


PD>Gets the mantissa and exponent of a floating-point number.


PD>double frexp(

PD> double x,
PD> int *expptr
PD>);
PD>float frexp(
PD> float x,
PD> int * expptr
PD>); // C++ only
PD>long double frexp(
PD> long double x,
PD> int * expptr
PD>); // C++ only



PD>frexp returns the mantissa. If x is 0, the function returns 0 for both the mantissa and the exponent. If expptr is NULL, the invalid parameter handler is invoked as described in Parameter Validation. If execution is allowed to continue, this function sets errno to EINVAL and returns 0.


PD>Remarks

PD>The frexp function breaks down the floating-point value (x) into a mantissa (m) and an exponent (n), such that the absolute value of m is greater than or equal to 0.5 and less than 1.0, and x = m*2n. The integer exponent n is stored at the location pointed to by expptr.

PD>Example

PD>// crt_frexp.c
PD>// This program calculates frexp( 16.4, &n )
PD>// then displays y and n.


PD>#include <math.h>

PD>#include <stdio.h>

PD>int main( void )

PD>{
PD> double x, y;
PD> int n;

PD> x = 16.4;

PD> y = frexp( x, &n );
PD> printf( "frexp( %f, &n ) = %f, n = %d\n", x, y, n );
PD>}

PD>frexp( 16.400000, &n ) = 0.512500, n = 5


PD>Нахождение знака оставляю в качестве самостоятельного упражнения


Вот такую frxp() надо реализовать на C#. Думаю использовать структуру, состоящую из double и Int64 c помощбю атрибута задоть нулевое смещение и сдвигами разделять на мантиссу и степень.
Re[3]: Преобразовать System.Double в обыкновенную дробь
От: Ахмед  
Дата: 09.09.10 17:07
Оценка:
Здравствуйте, Mak_71_rus, Вы писали:

M__>Алгоритм хорош, только если нужно приближать с небольшой точностью. А double может хранить числа порядка 10^-308 Если в алгоритме задать такую точность, он будет выполняться несколько часов.


double сам по себе не особо точный, так что алгоритм вполне рабочий для преобразований типа 1/3 в double и обратно.
Re[7]: Преобразовать System.Double в обыкновенную дробь
От: Pavel Dvorkin Россия  
Дата: 10.09.10 05:28
Оценка:
Здравствуйте, Mak_71_rus, Вы писали:


M__>Вот такую frxp() надо реализовать на C#. Думаю использовать структуру, состоящую из double и Int64 c помощбю атрибута задоть нулевое смещение и сдвигами разделять на мантиссу и степень.


Попробуй . Но дело в том, что С-шная frexp есть просто inline функция для команды FPU FXTRACT.

Посмотри, кстати, сюда

http://www.rsdn.ru/forum/cpp/2936605.1.aspx
Автор: cepstr
Дата: 03.05.08
With best regards
Pavel Dvorkin
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.