Замена двух значений переменной без использования третьей переменной

104

Один из самых каверзных вопросов в интервью.

Поменяйте местами значения двух переменных, например a=10и b=15.

Обычно, чтобы поменять местами значения двух переменных, нам нужна третья переменная, например:

temp=a;
a=b;
b=temp;

Теперь необходимо поменять местами значения двух переменных без использования третьей переменной.

Мухаммад Ахтар
источник
76
Вот это да. Плохо подобранный вопрос интервью ИМХО. Это метод, который редко, если вообще когда-либо, полезен на практике. Велика вероятность, что это только запутает оптимизатор компилятора, что приведет к менее эффективному коду, чем «временная подкачка». Если только это место, где вы проходили собеседование, не связано с очень тяжелыми математическими вещами (подумайте: разработка алгоритма шифрования или тому подобное), я не могу представить себе какой-либо веской причины задать такой вопрос.
Дэн Молдинг
10
Действительно, этот вопрос не говорит вам ничего, кроме того, знает ли кандидат этот конкретный трюк, который практически бесполезен в производственном коде. Я полагаю, вы можете встретить случайного волшебника, который поймет это на лету, но такие люди, которые еще не знают трюка, скорее всего, будут довольно редкими.
генеральный директор,
2
Может быть, они хотят отсеять людей, которые думают, что знание таких уловок - вот что делает хорошего программиста? Кроме того, читая о трюке xor, обратите внимание, когда он выйдет из строя (что IMO делает его практически полностью бесполезным для обмена целыми числами общего назначения).
UncleBens, 01

Ответы:

155

Использование алгоритма обмена xor

void xorSwap (int* x, int* y) {
    if (x != y) { //ensure that memory locations are different
       *x ^= *y;
       *y ^= *x;
       *x ^= *y;
    }
}


Зачем тест?

Тест должен убедиться, что x и y имеют разные ячейки памяти (а не разные значения). Это потому что(p xor p) = 0 если и x, и y совместно используют одну и ту же ячейку памяти, когда один из них установлен в 0, оба устанавливаются в 0. Когда оба * x и * y равны 0, все другие операции xor для * x и * y будут равны 0 (поскольку они одинаковы), что означает, что функция установит для * x и * y значение 0.

Если у них одинаковые значения, но не одно и то же место в памяти, все работает должным образом.

*x = 0011
*y = 0011
//Note, x and y do not share an address. x != y

*x = *x xor *y  //*x = 0011 xor 0011
//So *x is 0000

*y = *x xor *y  //*y = 0000 xor 0011
//So *y is 0011

*x = *x xor *y  //*x = 0000 xor 0011
//So *x is 0011


Следует ли это использовать?

В общих случаях нет. Компилятор оптимизирует временную переменную и, учитывая, что замена является обычной процедурой, он должен вывести оптимальный машинный код для вашей платформы.

Возьмем, к примеру, эту программу быстрого тестирования, написанную на C.

#include <stdlib.h>
#include <math.h>

#define USE_XOR 

void xorSwap(int* x, int *y){
    if ( x != y ){
        *x ^= *y;
        *y ^= *x;
        *x ^= *y;
    }
}

void tempSwap(int* x, int* y){
    int t;
    t = *y;
    *y = *x;
    *x = t;
}


int main(int argc, char* argv[]){
    int x = 4;
    int y = 5;
    int z = pow(2,28); 
    while ( z-- ){
#       ifdef USE_XOR
            xorSwap(&x,&y);
#       else
            tempSwap(&x, &y);
#       endif
    }
    return x + y;    
}

Скомпилировано с использованием:

gcc -Os main.c -o swap

Версия xor принимает

real    0m2.068s
user    0m2.048s
sys  0m0.000s

Где в качестве версии с временной переменной принимает:

real    0m0.543s
user    0m0.540s
sys  0m0.000s
Якоби
источник
1
Возможно, стоит объяснить причину этого теста (т.е. что подход тройного xor терпит неудачу, если x и y ссылаются на один и тот же объект).
dmckee --- котенок экс-модератора
1
Я не знаю, как получилось так много голосов, когда код так сломан. Оба свопа в тестируемом сегменте кода полностью неверны. Посмотри внимательно.
SoapBox 03
@SoapBox: Очень хорошая мысль! XOR заменяет нули x и оставляет нетронутым y, а временный обмен оставляет оба значения со значением x. Ой!
Дрю Холл
4
@SoapBox Хороший улов. Исправлено и повторно протестировано, и я не заметил существенной разницы во времени.
Yacoby 03
4
Спрашивающий сказал две переменные, а не целые числа. : P
Plumenator
93

общая форма:

A = A operation B
B = A inverse-operation B
A = A inverse-operation B 

однако вы должны потенциально остерегаться переполнения, а также не все операции имеют инверсию, которая хорошо определена для всех значений, определенных для операции. например, * и / работают, пока A или B не станут 0

xor особенно приятен, поскольку он определен для всех целых чисел и является его собственным обратным

jk.
источник
XOR (X, X) == 0, поэтому xor работает для целых чисел, ЗА ИСКЛЮЧЕНИЕМ, когда два заменяемых значения равны. Я не первый, кто указывает на это, и слишком многие сказали это (здесь и где-то еще), чтобы выделить кого-то в заслугу.
AlanK
85
a = a + b
b = a - b // b = a
a = a - b
Дор
источник
16
Что делать, если a+bпереполнится?
Alok Singhal
2
@Alok: Это не принято во внимание, поэтому это непрактично :)
Дор
4
Если б имеют те же, основные, размеров целочисленных типов (как int, unsigned short...), он все еще работает, в конце концов, даже с переливом, потому что если а + Ь переполняется, то а - Ь будет Underflow. С этими базовыми целочисленными типами значения просто меняются местами.
Патрик Джонмейер,
11
В C использование этого для unsignedцелочисленных типов нормально и работает всегда. Для подписанных типов это вызовет неопределенное поведение при переполнении.
jpalecek
2
@Shahbaz: Даже если целые числа со знаком хранятся в дополнении до 2, поведение при переполнении все еще не определено.
Кейт Томпсон
79

Пока никто не предлагал использовать std::swap.

std::swap(a, b);

Я не использую никаких временных переменных и в зависимости от типа aиb реализации может иметь специализацию, которая тоже не работает. Реализация должна быть написана с учетом того, уместен ли «трюк». Нет смысла повторять догадки.

В более общем плане я, вероятно, хотел бы сделать что-то подобное, поскольку это будет работать для типов классов, позволяющих ADL найти лучшую перегрузку, если это возможно.

using std::swap;
swap(a, b);

Конечно, реакция интервьюера на такой ответ может многое сказать о вакансии.

CB Bailey
источник
конечно, в C ++ 0x swap будет использовать ссылки rvalue, так что будет еще лучше!
jk.
16
Каждый хочет продемонстрировать блестящий xor или другой трюк, которому они научились, с различными оговорками о том, как его использовать, но это, несомненно, лучший ответ.
2
почему бы не std :: swap (a, b); ? Я имею ввиду зачем использовать?
Динаиз
2
@Dinaiz без определяемых std::пользователем swapреализаций для определяемых пользователем типов также доступны для вызова (это означает, что «он будет работать для типов классов, позволяющих ADL найти лучшую перегрузку, если это возможно»).
Кайл Стрэнд
std::swapиспользует дополнительную временную память в своей реализации нет? cplusplus.com/reference/utility/swap
Ниндзя
19

Как уже отмечалось в manu, алгоритм XOR - популярный алгоритм, который работает для всех целочисленных значений (включая указатели, если повезет и приведение типов). Для полноты картины хотелось бы упомянуть еще об одном менее мощном алгоритме с добавлением / вычитанием:

A = A + B
B = A - B
A = A - B

Здесь вы должны быть осторожны с переполнением / недостаточным заполнением, но в остальном все работает так же хорошо. Вы даже можете попробовать это с числами с плавающей запятой / удвоением, если XOR для них не разрешен.

Вилкс-
источник
" все целочисленные значения (включая указатели) " - Что? Нет, указатели не являются целыми числами, и вы не можете выполнять xor значения указателя. Вы также не можете использовать xor для значений с плавающей запятой. Но метод сложения / вычитания имеет неопределенное поведение (для типов со знаком или с плавающей запятой) при переполнении или потере значимости, может терять точность для чисел с плавающей запятой и не может применяться к указателям или другим нечисловым типам.
Кейт Томпсон
@KeithThompson - Хорошо, потеря точности для чисел с плавающей запятой верна, но в зависимости от обстоятельств она может быть приемлемой. Что касается указателей - ну, я никогда не слышал о компиляторе / платформе, где указатели нельзя было бы свободно преобразовывать в целые числа и обратно (конечно, стараясь выбрать целое число правильного размера). Но тогда я не специалист по C / C ++. Я знаю, что это идет вразрез со стандартом, но у меня создалось впечатление, что такое поведение довольно последовательно во всем ...
Vilx
1
@ Vilx-: Почему потеря точности допустима, если ее так легко избежать, используя временную переменную - или std::swap()? Конечно, вы можете преобразовывать указатели в целые числа и обратно (при условии, что существует достаточно большой целочисленный тип, что не гарантируется), но это не то, что вы предлагали; вы подразумевали, что указатели - это целые числа, а не то, что они могут быть преобразованы в целые числа. Да, принятый ответ не будет работать для указателей. Использование ответа Чарльза Бейлиstd::swap действительно единственно правильное.
Кейт Томпсон
1
Прежде всего, вопрос заключался в том, «как поменять местами без использования третьей переменной», и причиной этого был «вопрос интервью». Я просто дал еще один возможный ответ. Во-вторых, хорошо, приношу свои извинения за то, что намекаю, что указатели являются целыми числами. Я обновил ответ, надеясь, что он будет более ясным и правильным. Пожалуйста, поправьте меня, если я все еще ошибаюсь (или обновите ответ самостоятельно). В-третьих, я удалил часть о принятом ответе. Он нигде не использует преобразование указателя в целое число и работает правильно (насколько я понимаю).
Vilx
@KeithThompson: Вопрос может быть изменен, чтобы он касался того, как std::swapможно реализовать без использования третьей переменной.
jxh
10

Глупые вопросы заслуживают соответствующих ответов:

void sw2ap(int& a, int& b) {
  register int temp = a; // !
  a = b;
  b = temp;
}

Единственное хорошее использование registerключевого слова.

MSalters
источник
1
Не является ли объект, объявленный с помощью регистра класса хранения, «переменной»? Также я не уверен, что это хорошее использование регистра, поскольку, если ваш компилятор уже не может оптимизировать это, тогда какой смысл даже пытаться, вы должны либо принять весь мусор, который вы получите, либо написать сборку самостоятельно ;-) Но поскольку Вы скажете, что хитрый вопрос заслуживает хитрого ответа.
Стив Джессоп,
1
Практически все современные компиляторы игнорируют класс хранилища регистров, так как они имеют гораздо лучшее представление о том, к чему часто обращаются, чем вы.
генеральный директор,
6
Обратите внимание, что этот ответ предназначен для интервьюеров, а не для составителей. Он особенно использует тот факт, что интервьюеры, которые задают такие вопросы, на самом деле не понимают C ++. Итак, они не могут отказаться от этого ответа. (и стандартного ответа нет; ISO C ++ говорит об объектах, а не о переменных).
MSalters 01
Ах, я тебя понял. Я искал в стандарте C упоминание переменной как существительного, а не просто значения неконстантного. Я нашел один в n1124, в разделе циклов for, определяющих объем «переменных», объявленных в инициализаторе. Затем я понял, что вопрос был в C ++, и не стал искать, не допустил ли C ++ где-нибудь такую ​​же опечатку.
Стив Джессоп,
3

Обмен двух чисел с использованием третьей переменной будет таким:

int temp;
int a=10;
int b=20;
temp = a;
a = b;
b = temp;
printf ("Value of a", %a);
printf ("Value of b", %b);

Замена двух чисел без использования третьей переменной

int a = 10;
int b = 20;
a = a+b;
b = a-b;
a = a-b;
printf ("value of a=", %a);
printf ("value of b=", %b);
Ашвини
источник
2
#include<iostream.h>
#include<conio.h>
void main()
{
int a,b;
clrscr();
cout<<"\n==========Vikas==========";
cout<<"\n\nEnter the two no=:";
cin>>a>>b;
cout<<"\na"<<a<<"\nb"<<b;
a=a+b;
b=a-b;
a=a-b;

cout<<"\n\na="<<a<<"\nb="<<b;
getch();
}
викас
источник
1
После того, как cout << "Enter the two no=:"я ожидал прочитатьcout << "Now enter the two no in reverse order:"
user253751
Как и в случае с несколькими другими ответами, это имеет неопределенное поведение при переполнении a+bили a-bпереполнении. Также void main()недействителен и <conio.h>нестандартен.
Кейт Томпсон
2

Поскольку исходное решение:

temp = x; y = x; x = temp;

Вы можете сделать его двухслойным, используя:

temp = x; y = y + temp -(x=y);

Затем сделайте его однострочным, используя:

x = x + y -(y=x);
user3258202
источник
1
Неопределенное поведение. yчитается и записывается одним и тем же выражением без промежуточной точки последовательности.
Кейт Томпсон
1

Если вы немного измените вопрос о двух регистрах сборки вместо переменных, вы также можете использовать xchgоперацию как одну опцию, а операцию стека как другую.

rkellerm
источник
1
О какой xchgоперации вы имеете в виду? В вопросе не была указана архитектура процессора.
Кейт Томпсон
1

Учтите a=10, что b=15:

Использование сложения и вычитания

a = a + b //a=25
b = a - b //b=10
a = a - b //a=15

Использование деления и умножения

a = a * b //a=150
b = a / b //b=10
a = a / b //a=15
Венкат
источник
1
Имеет неопределенное поведение при переполнении.
Кейт Томпсон
1
Кроме того, в C ++ он может вести себя нежелательно, если значения не одного типа. Например, a=10и b=1.5.
Мэтью Коул
1
#include <iostream>
using namespace std;
int main(void)
{   
 int a,b;
 cout<<"Enter a integer" <<endl;
 cin>>a;
 cout<<"\n Enter b integer"<<endl;
 cin>>b;

  a = a^b;
  b = a^b;
  a = a^b;

  cout<<" a= "<<a <<"   b="<<b<<endl;
  return 0;
}

Обновление: здесь мы получаем от пользователя два целых числа. Затем мы используем побитовую операцию XOR, чтобы поменять их местами.

Скажем , у нас есть два целых числа a=4и , b=9а затем:

a=a^b --> 13=4^9 
b=a^b --> 4=13^9 
a=a^b --> 9=13^9
Наим Уль Хассан
источник
Добавьте краткое пояснение к вашему ответу для будущих посетителей.
Николай Михайлов
1
Здесь мы получаем от пользователя два целых числа. Затем мы используем побитовую операцию xor, и мы их меняем местами. Скажем, у нас есть два промежуточных числа a = 4 и b = 9, тогда теперь a = a ^ b -> 13 = 4 ^ 9 b = a ^ b -> 4 = 13 ^ 9 a = a ^ b -> 9 = 13 ^ 9
Наим Уль Хассан
1

Вот еще одно решение, но с одним риском.

код:

#include <iostream>
#include <conio.h>
void main()
{

int a =10 , b =45;
*(&a+1 ) = a;
a =b;
b =*(&a +1);
}

любое значение в местоположении a + 1 будет переопределено.

Смельчак
источник
2
Это был полезный ответ, возможно, ценность исчезла.
Биби Тахира
1
Несколько видов неопределенного поведения. *(&a+1)вполне может быть b. void main()является недействительным. <conio.h>нестандартно (и вы им даже не пользуетесь).
Кейт Томпсон
Код был протестирован с другими выходными параметрами, что исключало, как я уже упоминал, риск. Риск переопределения памяти .. Вот и все .. Но это было полезно, поменяв местами две переменные без участия третьей.
DareDevil 06
Хуже всего то, что он может действительно работать несколько раз, поэтому вы можете не сразу понять, что он полностью сломан и выйдет из строя при оптимизации, других компиляторах и т. Д.
avl_sweden
1

Конечно, ответ C ++ должен быть std::swap.

Однако в следующей реализации swap:

template <typename T>
void swap (T &a, T &b) {
    std::pair<T &, T &>(a, b) = std::make_pair(b, a);
}

Или, как однострочный:

std::make_pair(std::ref(a), std::ref(b)) = std::make_pair(b, a);
jxh
источник
0
#include <stdio.h>

int main()
{
    int a, b;
    printf("Enter A :");
    scanf("%d",&a);
    printf("Enter B :");
    scanf("%d",&b);
    a ^= b;
    b ^= a;
    a ^= b;
    printf("\nValue of A=%d B=%d ",a,b);
    return 1;
}
Сиддики
источник
0

это правильный алгоритм обмена XOR

void xorSwap (int* x, int* y) {
   if (x != y) { //ensure that memory locations are different
      if (*x != *y) { //ensure that values are different
         *x ^= *y;
         *y ^= *x;
         *x ^= *y;
      }
   }
}

вы должны убедиться, что ячейки памяти разные, а также фактические значения, потому что A XOR A = 0

Джанлука Геттини
источник
Вам не нужно следить за тем, чтобы значения были разными.
user253751
0

Вы можете сделать .... простым способом ... в пределах одной строки Логика

#include <stdio.h>

int main()
{
    int a, b;
    printf("Enter A :");
    scanf("%d",&a);
    printf("Enter B :");
    scanf("%d",&b);
    int a = 1,b = 2;
    a=a^b^(b=a);
    printf("\nValue of A=%d B=%d ",a,b);

    return 1;
}

или

#include <stdio.h>

int main()
{
    int a, b;
    printf("Enter A :");
    scanf("%d",&a);
    printf("Enter B :");
    scanf("%d",&b);
    int a = 1,b = 2;
    a=a+b-(b=a);
    printf("\nValue of A=%d B=%d ",a,b);

    return 1;
}
МОХАММАД С ХУССЕЙН
источник
1
Неопределенное поведение. В обеих версиях bи читается, и модифицируется в одном выражении без промежуточной точки последовательности.
Кейт Томпсон
0
public void swapnumber(int a,int b){
    a = a+b-(b=a);
    System.out.println("a = "+a +" b= "+b);
}
Ашиш Бхавсар
источник
0

Лучшим ответом было бы использовать XOR, и использовать его в одной строке было бы здорово.

    (x ^= y), (y ^= x), (x ^= y);

x, y - переменные, и запятая между ними вводит точки последовательности, поэтому они не зависят от компилятора. Ура!

Анкит Шарма
источник
0

Давайте посмотрим на простой пример c, чтобы поменять местами два числа без использования третьей переменной.

программа 1:

#include<stdio.h>
#include<conio.h>
main()
{
int a=10, b=20;
clrscr();
printf("Before swap a=%d b=%d",a,b);
a=a+b;//a=30 (10+20)
b=a-b;//b=10 (30-20)
a=a-b;//a=20 (30-10)
printf("\nAfter swap a=%d b=%d",a,b);
getch();
}

Вывод:

До свопа a = 10 b = 20 После свопа a = 20 b = 10

Программа 2: Использование * и /

Давайте посмотрим на другой пример, чтобы поменять местами два числа с помощью * и /.

#include<stdio.h>
#include<conio.h>
main()
{
int a=10, b=20;
clrscr();
printf("Before swap a=%d b=%d",a,b);
a=a*b;//a=200 (10*20)
b=a/b;//b=10 (200/20)
a=a/b;//a=20 (200/10)
printf("\nAfter swap a=%d b=%d",a,b);
getch();
}

Вывод:

До свопа a = 10 b = 20 После свопа a = 20 b = 10

Программа 3: Использование побитового оператора XOR:

Побитовый оператор XOR можно использовать для обмена двумя переменными. XOR двух чисел x и y возвращает число, в котором все биты равны 1, если биты x и y различаются. Например, XOR для 10 (в двоичном формате 1010) и 5 ​​(в двоичном формате 0101) равно 1111, а XOR для 7 (0111) и 5 ​​(0101) равно (0010).

#include <stdio.h>
int main()
{
 int x = 10, y = 5;
 // Code to swap 'x' (1010) and 'y' (0101)
 x = x ^ y;  // x now becomes 15 (1111)
 y = x ^ y;  // y becomes 10 (1010)
 x = x ^ y;  // x becomes 5 (0101)
 printf("After Swapping: x = %d, y = %d", x, y);
 return 0;

Вывод:

После обмена: x = 5, y = 10

Программа 4:

Пока никто не предлагал использовать std :: swap.

std::swap(a, b);

Я не использую какие-либо временные переменные, и в зависимости от типа a и b реализация может иметь специализацию, которой тоже нет. Реализация должна быть написана с учетом того, уместен ли «трюк».

Проблемы с вышеуказанными методами:

1) Подход, основанный на умножении и делении, не работает, если одно из чисел равно 0, поскольку произведение становится 0 независимо от другого числа.

2) Оба арифметических решения могут вызвать арифметическое переполнение. Если x и y слишком велики, сложение и умножение могут выйти за пределы целочисленного диапазона.

3) Когда мы используем указатели на переменную и выполняем замену функций, все вышеперечисленные методы не работают, когда оба указателя указывают на одну и ту же переменную. Давайте посмотрим, что произойдет в этом случае, если оба будут указывать на одну и ту же переменную.

// Побитовый метод на основе XOR

x = x ^ x; // x becomes 0
x = x ^ x; // x remains 0
x = x ^ x; // x remains 0

// Метод на основе арифметики

x = x + x; // x becomes 2x
x = x  x; // x becomes 0
x = x  x; // x remains 0

Посмотрим на следующую программу.

#include <stdio.h>
void swap(int *xp, int *yp)
{
    *xp = *xp ^ *yp;
    *yp = *xp ^ *yp;
    *xp = *xp ^ *yp;
}

int main()
{
  int x = 10;
  swap(&x, &x);
  printf("After swap(&x, &x): x = %d", x);
  return 0;
}

Выход :

После обмена (& x, & x): x = 0

Замена переменной самой собой может потребоваться во многих стандартных алгоритмах. Например, посмотрите эту реализацию QuickSort, где мы можем поменять местами переменную с собой. Вышеупомянутой проблемы можно избежать, поставив условие перед заменой.

#include <stdio.h>
void swap(int *xp, int *yp)
{
    if (xp == yp) // Check if the two addresses are same
      return;
    *xp = *xp + *yp;
    *yp = *xp - *yp;
    *xp = *xp - *yp;
}
int main()
{
  int x = 10;
  swap(&x, &x);
  printf("After swap(&x, &x): x = %d", x);
  return 0;
}

Выход :

После обмена (& x, & x): x = 10

shobhit2905
источник
0

Может быть, не по теме, но если вы знаете, что меняете одну переменную между двумя разными значениями, вы можете выполнить логику массива. Каждый раз, когда эта строка кода запускается, она меняет значение между 1 и 2.

n = [2, 1][n - 1]
неприятный
источник
0

Вы могли сделать:

std::tie(x, y) = std::make_pair(y, x);

Или используйте make_tuple при замене более двух переменных:

std::tie(x, y, z) = std::make_tuple(y, z, x);

Но я не уверен, использует ли std :: tie внутреннюю временную переменную или нет!

pooya13
источник
0

В javascript:

function swapInPlace(obj) {
    obj.x ^= obj.y
    obj.y ^= obj.x
    obj.x ^= obj.y
}

function swap(obj) {
    let temp = obj.x
    obj.x = obj.y
    obj.y = temp
}

Обратите внимание на время выполнения обоих вариантов.

Запустив этот код, я измерил его.

console.time('swapInPlace')
swapInPlace({x:1, y:2})
console.timeEnd('swapInPlace') // swapInPlace: 0.056884765625ms

console.time('swap')
swap({x:3, y:6})
console.timeEnd('swap')        // swap: 0.01416015625ms

Как вы можете видеть (и как многие говорили), замена на месте (xor) занимает намного больше времени, чем другой вариант с использованием временной переменной.

ofir_aghai
источник
0

В R отсутствует параллельное задание, предложенное Эдсгером В. Дейкстра в «Дисциплина программирования» , 1976, глава 4, стр.29. Это позволило бы найти элегантное решение:

a, b    <- b, a         # swap
a, b, c <- c, a, b      # rotate right
user3604103
источник
-1
a = a + b - (b=a);

Это очень просто, но может вызвать предупреждение.

Alsimoneau
источник
5
Предупреждение в том, что это не работает? Мы не знаем, b=aвыполняется ли это до или после a + b.
Bo Persson
-1

однострочное решение для замены двух значений на языке c.

a=(b=(a=a+b,a-b),a-b);
HARITUSH
источник
Неопределенное поведение при переполнении.
Кейт Томпсон
-1
second_value -= first_value;
first_value +=  second_value;
second_value -= first_value;
second_value *= -1;
Зикрия Куреши
источник
Это имеет неопределенное поведение, если какая-либо из операций переполнена или не заполнена. Он также может потерять точность, если объекты являются плавающими.
Кейт Томпсон