I had to write a function which takes an angle in radians and precision N as input and returns a string representation of its magnitude in the format DDD°MM′SS.FFF′′
where FFF
is fraction of the second N decimal digits long.
As conversion of a floating-point binary number to a decimal string can be tricky, I've decided to not reinvent any wheels for generation of the .FFF
part, and instead use some existing ones like QString::number
. Here's the code I've written:
#include <QString>
#include <QStringList>
#include <cmath>
#include <cassert>
QString angleMagnitudeToString(double angleInRad, int precision)
{
const auto angleInDeg = std::abs(angleInRad*180/M_PI);
auto degrees = static_cast<int>(angleInDeg);
auto minutes = static_cast<int>((angleInDeg-degrees)*60);
const auto seconds = ((angleInDeg-degrees)*60-minutes)*60;
const auto secStr = QString::number(seconds, 'f', precision);
auto secStrIntAndFrac = secStr.split('.');
if(precision==0)
assert(secStrIntAndFrac.size()==1);
else
assert(secStrIntAndFrac.size()==2);
// Rounding on converison to string may have resulted in 60 seconds, which should be carried to minutes
if(secStrIntAndFrac[0]=="60")
{
secStrIntAndFrac[0]="0";
++minutes;
}
// Carry to minutes may have resulted in 60 minutes, which should be carried to degrees
if(minutes==60)
{
minutes=0;
++degrees;
}
const auto intSecStr = secStrIntAndFrac[0].rightJustified(2, '0');
const auto fracSecStr = precision==0 ? "" : "."+secStrIntAndFrac[1];
return QString::fromUtf8(u8"%1°%2′%3′′")
.arg(degrees, 3, 10, QChar('0'))
.arg(minutes, 2, 10, QChar('0'))
.arg(intSecStr+fracSecStr);
}
The code seems kludgy due to the fact that I had to "manually" process the possible carry from the seconds to minutes to degrees, but I couldn't find any better way.
-
\$\begingroup\$ I'd love to see an implementation of the reverse function, which parses degree-minute-second strings and returns a value in radians. That would be especially useful to create a test of round-trip stability. \$\endgroup\$Toby Speight– Toby Speight2017年11月16日 11:44:56 +00:00Commented Nov 16, 2017 at 11:44
1 Answer 1
Nice code - easy to read and well laid out. No surprises in the code, apart from the need to clear up the carry from seconds to minutes etc.
Some small improvements:
Although
M_PI
is in POSIX, if you want to be properly portable, you'll want to define your own π, perhaps as4*std::atan(1.)
;You might help the compiler to hoist the value of
180/PI
if you re-write the multiplication asangleInRad*(180/PI)
or180/PI*angleInRad
.There's no checking that the degrees value is in range of
int
when casting.It might make more sense to represent the angle in seconds or minutes rather than degrees (compromising the range in exchange for easier handling of the modular arithmetic).
My version
#include <QString>
#include <climits>
#include <cmath>
QString radians_to_dms(double radians, int precision)
{
static const double PI = 4*std::atan(1.);
if (std::abs(radians) > ULONG_MAX*PI/180/60)
// out of range
return "---";
const auto total_minutes = std::abs(radians*(60*180/PI));
auto whole_minutes = static_cast<int>(total_minutes);
auto seconds = 60 * (total_minutes - whole_minutes);
auto secStr = QString::number(seconds, 'f', precision);
if (secStr.startsWith("60")) {
// it rounds up - carry to minutes
secStr = QString::number(0.0, 'f', precision);
whole_minutes += 1;
} else if (secStr.startsWith('-')) {
// negative zero - shouldn't happen
secStr = QString::number(0.0, 'f', precision);
}
if (secStr.size() < 2 || secStr[1] == '.')
// zero-fill
secStr.prepend('0');
return QString::fromUtf8(u8"%1%2°%3′%4′′")
.arg(radians < 0 ? "-" : "")
.arg(whole_minutes / 60, 3, 10, QChar('0'))
.arg(whole_minutes % 60, 2, 10, QChar('0'))
.arg(secStr);
}
#include <QDebug>
int main()
{
for (double a: { -0.0, -3.1415926535897932384, 1.0,
59.999996/3600*4*std::atan(1.)/180,
1. * ULONG_MAX})
qDebug() << a << " = " << qPrintable(radians_to_dms(a, 5));
const double step = .000142857;
for (double a = 0; a < 10*step; a+= step)
qDebug() << a << " = " << qPrintable(radians_to_dms(a, 5));
}
I get reasonable output from the test cases:
0 = 000°00′00.00000′′
-3.14159 = -180°00′00.00000′′
1 = 057°17′44.80625′′
0.000290888 = 000°01′00.00000′′
1.84467e+19 = ---
0 = 000°00′00.00000′′
0.000142857 = 000°00′29.46637′′
0.000285714 = 000°00′58.93274′′
0.000428571 = 000°01′28.39911′′
0.000571428 = 000°01′57.86549′′
0.000714285 = 000°02′27.33186′′
0.000857142 = 000°02′56.79823′′
0.000999999 = 000°03′26.26460′′
0.00114286 = 000°03′55.73097′′
0.00128571 = 000°04′25.19734′′
0.00142857 = 000°04′54.66371′′
I'm not sure whether it's much improvement over the original, but I hope it gives you some ideas.