Нормализация угла C#

У меня есть класс Angle с этим конструктором

public  Angle(int deg, //  Degrees, minutes, seconds
                  int min, //    (Signs should agree
                  int sec) //       for conventional notation.)
{
   /* //Bug degree normalization 
    while (deg <= -180) deg += 360;
    while (deg > Math.PI) deg -= 360;
    //correction end */

    double seconds = sec + 60 * (min + 60 * deg);
    value = seconds * Math.PI / 648000.0;
    normalize(); 
}

и у меня есть эти значения для тестирования этого конструктора

  int[] degrees = { 0, 180, -180, Int32.MinValue / 60, 120+180*200000};
        int[] minutes = { 0, 0, 0, 0,56};
        int[] seconds = { 0, 0, 0, 0,10};
        Console.WriteLine("Testing constructor Angle(int deg, int min)");
        for (int i = 0; i < degrees.Length; i++)
        {

            p = new Angle(degrees[i], minutes[i], seconds[i]);
            Console.WriteLine("p = " + p);
        }
        /*Testing constructor Angle(int deg, int min)
        p = 0°0'0"
        p = 180°0'0"
        p = 180°0'0"
        p = 0°8'0" incorrect output 
        p = -73°11'50"  incorrect output expected  120 56 10 
         */

Я не понимаю, почему здесь ошибка? и почему они использовали деление Int32.MinValue на 60 и 120 + 180 * 200000 в качестве этого формата?

комментарии в конструкторе - это исправление кода

ОБНОВЛЕНИЕ: добавлен код normalize()

//  For compatibility with the math libraries and other software
//  range is (-pi,pi] not [0,2pi), enforced by the following function:  

void normalize()
{
    double twoPi = Math.PI + Math.PI;          
    while (value <= -Math.PI) value += twoPi;
    while (value >   Math.PI) value -= twoPi;
}  

person Concealed    schedule 11.10.2014    source источник
comment
Что именно вы спрашиваете? Вы спрашиваете, как обеспечить угол между 0 и 2pi, или вы спрашиваете, как был достигнут ваш результат? Если это последнее, вам нужно будет показать свою функцию normalize(). Одна из распространенных проблем с этим типом испытаний заключается в том, что вы учитываете только первые две ротации и ничего больше. Вы должны убедиться, что ваша функция normalize() учитывает углы больше 4pi.   -  person brcpar    schedule 11.10.2014
comment
в то время как (deg › Math.PI) deg -= 360; эта строка не имеет смысла... Вы наверняка имеете в виду deg › 180   -  person tolanj    schedule 12.10.2014
comment
Использование цикла while для переноса значений — плохая идея.   -  person John Alexiou    schedule 12.10.2014


Ответы (4)


Проблема в этом фрагменте кода:

double seconds = sec + 60 * (min + 60 * deg);

Хотя вы сохраняете seconds как double, преобразование из int в double происходит после вычисления sec + 60 * (min + 60 * deg) как int.

Компилятор не будет выбирать double арифметику за вас на основе типа, в котором вы решите сохранить результат. Компилятор выберет наилучшую перегрузку оператора на основе типов операндов, которые в данном случае все int и найдите допустимое неявное преобразование (в данном случае int в double) после этого; поэтому он выбирает int арифметику, и операция переполнится в последних двух тестовых случаях:

Int32.MinValue / 60 * 60 * 60 = Int32.MinValue * 60Int32.MinValue, которое переполнится.

120 + 180 * 200000 * 60 * 60 > Int32.MaxValue, который также переполнится.

Ваши ожидаемые результаты для этих двух случаев, вероятно, не учитывают это поведение.

Чтобы решить эту проблему, измените код на:

double seconds = sec + 60 * (min + 60f * deg);

Явная установка 60 в литеральную константу типа double (60f) заставит компилятор разрешать все операции в double арифметике.

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

  1. Вы должны проверять входные данные; следует ли указывать отрицательные минуты или секунды? ИМО это не кажется разумным. Только deg должно иметь отрицательное значение. Вы должны проверить это условие и действовать соответственно: сгенерировать исключение (предпочтительнее) или нормализовать знак min и sec на основе знака deg (некрасиво и потенциально запутанно).

  2. Ваш расчет seconds не кажется правильным для отрицательных углов (опять же, это связано с предыдущей проблемой и любым соглашением о знаках, которое вы решили реализовать). Если соглашение не гласит, что отрицательные углы должны иметь отрицательные deg, min и sec, способ вычисления seconds неверен, потому что вы всегда добавляете минуты и секунды независимо от знака deg.

ОБНОВЛЕНИЕ В вашем коде есть еще одна проблема, которую я пропустил, пока не выпал шанс ее протестировать. Некоторые из ваших тестов не работают, потому что double не имеет достаточного разрешения. Я думаю, что ваш код нуждается в серьезном рефакторинге; normalize() должен быть вызван первым. Таким образом, вы всегда будете управлять жестко ограниченными значениями, которые не могут привести к переполнению или потере точности.

Я бы сделал так:

 public Angle(int deg, int min, int sec)
 {
     //Omitting input values check.

     double seconds = sec + 60 * (min + 60 * normalize(deg));
     value = seconds * Math.PI / 648000f;
 }

 private int normalize(int deg)
 {
     int normalizedDeg = deg % 360;

     if (normalizedDeg <= -180)
         normalizedDeg += 360;
     else if (normalizedDeg > 180)
         normalizedDeg -= 360;

     return normalizedDeg;
 }
person InBetween    schedule 12.10.2014
comment
когда я попытался изменить на двойные секунды = сек + 60 * (мин + 60f * градус).. это дает мне окончательный результат для Int32.MinValue / 60 как 167 ° 59'0.. почему он меняет мин на 59 Я думаю, что правильный вывод должен быть 166 0 0 ..правильно? но это дает мне ожидаемый результат для последнего значения, которое составляет 120 ° 56'10.. - person Concealed; 12.10.2014
comment
Ваш метод неверен, он нормализует только значения между -360 и 360. Все, что выше, не будет нормализовано. - person Aberro; 02.08.2021

    //  For compatibility with the math libraries and other software
//  range is (-pi,pi] not [0,2pi), enforced by the following function:  

void normalize()
{double twoPi = Math.PI + Math.PI;          
    while (value <= -Math.PI) value += twoPi;
    while (value >   Math.PI) value -= twoPi;
}     

Это функция нормализации, которая у меня есть

person Concealed    schedule 11.10.2014
comment
Не отвечайте на свой вопрос, чтобы предоставить дополнительную информацию. Отредактируйте свой вопрос, добавив новую информацию, и укажите на нее уведомление ОБНОВЛЕНИЕ с описанием новой информации и ее актуальности. - person InBetween; 12.10.2014

while циклы - вообще плохая идея. Если вы имеете дело с небольшими значениями, все в порядке, но представьте, что у вас есть некоторый угол, например 1e+25, это будет 1,59e+24 итерации или около 100 миллионов лет для вычислений, если у вас есть приличный процессор. Как это должно быть сделано вместо этого:

static double NormalizeDegree360(double value)
{
    var result = value % 360.0;
    return result > 0 ? result : result + 360;
}
static double NormalizeDegree180(double value)
{
    return (((value + 180) % 360) + 360) % 360 - 180;
}
static double TwoPI = 2*System.Math.PI;
static double NormalizeRadians2Pi(double value)
{
    var result = value % TwoPI;
    return result > 0 ? result : result + TwoPI;
}
static double NormalizeRadiansPi(double value)
{
    return (((value + System.Math.PI) % TwoPI) + TwoPI) % TwoPI - System.Math.PI;
}
person Aberro    schedule 02.08.2021
comment
Не совсем уверен, но я думаю, что тернарный оператор со сравнением должен быть быстрее, чем оператор второго модуля (который в основном должен иметь ту же скорость, что и деление (где-то между 10-50 операций), в то время как сравнение в основном представляет собой вычитание (2 операции), а тернарный оператор - один условный переход, который обычно составляет 1 или 2 операции). - person Aberro; 02.08.2021

Они используют очень большие отрицательные и положительные числа, чтобы убедиться, что нормализация ограничивает углы в диапазоне [-180, 180] градусов правильно.

person Eric    schedule 11.10.2014