Блог пользователя Xazker

Автор Xazker, 15 лет назад, По-русски
Я всегда думал, что в стандартных библиотеках все хорошо протестировано и работает безупречно. Моему удивлению не было предела, когда я увидел какие результаты выдал следующий код.
java.awt.geom 
Class Line2D

ptLineDist

public static double ptLineDist(double X1,
                                double Y1,
                                double X2,
                                double Y2,
                                double PX,
                                double PY)

Returns the distance from a point to a line. The distance measured is the distance between the specified point and the closest point on the infinitely-extended line defined by the specified coordinates. If the specified point intersects the line, this method returns 0.0.

System.out.println(Line2D.ptLineDist(0, 0, 1, 200, 1, 199));
System.out.println(Line2D.ptLineDist(0, 0, 1, 2000, 1, 1999));
System.out.println(Line2D.ptLineDist(0, 0, 1, 20000, 1, 19999));
System.out.println(Line2D.ptLineDist(0, 0, 1, 200000, 1, 199999));
System.out.println(Line2D.ptLineDist(0, 0, 1, 2000000, 1, 1999999));
System.out.println(Line2D.ptLineDist(0, 0, 1, 20000000, 1, 19999999));


0.004999937545118085
5.000601076713239E-4
0.0                                      --- расстояние от точки  (1,19999) до прямой (0,0) (1, 20000) равно 0 !!!
0.0
0.0
0.25                                    --- откуда здесь 0.25 ???

Кто может обьяснить такие странные результаты?
  • Проголосовать: нравится
  • +7
  • Проголосовать: не нравится

15 лет назад, # |
  Проголосовать: нравится +4 Проголосовать: не нравится
Забавно, что при этом расстояние до отрезка на первый взгляд считается правильно

import java.awt.geom.*;

public class test
{
    public static void main( String[] args )
    {
        System.out.println( new Line2D.Double( 0.0, 0.0, 1.0, 200.0     ).ptSegDist( 1.0, 199.0 ) );
        System.out.println( new Line2D.Double( 0.0, 0.0, 1.0, 2000.0    ).ptSegDist( 1.0, 1999.0 ) );
        System.out.println( new Line2D.Double( 0.0, 0.0, 1.0, 20000.0   ).ptSegDist( 1.0, 19999.0 ) );
        System.out.println( new Line2D.Double( 0.0, 0.0, 1.0, 200000.0  ).ptSegDist( 1.0, 199999.0 ) );
        System.out.println( new Line2D.Double( 0.0, 0.0, 1.0, 2000000.0 ).ptSegDist( 1.0, 1999999.0 ) );
        System.out.println( new Line2D.Double( 0.0, 0.0, 1.0, 20000000.0).ptSegDist( 1.0, 19999999.0 ) );
    }
}

0.004999937501174908
4.999999375293843E-4
4.9999999848063224E-5
5.000000206850923E-6
5.000222246516501E-7
5.053229617420784E-8
  • 15 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    А в чём прикол?
  • 15 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    удвоить длину отрезка (см. ниже) - снова начинаются проблемы
  • 15 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Еще лучше

    System.out.println( new Line2D.Double( 1.0, 200.0 ,0.0, 0.0).ptSegDist( 1.0, 199.0 ) );
    System.out.println( new Line2D.Double( 1.0, 2000.0 ,0.0, 0.0).ptSegDist( 1.0, 1999.0 ) );
    System.out.println( new Line2D.Double( 1.0, 20000.0 ,0.0, 0.0).ptSegDist( 1.0, 19999.0 ) );
    System.out.println( new Line2D.Double( 1.0, 200000.0 ,0.0, 0.0).ptSegDist( 1.0, 199999.0 ) );
    System.out.println( new Line2D.Double( 1.0, 2000000.0 ,0.0, 0.0).ptSegDist( 1.0, 1999999.0 ) );
    System.out.println( new Line2D.Double( 1.0, 20000000.0,0.0, 0.0).ptSegDist( 1.0, 19999999.0 ) );

    0.004999937545118085
    5.000601076713239E-4
    0.0
    0.0
    0.0
    0.25

    • 15 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Хм, значит ты с кодом Алекса получил то же, что и автор поста?
      • 15 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится
        Нет. Сравни еще раз мой код и Алекса, в чем они различаются.
      • 15 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится
        Нет. Сравни еще раз мой код и Алекса, в чем они различаются.
15 лет назад, # |
  Проголосовать: нравится +3 Проголосовать: не нравится
Посмотрел исходники. Там в самом деле какой-то неудачный код... Это такая потеря точности.
15 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Это называется потеря точности при вычитании. Вот в этой строке (из JDK 1.6)

lenSq = px * px + py * py - projlenSq;

В машинной арифметике (а именно, IEEE 754 double) точно представимо лишь некоторое подмножество рациональных чисел. Поэтому, фактически, остается допускать, что любое число double находится в (V1 - e1, V1+e1).

Что происходит, когда вычитают два неточно заданных числа? Величины вычитаются, а погрешности складываются:

(V1 - V2 - (e1+e2) , V1 - V2 +(e1+e2))

В нашем случае серии из шести тестов V1-V2 убывает, а e1+e2 растет. 

Кстати, если  этот код откомпилировать GCC, то будет получен более осмысленный результат:

4.999938e-003
4.999999e-004
5.002929e-005
0.000000e+000
0.000000e+000
0.000000e+000



  • 15 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    Могу ответить только одно: шизаа...
    Неужли уже при тесте  ( ( 0, 0 ) - ( 1, 20000 ) ) == ( 1, 19999 ) e1+e2 столь велико относительно v1 и v2?
  • 15 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    интересно..а почему вообще есть какая-то разница? разве double разный в C++ (ну в частности GCC) и Java!? .. разве это не один и тот же дабл из IEEE 754?
    • 15 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      Разница потому, что одинаковые конструкции языка высокого уровня по-разному транслируются в инструкции процессора, и разные компиляторы Си тоже дают разные результаты. Надо еще GCJ посмотреть, ради интереса.

      Тип double -- можно сказать, что разный, т.к. настройки округления разные.

      • 15 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится
        всё равно как-то странно..в любом случае операции с double'ами выполняет сопроцессор...так почему же им быть разными?? .. нет, это не как заявление о том, что ты не прав.. просто я понять не могу ...
        • 15 лет назад, # ^ |
            Проголосовать: нравится 0 Проголосовать: не нравится
          Например, double в c++ обычно 64 бита, в сопроцессоре же ведется обычно работа с 80-битовыми числами. То есть при копировании из регистров в память происходит округление. Получается, что результат будет сильно зависеть от последовательности инструкций, сгенерированных компилятором, хотя бы потому что round(a+b) не равен round(a)+round(b).
15 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
double позволяет хранить около 15 точных знаков после запятой, и не должно быть таких проблем, такое ощущение что все вычисления проводились в float
  • 15 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Хранить-то он позволяет, а вот при промежуточных вычислениях (если Вы не заметили, там и возведение в квадрат, и взятие квадратного корня) точность может теряться, в том числе и полностью (да, есть и термин такой: total loss of precision), поэтому проблемы должны быть и будут.


    • 15 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      И какой же выход из ситуации?
      • 15 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится

        несколько вариантов (не применительно к олимпиадам, а в целом)

        1. Использовать интервальный тип данных (к сожалению это невозможно в Java), что позволит определить погрешность выходных данных и сообщить, если ошибка слишком велика

        2. Использовать более длинный тип данных, что позволит подсчитать верный результат

        самый лучший способ:

        3. Изменить формулу или алгоритм, чтобы потери точности при вычитании не было. (а также, по возможности и других источников накопления погрешности)


        • 15 лет назад, # ^ |
            Проголосовать: нравится 0 Проголосовать: не нравится

          А какие вообще есть источники потери точности? Можно как-то быстро находить в исходнике проблемные места?

          И ещё, давно интересует, как же всё-таки правильно выбирать погрешности при сравнении в задачах?

          • 15 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится
            Если не юзаешь тригонометрию и корни, то самая жесткая потеря точности - это вычитание.
            Если ты можешь переработать решение так, чтобы были только умножения, деления и сложения, точность возрастет драматически.
            Хороший пример - задача G на NEERC 2007/2008 - там не проходило решение с вычитаниями.
            • 15 лет назад, # ^ |
                Проголосовать: нравится 0 Проголосовать: не нравится

              А как ты выбираешь eps? Ставишь то, что упоминается в условии?

              Помнится, около года назад на сборах была задача про каких-то выглядывающих кротов, которых нужно бить в соответствии с углами, так на разборе говорилось что-то о противности косинусов и том, что проходили [только] решения с 1e-12..1e-14.

              • 15 лет назад, # ^ |
                  Проголосовать: нравится +1 Проголосовать: не нравится
                На ICPC я принципиально никогда не решал геометрию.
                И сейчас стараюсь поступать по возможности также.
                Именно потому, что идиотизм с выбором eps я не понимаю. А случаи, когда задача сдается скажем только с eps=1e-6, и ни с каким другим, бывают (аналогично для других значений eps), и угадывать то того, как начать решать, что задумал автор, я не очень люблю :о)
            • 15 лет назад, # ^ |
                Проголосовать: нравится +3 Проголосовать: не нравится
              боюсь сказать глупость... а если я a-b буду записывать как a+(-1)*b - точность возрастет?
              • 15 лет назад, # ^ |
                  Проголосовать: нравится +3 Проголосовать: не нравится
                Нет. Дело не в самой операции, а в том, что при сложении/вычитании больших чисел с небольшой погрешностью в результате может получиться маленькое число с еще большей погрешностью. То есть относительная погрешность при такой операции может вырасти на порядки.
                • 15 лет назад, # ^ |
                    Проголосовать: нравится 0 Проголосовать: не нравится
                  ясно, т.е. не в вычитании таки дело, а в офигенном увеличении относительной погрешности))
                  • 15 лет назад, # ^ |
                      Проголосовать: нравится +18 Проголосовать: не нравится
                    Просто когда говорят о floating point, то под вычитанием/сложением имеют ввиду вычитание/сложение модулей числа. При сложении модулей этой проблемы нет.
          • 15 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится
            • 15 лет назад, # ^ |
                Проголосовать: нравится 0 Проголосовать: не нравится
              Эту статью я просматривал, основы 2 раза изучал, в школе и универе :). Но всё равно, интересно увидеть не ЭТО описание стандарта, а более практическое видение.
              • 15 лет назад, # ^ |
                  Проголосовать: нравится 0 Проголосовать: не нравится
                Там описание стандарта только в конце статьи. А до этого показывается откуда погрешности возникают и как числа сравнивать надо.
          • 15 лет назад, # ^ |
              Проголосовать: нравится +1 Проголосовать: не нравится
            По-хорошему, нужная точность должна обеспечиваться на этапе аналитических выкладок до написания любого кода.
            • 15 лет назад, # ^ |
                Проголосовать: нравится 0 Проголосовать: не нравится
              Да, я недавно видел совет использовать метод наименьших квадратов вместо решения СЛАУ для какой-то простой подзадачи (типа пересечения прямых), для точности - вот такие вот практические мелочи меня и интересуют.
    • 15 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Хм. ))
      А long double нас хотя бы частично не спасёт?)
  • 15 лет назад, # ^ |
      Проголосовать: нравится +1 Проголосовать: не нравится
    Не 15 знаков после запятой, а 15 значащих цифр.
15 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Понятно, что где-то точность теряется, но это не мешает

new Line2D.Double( 0.0, 0.0, 1.0, 20000.0   ).ptSegDist( 1.0, 19999.0 ) );

возвращать не 0, а что-то похожее на правду.

  • 15 лет назад, # ^ |
      Проголосовать: нравится +3 Проголосовать: не нравится

    У ptSegDistSq в отличие от ptLineDistSq, множество входных данных распадается на три области: в одной результат совпадает с ptLineDistSq, а в двух других искомое расстояние является расстоянием до одной из граничных точек отрезка. ptSegDistSq поэтому делает еще одно преобразование координат:

    px = x2 - px;

    py = y2 - py;

    и дальше - в нашем примере - получаются малые числа, на которые точности хватает.

    Если взять другой случай - просто удвоим длину отрезка, получим снова ноль

    System.out.println( new Line2D.Double( 0.0, 0.0, 2.0, 400.0 ).ptSegDist( 1.0, 199.0 ) );
    System.out.println( new Line2D.Double( 0.0, 0.0, 2.0, 4000.0 ).ptSegDist( 1.0, 1999.0 ) );
    System.out.println( new Line2D.Double( 0.0, 0.0, 2.0, 40000.0 ).ptSegDist( 1.0, 19999.0 ) );
    System.out.println( new Line2D.Double( 0.0, 0.0, 2.0, 400000.0 ).ptSegDist( 1.0, 199999.0 ) );
    System.out.println( new Line2D.Double( 0.0, 0.0, 2.0, 4000000.0 ).ptSegDist( 1.0, 1999999.0 ) );
    System.out.println( new Line2D.Double( 0.0, 0.0, 2.0, 40000000.0).ptSegDist( 1.0, 19999999.0 ) );

15 лет назад, # |
  Проголосовать: нравится +3 Проголосовать: не нравится
I think the standard libraries about ptLineDist is not good;

this is the ptLineDist source code;


public static double
ptLineDistSq (double X1, double Y1,
                      double X2, double Y2,
                      double PX, double PY) {
 
X2 -= X1;
     Y2 -= Y1;
 
PX -= X1;
     PY -= Y1;
     double dotprod = PX * X2 + PY * Y2;
   
double projlenSq = dotprod * dotprod / (X2 * X2 + Y2 * Y2);
   
double lenSq = PX * PX + PY * PY - projlenSq;
   
if (lenSq < 0) {
         
lenSq = 0;
    }
   
return lenSq;
}

public static double ptLineDist (double X1, double Y1,
                      double X2, double Y2,
                      double PX, double PY) {
    return
return Math.sqrt(ptLineDistSq(X1, Y1, X2, Y2, PX, PY));
}

in the method
ptLineDistSq java stander use dot-product to calculate the dist.
and use dot*dot this maybe as larger as x^4,but
(X2 * X2 + Y2 * Y2) is x^2;
so the result
projlenSq could be make a flat error,and make lenSq could be very small,even to zero.

you can try write a new method use cross-product,then you will find it come up with the correct answer:
    public static double pointToLineDist(Point p,Point A,Point B)
    {
        return Math.abs(p.sub(A).cross(B.sub(A))/B.sub(A).len());
    }


15 лет назад, # |
  Проголосовать: нравится +3 Проголосовать: не нравится
I think the standard libraries about ptLineDist is not good;

this is the ptLineDist source code;


public static double
ptLineDistSq (double X1, double Y1,
                      double X2, double Y2,
                      double PX, double PY) {
 
X2 -= X1;
     Y2 -= Y1;
 
PX -= X1;
     PY -= Y1;
     double dotprod = PX * X2 + PY * Y2;
   
double projlenSq = dotprod * dotprod / (X2 * X2 + Y2 * Y2);
   
double lenSq = PX * PX + PY * PY - projlenSq;
   
if (lenSq < 0) {
         
lenSq = 0;
    }
   
return lenSq;
}

public static double ptLineDist (double X1, double Y1,
                      double X2, double Y2,
                      double PX, double PY) {
    return
return Math.sqrt(ptLineDistSq(X1, Y1, X2, Y2, PX, PY));
}

in the method
ptLineDistSq java stander use dot-product to calculate the dist.
and use dot*dot this maybe as larger as x^4,but
(X2 * X2 + Y2 * Y2) is x^2;
so the result
projlenSq could be make a flat error,and make lenSq could be very small,even to zero.

you can try write a new method use cross-product,then you will find it come up with the correct answer:
    public static double pointToLineDist(Point p,Point A,Point B)
    {
        return Math.abs(p.sub(A).cross(B.sub(A))/B.sub(A).len());
    }


15 лет назад, # |
  Проголосовать: нравится +6 Проголосовать: не нравится
I think the standard libraries about ptLineDist is not good;

this is the ptLineDist source code;


public static double
ptLineDistSq (double X1, double Y1,
                      double X2, double Y2,
                      double PX, double PY) {
 
X2 -= X1;
     Y2 -= Y1;
 
PX -= X1;
     PY -= Y1;
     double dotprod = PX * X2 + PY * Y2;
   
double projlenSq = dotprod * dotprod / (X2 * X2 + Y2 * Y2);
   
double lenSq = PX * PX + PY * PY - projlenSq;
   
if (lenSq < 0) {
         
lenSq = 0;
    }
   
return lenSq;
}

public static double ptLineDist (double X1, double Y1,
                      double X2, double Y2,
                      double PX, double PY) {
    return
return Math.sqrt(ptLineDistSq(X1, Y1, X2, Y2, PX, PY));
}

in the method
ptLineDistSq java stander use dot-product to calculate the dist.
and use dot*dot this maybe as larger as x^4,but
(X2 * X2 + Y2 * Y2) is x^2;
so the result
projlenSq could be make a flat error,and make lenSq could be very small,even to zero.

you can try write a new method use cross-product,then you will find it come up with the correct answer:
    public static double pointToLineDist(Point p,Point A,Point B)
    {
        return Math.abs(p.sub(A).cross(B.sub(A))/B.sub(A).len());
    }


15 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
I think the standard libraries about ptLineDist is not good;

this is the ptLineDist source code;


public static double
ptLineDistSq (double X1, double Y1,
                      double X2, double Y2,
                      double PX, double PY) {
 
X2 -= X1;
     Y2 -= Y1;
 
PX -= X1;
     PY -= Y1;
     double dotprod = PX * X2 + PY * Y2;
   
double projlenSq = dotprod * dotprod / (X2 * X2 + Y2 * Y2);
   
double lenSq = PX * PX + PY * PY - projlenSq;
   
if (lenSq < 0) {
         
lenSq = 0;
    }
   
return lenSq;
}

public static double ptLineDist (double X1, double Y1,
                      double X2, double Y2,
                      double PX, double PY) {
    return
return Math.sqrt(ptLineDistSq(X1, Y1, X2, Y2, PX, PY));
}

in the method
ptLineDistSq java stander use dot-product to calculate the dist.
and use dot*dot this maybe as larger as x^4,but
(X2 * X2 + Y2 * Y2) is x^2;
so the result
projlenSq could be make a flat error,and make lenSq could be very small,even to zero.

you can try write a new method use cross-product,then you will find it come up with the correct answer:
    public static double pointToLineDist(Point p,Point A,Point B)
    {
        return Math.abs(p.sub(A).cross(B.sub(A))/B.sub(A).len());
    }


15 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
I think the standard libraries about ptLineDist is not good;

this is the ptLineDist source code;


public static double
ptLineDistSq (double X1, double Y1,
                      double X2, double Y2,
                      double PX, double PY) {
 
X2 -= X1;
     Y2 -= Y1;
 
PX -= X1;
     PY -= Y1;
     double dotprod = PX * X2 + PY * Y2;
   
double projlenSq = dotprod * dotprod / (X2 * X2 + Y2 * Y2);
   
double lenSq = PX * PX + PY * PY - projlenSq;
   
if (lenSq < 0) {
         
lenSq = 0;
    }
   
return lenSq;
}

public static double ptLineDist (double X1, double Y1,
                      double X2, double Y2,
                      double PX, double PY) {
    return
return Math.sqrt(ptLineDistSq(X1, Y1, X2, Y2, PX, PY));
}

in the method
ptLineDistSq java stander use dot-product to calculate the dist.
and use dot*dot this maybe as larger as x^4,but
(X2 * X2 + Y2 * Y2) is x^2;
so the result
projlenSq could be make a flat error,and make lenSq could be very small,even to zero.

you can try write a new method use cross-product,then you will find it come up with the correct answer:
    public static double pointToLineDist(Point p,Point A,Point B)
    {
        return Math.abs(p.sub(A).cross(B.sub(A))/B.sub(A).len());
    }


15 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
I think the standard libraries about ptLineDist is not good;

this is the ptLineDist source code;


public static double
ptLineDistSq (double X1, double Y1,
                      double X2, double Y2,
                      double PX, double PY) {
 
X2 -= X1;
     Y2 -= Y1;
 
PX -= X1;
     PY -= Y1;
     double dotprod = PX * X2 + PY * Y2;
   
double projlenSq = dotprod * dotprod / (X2 * X2 + Y2 * Y2);
   
double lenSq = PX * PX + PY * PY - projlenSq;
   
if (lenSq < 0) {
         
lenSq = 0;
    }
   
return lenSq;
}

public static double ptLineDist (double X1, double Y1,
                      double X2, double Y2,
                      double PX, double PY) {
    return
return Math.sqrt(ptLineDistSq(X1, Y1, X2, Y2, PX, PY));
}

in the method
ptLineDistSq java stander use dot-product to calculate the dist.
and use dot*dot this maybe as larger as x^4,but
(X2 * X2 + Y2 * Y2) is x^2;
so the result
projlenSq could be make a flat error,and make lenSq could be very small,even to zero.

you can try write a new method use cross-product,then you will find it come up with the correct answer:
    public static double pointToLineDist(Point p,Point A,Point B)
    {
        return Math.abs(p.sub(A).cross(B.sub(A))/B.sub(A).len());
    }


15 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
I think the standard libraries about ptLineDist is not good;

this is the ptLineDist source code;


public static double
ptLineDistSq (double X1, double Y1,
                      double X2, double Y2,
                      double PX, double PY) {
 
X2 -= X1;
     Y2 -= Y1;
 
PX -= X1;
     PY -= Y1;
     double dotprod = PX * X2 + PY * Y2;
   
double projlenSq = dotprod * dotprod / (X2 * X2 + Y2 * Y2);
   
double lenSq = PX * PX + PY * PY - projlenSq;
   
if (lenSq < 0) {
         
lenSq = 0;
    }
   
return lenSq;
}

public static double ptLineDist (double X1, double Y1,
                      double X2, double Y2,
                      double PX, double PY) {
    return
return Math.sqrt(ptLineDistSq(X1, Y1, X2, Y2, PX, PY));
}

in the method
ptLineDistSq java stander use dot-product to calculate the dist.
and use dot*dot this maybe as larger as x^4,but
(X2 * X2 + Y2 * Y2) is x^2;
so the result
projlenSq could be make a flat error,and make lenSq could be very small,even to zero.

you can try write a new method use cross-product,then you will find it come up with the correct answer:
    public static double pointToLineDist(Point p,Point A,Point B)
    {
        return Math.abs(p.sub(A).cross(B.sub(A))/B.sub(A).len());
    }


  • 15 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    May be i don't understand something, but i think there is a mistake. For example if A = (0, 0) B = (10, 0) and p = (3, 2)
    we get
    p.sub(A) = (3, 2)
    B.sub(A) = (10, 0)
    p.sub(A).cross(B.sub(A)) = 3 * 10 + 0 * 2 = 30
    p.sub(A).cross(B.sub(A)) / (B.sub(A).len()) = 30 / 10 = 3

    but real answer is 2. How to fix that?
15 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Oh, it was my fault. I mixed up cross and dot product. :)