Java: результаты вращения точек не точны

У меня есть следующий код...

public class Point {
    private double x;
    private double y;
    static private final double RADTODEG = 180.0d / Math.PI ;
    static private final double DEGTORAD = Math.PI / 180.0d;

    /**
     * Rotates the point by a specific number of radians about a specific origin point.
     * @param origin The origin point about which to rotate the point
     * @param degrees The number of radians to rotate the point
     */
    public void rotateByRadians(Point origin, double radians) {
        double cosVal = Math.cos(radians);
        double sinVal = Math.sin(radians);

        double ox = x - origin.x;
        double oy = y - origin.y;

        x = origin.x + ox * cosVal - oy * sinVal;
        y = origin.y + ox * sinVal + oy * cosVal;
    }

    /**
     * Rotates the point by a specific number of degrees about a specific origin point.
     * @param origin The origin point about which to rotate the point
     * @param degrees The number of degrees to rotate the point
     */
    public void rotateByDegrees(Point origin, double degrees) {
        rotateByRadians(origin, degrees * DEGTORAD);
    }

    /**
     * Rotates the point by the specified number of radians about the axis' origin (0,0). To rotate about a specific origin point, see rotateByRadians(Point, double)
     * @param radians Measure of radians to rotate the point
     */
    public void rotateByRadians(double radians) {
        if(isEmpty()) // Since we're rotating about 0,0, if the point is 0,0, don't do anything
            return;

        double cosVal = Math.cos(radians);
        double sinVal = Math.sin(radians);

        double newx = x * cosVal - y * sinVal;
        double newy = x * sinVal + y * cosVal;

        x = newx;
        y = newy;
    }

    /**
     * Rotates the point by the specified number of degrees about to the axis' origin (0,0). To rotate about a specific origin point, see rotateByDegrees(Point, double)
     * @param degrees Measure of degrees to rotate the point
     */
    public void rotateByDegrees(double degrees) {
        rotateByRadians(degrees * DEGTORAD);
    }

Проблема возникает, когда дается точка, скажем, 0,200. Называя вращение (вокруг начала координат 0,0) на 180 градусов, оно должно быть (0, -200). x координата не должна была измениться. Тем не менее, это в конечном итоге (-2,4492935982947064E-14, -200). Я пытался с помощью strictfp но это не имеет значения. Это влияет только на результат, если вращаемая координата равна нулю. Ненулевые значения работают нормально. Есть идеи, почему это не так?

Код ниже:

   Point p = new Point(0.0d, 200.0d);
   p.rotateByDegrees(180.0d);
   System.out.println(p);

Дает вывод:

shapelib.Point Object {x: -2.4492935982947064E-14 y: -200.0}

3 ответа

Решение

Арифметика с плавающей точкой не является полностью точной. Погрешность 10^-14 мощности достаточно в большинстве случаев. Если вы рассчитываете Math.sin(Math.PI) ты получишь 1.2246467991473532E-16, Почему вам нужно получить точно 0 в вашем случае?

К лучшему или к худшему, так обстоит дело с математикой с плавающей запятой. С http://mindprod.com/jgloss/floatingpoint.html:

"Думайте о float и double как о физических измерениях. Никто не будет жаловаться, если их столяр изготовит стол длиной 6,000000000001 футов. Аналогично, не жалуйтесь на неизбежные крошечные ошибки в арифметических результатах с плавающей запятой, например, Math. Cos( Math.toRadians). ( 90)) не получая удар на нуле. (Если вы хотите совершенства, используйте int, long, BigInteger или BigDecimal.)"

Оба приведенных ответа верны, но в них отсутствует основополагающий момент.

Диапазон возможных значений, которые может принимать число с плавающей запятой, не является непрерывным. Скорее, в нем есть дыры. Таким образом, вы можете представить, что от 0,1 до 0,2 вместо бесконечного количества чисел существует только конечное число.

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

Например, вы не можете точно представить дробь 2 / 10. Если вы распечатаете все десятичные разряды для этого числа, вы найдете что-то вроде 0.20000000000000001.

Смотрите здесь для более тщательной рецензии: http://floating-point-gui.de/

Другие вопросы по тегам