4
\$\begingroup\$

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.

200_success
146k22 gold badges190 silver badges479 bronze badges
asked Nov 15, 2017 at 7:18
\$\endgroup\$
1
  • \$\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\$ Commented Nov 16, 2017 at 11:44

1 Answer 1

3
\$\begingroup\$

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 as 4*std::atan(1.);

  • You might help the compiler to hoist the value of 180/PI if you re-write the multiplication as angleInRad*(180/PI) or 180/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.

answered Nov 15, 2017 at 10:38
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.