Другой способ умножить два числа без использования оператора "*"
Вчера у меня было интересное интервью, где интервьюер задал мне классический вопрос: как мы можем умножить два числа на Java без использования оператора *
. Честно говоря, я не знаю, вызвал ли это стресс, который приходит с интервью, но я не смог придумать какое-либо решение.
После собеседования я вернулся домой и провел через SO ответы. До сих пор вот те, которые я нашел:
Первый метод: использование цикла For
// Using For loop
public static int multiplierLoop(int a, int b) {
int resultat = 0;
for (int i = 0; i < a; i++) {
resultat += b;
}
return resultat;
}
Второй метод: использование рекурсии
// using Recursion
public static int multiplier(int a, int b) {
if ((a == 0) || (b == 0))
return 0;
else
return (a + multiplier(a, b - 1));
}
Третий метод: использование Log10
**// Using Math.Log10
public static double multiplierLog(int a, int b) {
return Math.pow(10, (Math.log10(a) + Math.log10(b)));
}**
Итак, теперь у меня есть два вопроса:
- Есть ли еще один метод, который мне не хватает?
- Неужели тот факт, что я не смог ответить на этот вопрос, доказывает, что мои логические рассуждения недостаточно сильны, чтобы придумывать решения и что я не "вырезана", чтобы быть программистом? Потому что позвольте быть честным, вопрос не казался таким сложным, и я уверен, что большинство программистов легко и быстро найдут ответ.
Ответы
Ответ 1
Я не знаю, должен ли это быть "программирующим вопросом". Но в Maths:
x * y = x / (1 / y) #divide by inverse
Так:
Способ 1:
public static double multiplier(double a, double b) {
// return a / (1 / b);
// the above may be too rough
// Java doesn't know that "(a / (b / 0)) == 0"
// a special case for zero should probably be added:
return 0 == b ? 0 : a / (1 / b);
}
Метод 2 (более "программирование /API"):
Используйте большое десятичное целое число:
new BigDecimal("3").multiply(new BigDecimal("9"))
Есть, вероятно, еще несколько способов.
Ответ 2
Существует метод под названием " Русское крестьянское умножение". Продемонстрируйте это с помощью оператора сдвига,
public static int multiply(int n, int m)
{
int ans = 0, count = 0;
while (m > 0)
{
if (m % 2 == 1)
ans += n << count;
count++;
m /= 2;
}
return ans;
}
Идея состоит в том, чтобы удвоить первое число и вдвое увеличить второй номер, пока второе число не станет равным 1. В процессе, когда второе число становится нечетным, мы добавляем первое число в результат (результат инициализируется как 0). Другая реализация является,
static int russianPeasant(int n, int m) {
int ans = 0;
while (m > 0) {
if ((m & 1) != 0)
ans = ans + n;
n = n << 1;
m = m >> 1;
}
return ans;
}
см.
Ответ 3
Другие затронули вопрос 1 достаточно, что я не собираюсь переделывать его здесь, но я действительно хотел немного затронуть вопрос 2, потому что мне кажется (мне) более интересным.
Итак, когда кто-то задает вам этот вопрос, они меньше озабочены тем, как выглядит ваш код, и больше озабочены тем, как вы думаете. В реальном мире вам никогда не придется писать умножение без оператора *; каждый язык программирования, известный человеку (за исключением Brainfuck, я думаю), имеет умножение, почти всегда с оператором *. Дело в том, что иногда вы работаете с кодом и по какой-либо причине (возможно, из-за раздувания библиотеки из-за ошибок конфигурации из-за несовместимости пакета и т.д.) Вы не сможете использовать библиотеку, к которой вы привыкли. Идея состоит в том, чтобы увидеть, как вы работаете в этих ситуациях.
Вопрос заключается не в том, что вы "вырезаны", чтобы быть программистом; навыки, подобные этим, могут быть изучены. Трюк, который я использую лично, - это думать о том, что именно представляет собой ожидаемый результат для вопроса, который они задают? В этом конкретном примере, как я (и я предполагаю, что вы тоже), изученный в 4 классе в начальной школе, умножение повторного добавления. Поэтому я бы это сделал (и имел в прошлом, у меня был такой же вопрос в нескольких интервью), когда цикл for делал повторное добавление.
Дело в том, что если вы не понимаете, что умножение - это повторное добавление (или любой другой вопрос, на который вас просят ответить), тогда вы просто будете ввернуты. Вот почему я не являюсь большим поклонником этих типов вопросов, потому что многие из них сводятся к мелочам, которые вы либо знаете, либо не знаете, вместо того, чтобы тестировать свои истинные навыки в качестве программиста (упомянутые выше навыки в отношении библиотеки и т.д. могут быть проверены гораздо лучше другими способами).
Ответ 4
TL; DR - сообщите интервьюеру, что повторное изобретательство колеса - плохая идея
Вместо того, чтобы развлекать вопрос интервьюера Code Golf, я бы ответил на другой вопрос:
Блестящие инженеры Intel, AMD, ARM и других производителей микропроцессоров десятилетиями мучились тем, как умножать 32-битные целые числа в наименьшем числе возможных циклов и на самом деле даже способны создать правильный, полный 64-битный результат умножения 32 битных целых чисел без переполнения.
(например, без предварительного литья a
или b
в long
, умножение на 2 ints, например 123456728 * 23456789
переполняется на отрицательное число)
В этом отношении языки высокого уровня имеют только одно задание с такими целыми умножениями, как это, а именно, чтобы получить работу, выполняемую процессором, с минимальным количеством пуха.
Любая сумма Code Golf для тиражирования такого умножения в программном обеспечении IMO является безумием.
Там, несомненно, много hacks
которые могли бы имитировать умножение, хотя многие из них будут работать только в ограниченных диапазонах значений a
и b
(фактически, ни один из трех методов, перечисленных в OP, не работает без ошибок для всех значений a и b, даже если мы игнорировать проблему переполнения). И все будет (на порядок) медленнее, чем инструкция IMUL
.
Например, если либо a
либо b
является положительной мощностью 2, тогда может быть выполнено смещение бита другой переменной влево на журнал.
if (b == 2)
return a << 1;
if (b == 4)
return a << 2;
...
Но это было бы очень утомительно.
В маловероятном случае, когда оператор *
действительно исчезает в течение ночи из спецификации языка Java, в лучшем случае я бы использовал существующие библиотеки, которые содержат функции умножения, например BigInteger.multiply()
, по тем же причинам - многолетнее критическое мышление умы ярче, чем мои, в производстве и тестировании таких библиотек.
BigInteger.multiply
, очевидно, будет надежным до 64 бит и более, хотя возврат результата обратно в 32-битный int снова вызовет проблемы с переполнением.
Проблема с игровым оператором * Code Golf
Там присущи проблемы со всеми тремя решениями, указанными в вопросе ОП:
- Метод A (цикл) не будет работать, если первое число
a
отрицательно.
for (int i = 0; i < a; i++) {
resultat += b;
}
Вернет 0 для любого отрицательного значения a, так как условие продолжения цикла никогда не выполняется
multiplier(100, 1000000)
"main" java.lang.StackOverflowError
- И в методе 3 вы получите ошибки округления с
log10
(не говоря уже о очевидных проблемах с попыткой взять журнал любого числа <= 0). например
multiplier(2389, 123123);
возвращает 294140846
, но фактический ответ 294140847
(последние цифры 9 x 3 означают, что продукт должен заканчиваться на 7)
Даже ответ с использованием двух последовательных операторов деления двойной точности склонен к проблемам округления при повторном повторении двойного результата до целого числа:
static double multiply(double a, double b) {
return 0 == (int)b
? 0.0
: a / (1 / b);
}
например, для значения (int)multiply(1, 93)
возвращает 92, потому что multiply
возвращает 92.99999.... который усечен с возвратом в 32-битное целое число.
И, конечно, нам не нужно упоминать, что многие из этих алгоритмов O(N)
или хуже, поэтому производительность будет ужасной.
Ответ 5
Для полноты:
Math.multiplyExact(int, int):
Возвращает произведение аргументов, вызывая исключение, если результат переполняет int.
если выбрасывание при переполнении допустимо.
Ответ 6
Если у вас нет целочисленных значений, вы можете воспользоваться другими математическими свойствами, чтобы получить произведение из двух чисел. Кто-то уже упоминал log10
, так что здесь немного более неясное:
public double multiply(double x, double y) {
Vector3d vx = new Vector3d(x, 0, 0);
Vector3d vy = new Vector3d(0, y, 0);
Vector3d result = new Vector3d().cross(vx, vy);
return result.length();
}
Ответ 7
Одним из решений является использование битовых операций. Это немного похоже на ранее представленный ответ, но также исключает разделение. У нас может быть что-то вроде этого. Я буду использовать C, потому что я не очень хорошо знаю Java.
uint16_t multiply( uint16_t a, uint16_t b ) {
uint16_t i = 0;
uint16_t result = 0;
for (i = 0; i < 16; i++) {
if ( a & (1<<i) ) {
result += b << i;
}
}
return result;
}
Ответ 8
Вопросы, задаваемые интервьюерами, отражают их ценности. Многие программисты приносят свои навыки головоломки и математические соображения, и они считают, что эти навыки составляют лучших программистов.
Они ошибаются. Лучшие программисты работают над самым важным, а не самым интересным битом; сделать простой, скучный технический выбор; пиши понятно; думать о пользователях; и избегать глупых обходов. Хотелось бы, чтобы у меня были эти навыки и тенденции!
Если вы можете сделать несколько из этих вещей, а также выработать рабочий код, вам потребуются многие команды разработчиков. Вы можете быть суперзвездой.
Но что вы должны делать в интервью, когда вы в тупике?
Задавайте уточняющие вопросы. ("Какие числа?" "Какой язык программирования это не имеет умножения?" И, не будучи грубым: "Зачем я это делаю?") Если, как вы подозреваете, вопрос просто немой головоломки, не влияющие на реальность, эти вопросы не дадут полезных ответов. Но здравый смысл и желание попасть на "проблему, стоящую за проблемой", являются важными инженерными достоинствами.
Лучшее, что вы можете сделать в плохом интервью, - это продемонстрировать свои сильные стороны. Признание их зависит от вашего интервьюера; если они этого не сделают, что их потеря. Не обескураживайте. Есть и другие компании.
Ответ 9
Используйте BigInteger.multiply
или BigDecimal.multiply
если необходимо.