角度归一化C#
本文关键字: | 更新日期: 2023-09-27 18:26:21
我有一个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作为这种格式?
构造函数中的注释是对代码的更正
UPDATE:添加了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;
}
问题出在这段代码中:
double seconds = sec + 60 * (min + 60 * deg);
尽管您将seconds
存储为double
,但在sec + 60 * (min + 60 * deg)
被计算为int
之后,从int
到double
的转换正在进行。
编译器不会根据您决定存储结果的类型为您选择double
算法。编译器将根据操作数的类型选择最佳运算符重载,在这种情况下,这些操作数都是int
,然后查找有效的隐式转换(在这种情况中,int
到double
);因此,它选择了int
算法,并且在最后两个测试用例中运算会溢出:
CCD_ 13=CCD_;CCD_ 15将溢出。
CCD_ 16>CCD_。
您对这两种情况的预期结果可能没有考虑到这种行为。
为了解决这个问题,请将您的代码更改为:
double seconds = sec + 60 * (min + 60f * deg);
将60
显式设置为double
类型的文字常量(60f
)将强制编译器将所有操作解析为double
算法。
此外,值得指出的是,您的构造函数逻辑还有一些其他问题:
您应该验证输入数据;指定负分钟或负秒是否有效?国际海事组织认为这似乎不合理。应该只允许
deg
具有负值。您应该检查此条件并采取相应的行动:抛出异常(首选)或基于deg
的符号规范化min
和sec
的符号(丑陋且可能令人困惑)。对于负角度,您的
seconds
计算似乎不正确(同样,这与之前的问题以及您决定实施的任何符号约定有关)。除非约定负角度必须具有负的deg
、min
和sec
,否则计算seconds
的方法是错误的,因为无论deg
的符号如何,您总是在添加分和秒项。
UPDATE您的代码中还有一个问题,我在有机会测试它之前错过了。您的一些测试用例失败了,因为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;
}
// 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;
}
这是我有的归一化函数
while
循环通常是个坏主意。如果你处理小值,这没关系,但想象一下,你有一个像1e+25这样的角度,如果你有一台不错的CPU,那将是1.59e+24次迭代,或者大约1亿年的计算时间。应该如何做:
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;
}
他们使用非常大的负数和正数来确保标准化将角度正确地限制在[-180180]度的范围内。