У меня есть массив чисел, который потенциально может содержать до 8 знаков после запятой, и мне нужно найти наименьшее общее число, на которое я могу их умножить, чтобы все они были целыми числами. Мне это нужно, чтобы все исходные числа можно было умножить в одном масштабе и обработать запечатанной системой, которая будет иметь дело только с целыми числами, а затем я могу получить результаты и разделить их на общий множитель, чтобы получить мои относительные результаты. .
В настоящее время мы проводим несколько проверок чисел и умножаем их на 100 или 1000000, но обработка, выполняемая системой * sealed, может стать довольно дорогой при работе с большими числами, поэтому умножать все на миллион просто ради этого не совсем так. отличный вариант. В качестве приближения скажем, что запечатанный алгоритм становится в 10 раз дороже каждый раз, когда вы умножаете его на 10.
Какой алгоритм является наиболее эффективным, который также даст наилучший возможный результат для выполнения того, что мне нужно, и есть ли математическое имя и / или формула для того, что мне нужно?
* Герметичная система на самом деле не герметична. Я владею / поддерживаю исходный код для него, но его 100000 с лишним строк проприетарной магии, и он был тщательно протестирован на ошибки и производительность, изменение его для работы с плавающими точками не вариант по многим причинам. Это система, которая создает сетку из ячеек X на Y, затем прямоугольники, состоящие из X на Y, отбрасываются в сетку, происходит «проприетарная магия» и результаты выплевываются - очевидно, это чрезвычайно упрощенная версия реальности, но она достаточно хорошее приближение.
Пока есть несколько хороших ответов, и я подумал, как мне выбрать «правильный». Сначала я подумал, что единственный справедливый способ - это создать каждое решение и проверить его производительность, но позже я понял, что чистая скорость - не единственный важный фактор - более точное решение тоже очень важно. Я все равно написал тесты производительности, но в настоящее время я выбираю правильный ответ на основе скорости, а также точности, используя формулу «интуиции».
Мои тесты производительности обрабатывают 1000 различных наборов из 100 случайно сгенерированных чисел. Каждый алгоритм тестируется с использованием одного и того же набора случайных чисел. Алгоритмы написаны на .Net 3.5 (хотя пока будут совместимы с 2.0) Я очень старался сделать тесты максимально честными.
Итак, решение Грега «Умножить на большое число, а затем разделить на НОД» было вторым самым быстрым алгоритмом и дало наиболее точные результаты, поэтому сейчас я называю его правильным.
Я действительно хотел, чтобы решение Decimal.GetBits было самым быстрым, но оно было очень медленным, я не уверен, связано ли это с преобразованием типа Double в Decimal или с маскированием и сдвигом битов. Должен быть аналогичное удобное решение для прямого двойника с использованием BitConverter.GetBytes и некоторых знаний, содержащихся здесь: http://blogs.msdn.com/bclteam/archive/2007/05/29/bcl-refresher-floating-point-types-the-good-the-bad-and-the-ugly-inbar-gazit-matthew- greig.aspx, но мои глаза просто остекленели каждый раз, когда я читал эту статью, и в конечном итоге у меня не хватило времени, чтобы попытаться реализовать решение.
Я всегда открыт для других решений, если кто-то может придумать что-нибудь получше.





Я бы умножил на что-то достаточно большое (100000000 для 8 знаков после запятой), а затем разделил бы на НОД полученных чисел. В итоге вы получите кучу наименьших целых чисел, которые можно передать другому алгоритму. После получения результата повторите процесс в обратном порядке, чтобы восстановить исходный диапазон.
На каком языке ты программируешь? Что-то вроде
myNumber.ToString().Substring(myNumber.ToString().IndexOf(".")+1).Length
даст вам количество десятичных знаков для двойника в C#. Вы можете пропустить через это каждое число и найти наибольшее количество десятичных знаков (x), а затем умножить каждое число на 10 в степени x.
Обновлено: из любопытства, что это за запечатанная система, в которую вы можете передавать только целые числа?
Грег: Хорошее решение, но не станет ли вычисление НОД, которое является обычным для массива из 100+ чисел, немного дороже? И как бы вы это сделали? Легко сделать НОД для двух чисел, но для 100 становится сложнее (я думаю).
Злой Энди: Я программирую в .Net, и решение, которое вы предлагаете, в значительной степени соответствует тому, что мы делаем сейчас. Я не хотел включать его в свой первоначальный вопрос, потому что я надеялся на какое-то нестандартное (или, по крайней мере, мое) мышление, и я не хотел портить ответы людей потенциальным решением. Хотя у меня нет надежной статистики производительности (потому что у меня не было другого метода для сравнения), я знаю, что синтаксический анализ строк был бы относительно дорогим, и я полагал, что чисто математическое решение потенциально может быть более эффективным. Честно говоря, текущее решение для синтаксического анализа строк находится в производстве, и пока не было претензий к его производительности (оно даже находится в производстве в отдельной системе в формате VB6, и там тоже нет никаких претензий). Просто это кажется неправильным, я предполагаю, что это оскорбляет мою чувствительность к программированию - но это вполне может быть лучшим решением.
Тем не менее, я все еще открыт для любых других решений, чисто математических или иных.
Используйте бинарный метод GCD. Кнут сообщает об ускорении на 15% (см. Искусство компьютерного программирования, Том II: получисловые алгоритмы).
В цикле получить мантиссу и показатель степени каждого числа как целые числа. Вы можете использовать frexp для экспоненты, но я думаю, что для мантиссы потребуется битовая маска. Найдите минимальный показатель степени. Найдите самые значащие цифры в мантиссе (перебирайте биты в поисках последней "1") или просто используйте заранее определенное количество значащих цифр. Тогда ваш множитель будет примерно таким, как 2 ^ (numberOfDigits-minMantissa). «Что-то вроде», потому что я не помню смещения / смещения / диапазоны, но думаю, что идея достаточно ясна.
Если вы хотите найти какое-то целое число N, чтобы N * x также было точным целым числом для набора чисел с плавающей запятой, x в данном наборе - все целые числа, тогда у вас есть в основном неразрешимая проблема. Предположим, что x = наименьшее положительное число с плавающей запятой, которое может представлять ваш тип, скажем, 10 ^ -30. Если вы умножите все свои числа на 10 ^ 30, а затем попытаетесь представить их в двоичном формате (иначе почему вы так стараетесь сделать их целыми числами?), То вы потеряете практически всю информацию о других числах из-за переполниться.
Итак, вот два предложения:
Кстати, если вы имеете дело с дробями, а не с числами с плавающей запятой, то это другая игра. Если у вас есть связка дробей a / b, c / d, e / f; и вам нужен наименьший общий множитель N такой, чтобы N * (каждая дробь) = целое число, тогда N = aбc / gcd (a, b, c); и gcd (a, b, c) = gcd (a, gcd (b, c)). Вы можете использовать Алгоритм Евклида, чтобы найти НОД любых двух чисел.
Итак, в основном вы хотите определить количество цифр после десятичной точки для каждого числа.
Было бы проще, если бы у вас было двоичное представление числа. Преобразовываются ли числа из рациональных или научных представлений ранее в вашей программе? Если это так, вы можете пропустить более раннее преобразование, и вам будет намного легче. В противном случае вы можете передать каждое число функции во внешней DLL, написанной на C, где вы могли бы напрямую работать с представлением с плавающей запятой. Или вы можете преобразовать числа в десятичные и поработать с Decimal.GetBits.
Самый быстрый подход, который я могу придумать на месте и следуя вашим условиям, - это найти наименьшую необходимую степень десяти (или 2, или что-то еще), как предлагалось ранее. Но вместо того, чтобы делать это в цикле, сэкономьте некоторые вычисления, выполнив двоичный поиск возможных степеней. Предполагая максимум 8, что-то вроде:
int NumDecimals( double d )
{
// make d positive for clarity; it won't change the result
if ( d<0 ) d=-d;
// now do binary search on the possible numbers of post-decimal digits to
// determine the actual number as quickly as possible:
if ( NeedsMore( d, 10e4 ) )
{
// more than 4 decimals
if ( NeedsMore( d, 10e6 ) )
{
// > 6 decimal places
if ( NeedsMore( d, 10e7 ) ) return 10e8;
return 10e7;
}
else
{
// <= 6 decimal places
if ( NeedsMore( d, 10e5 ) ) return 10e6;
return 10e5;
}
}
else
{
// <= 4 decimal places
// etc...
}
}
bool NeedsMore( double d, double e )
{
// check whether the representation of D has more decimal points than the
// power of 10 represented in e.
return (d*e - Math.Floor( d*e )) > 0;
}
PS: вы бы не передавали цены на ценные бумаги в систему ценообразования опционов, не так ли? У него именно аромат ...
Я думаю, это касается всех случаев.
2.1 * 10/7 -> 3
0.008 * 10^3/2^3 -> 1
Это при условии, что ваш множитель может быть рациональной дробью.
Я тоже об этом думал. Вероятно, вам придется следить за числом, и для любого заданного k, которое работает, вам нужно сделать k снова. Итак, если 2 работает, вам придется продолжать использовать два или степень двойки, чтобы заставить его работать, и отслеживать это и переходить к следующему простому числу.
Вычислите GCD попарно, сначала вычислив n = gcd (a [0], a [1]). Затем n = gcd (n, a [2]), ... итеративно до конца вашего списка.