Удвоение до дроби
Иногда полезно представлять десятичные числа в виде дробей в C#. Используя простой алгоритм, мы можем аппроксимировать десятичные значения к дробному представлению.
Прямой алгоритм
Прямой способ превратить десятичное число в дробь — записать число как дробь 10. Что это значит? Возьмем, к примеру, 0,4. Это то же самое, что сказать 4/10, и, уменьшая, мы получаем дробь 2/5. Имеете около 0.125? То есть 125/1000, что уменьшается до 1/8.
А как насчет 0,753543? Технически это то же самое, что и 753543/1000000, который мы также можем уменьшить. Как насчет 0,7535234236233? Начинать с прямой дроби становится немного смешно на этом этапе.
Но возьмем, к примеру, 0,7535234236233. мы можем округлить число до 0,75, что равно 3/4. Таким образом, 3/4 не является точной дробью, но это близкое приближение.
Таким образом, наш алгоритм может быть более эффективным, просто сосредоточившись на получении дроби, которая находится в определенном диапазоне от фактического значения. Приближение.
Алгоритм аппроксимации
Один алгоритм, который мне очень понравился.
Этот алгоритм довольно прост в выполнении. Ниже я попытаюсь объяснить это:
Для заданного десятичного числа 3,43 отделите целое число (3) от десятичной части (0,43). Используя десятичную дробь, вычисляем новое значение. Назовем его значением «x». Полученная фракция составит 1/(3+х). Что такое x? «x» — это тот же алгоритм, применяемый к результату 1/0.43, который в данном случае равен 2.325…etc.
Как вы можете сказать, это идеальное место для реализации рекурсивного алгоритма, поскольку значение «x» является результатом той же функции. Но есть две важные вещи, которые мы должны решить, прежде чем осуществлять его.
Во-первых, это важнейшее правило рекурсивной функции: когда мы останавливаемся? Как описано на математической странице, хорошее место для остановки — это когда значение (1 / десятичная часть) действительно мало. Настолько маленький, что его можно считать равным 0. Затем остальная часть вызова может закончить сложение накопленных значений до тех пор, пока мы не получим окончательное (1 / целое число + x) значение.
Однако вот у нас есть еще одна проблема. «x» — дробь (так как результатом нашей функции является превращение десятичной дроби в дробь). Как мы можем добавить x к этому значению, не получив снова десятичную дробь? Что еще более важно, как только мы добавим его, мы получим что-то вроде 1 / (8 / 3). Используя простую дробную математику, мы знаем, что ответ равен 3 / 8, но как мы можем это реализовать?
Именно здесь появляется объектно-ориентированный стиль C#.
Реализация
Есть еще несколько вещей, которые следует учитывать перед реализацией кода C#. Мы договорились, что рекурсия прекратится, когда (1 / десятичное значение) станет слишком маленьким. Но что слишком мало? В этом случае мы будем использовать Double.Epsilon по умолчанию и предоставить перегрузку для пользователя, чтобы указать свои собственные границы.
Проблема сейчас в том, что значения никогда не будут меньше Эпсилона, если они на самом деле не равны нулю (или, по крайней мере, не вычисляются как ноль). Мы можем решить эту проблему, предоставив максимальное количество рекурсивных вызовов. При каждом вызове значение уменьшается, как только оно достигает 0, рекурсивный вызов будет возвращать 0, несмотря ни на что. С помощью этих двух вещей мы теперь можем реализовать алгоритм. Помните, что нам нужна структура для представления дробей.
Фактический код для преобразования десятичного числа в дробь прост, но немного длинный.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
using System; using System.Collections.Generic; using System.Text; namespace nookery.ru { class DecimalToFraction { public struct Fraction { public int Numerator; public int Denominator; public static Fraction Zero = new Fraction(0, 0); public Fraction(int numerator, int denominator) { this.Numerator = numerator; this.Denominator = denominator; //Если знаменатель отрицательный... if (this.Denominator < 0) { //...move the negative up to the numerator this.Numerator = -this.Numerator; this.Denominator = -this.Denominator; } } public Fraction(int numerator, Fraction denominator) { //разделить числитель на дробь знаменателя this = new Fraction(numerator, 1) / denominator; } public Fraction(Fraction numerator, int denominator) { //умножьте числовую дробь на 1 над знаменателем this = numerator * new Fraction(1, denominator); } public Fraction(Fraction fraction) { this.Numerator = fraction.Numerator; this.Denominator = fraction.Denominator; } private static int GetGCD(int a, int b) { //вернем абсолютное значение a = Math.Abs(a); b = Math.Abs(b); //Возвращаем наибольший общий знаменатель между двумя целыми числами while (a != 0 && b != 0) { if (a > b) a %= b; else b %= a; } if (a == 0) return b; else return a; } private static int GetLCD(int a, int b) { //Возвращаем наименьший общий знаменатель между двумя целыми числами return (a * b) / GetGCD(a, b); } public Fraction ToDenominator(int targetDenominator) { //Умножьте дробь на множитель, чтобы получить знаменатель //соответствуют целевому знаменателю Fraction modifiedFraction = this; //Не может уменьшиться до меньших знаменателей if (targetDenominator < this.Denominator) return modifiedFraction; //Целевой знаменатель должен быть множителем текущего знаменателя if (targetDenominator % this.Denominator != 0) return modifiedFraction; if (this.Denominator != targetDenominator) { int factor = targetDenominator / this.Denominator; modifiedFraction.Denominator = targetDenominator; modifiedFraction.Numerator *= factor; } return modifiedFraction; } public Fraction GetReduced() { //Уменьшить дробь до самых низких сроков Fraction modifiedFraction = this; //В то время как числитель и знаменатель имеют наибольший общий знаменатель, //продолжайте делить оба на него int gcd = 0; while (Math.Abs(gcd = GetGCD(modifiedFraction.Numerator, modifiedFraction.Denominator)) != 1) { modifiedFraction.Numerator /= gcd; modifiedFraction.Denominator /= gcd; } //Убедитесь, что в числителе есть только один отрицательный знак if (modifiedFraction.Denominator < 0) { modifiedFraction.Numerator = -this.Numerator; modifiedFraction.Denominator = -this.Denominator; } return modifiedFraction; } public Fraction GetReciprocal() { //Переверните числитель и знаменатель return new Fraction(this.Denominator, this.Numerator); } public static Fraction operator +(Fraction fraction1, Fraction fraction2) { //Проверьте, равна ли какая-либо дробь нулю if (fraction1.Denominator == 0) return fraction2; else if (fraction2.Denominator == 0) return fraction1; //Получить наименьший общий знаменатель int lcd = GetLCD(fraction1.Denominator, fraction2.Denominator); //Преобразование дробей fraction1 = fraction1.ToDenominator(lcd); fraction2 = fraction2.ToDenominator(lcd); //Возвращаемая сумма return new Fraction(fraction1.Numerator + fraction2.Numerator, lcd).GetReduced(); } public static Fraction operator -(Fraction fraction1, Fraction fraction2) { //Получить наименьший общий знаменатель int lcd = GetLCD(fraction1.Denominator, fraction2.Denominator); //Transform the fractions fraction1 = fraction1.ToDenominator(lcd); fraction2 = fraction2.ToDenominator(lcd); //Разница в возврате return new Fraction(fraction1.Numerator - fraction2.Numerator, lcd).GetReduced(); } public static Fraction operator *(Fraction fraction1, Fraction fraction2) { int numerator = fraction1.Numerator * fraction2.Numerator; int denomenator = fraction1.Denominator * fraction2.Denominator; return new Fraction(numerator, denomenator).GetReduced(); } public static Fraction operator /(Fraction fraction1, Fraction fraction2) { return new Fraction(fraction1 * fraction2.GetReciprocal()).GetReduced(); } public double ToDouble() { return (double)this.Numerator / this.Denominator; } public override string ToString() { return Numerator + "/" + Denominator; } } public static Fraction ToFraction(double number) { return ToFraction(number, double.Epsilon); } public static Fraction ToFraction(double number, double accuracy) { int passes = 10; return Helper(number, accuracy, passes); } private static Fraction Helper(double number, double accuracy, int passes) { if (number == 0 || passes == 0) return Fraction.Zero; else { int wholeNumber = (int)number; double decPart = number - wholeNumber; if (1 / number <= accuracy) return Fraction.Zero; Fraction wholeNumberFraction = new Fraction(wholeNumber, 1); Fraction denominator = Helper(1 / decPart, accuracy, passes - 1); denominator = wholeNumberFraction + denominator; if (wholeNumber == 0) return denominator; else return new Fraction(1, denominator); } } } } |