## DEV Community

Rémy Hannequin

Posted on • Updated on

# Astronomical computing – Ep 3 - Angles in Ruby

In the previous episode we discovered multiple new notions such as celestial coordinates and angles.

As a first step towards the Ruby library, we are going to start by manipulating angles and units.

I am going to pass on the gem creation and skeleton which is made using Bundler and its command `\$ bundle gem astronoby`.

## Object type

The book tells us the following:

To convert an angle from degrees to radians, multiply it by π/180.

In Ruby, it would be written as:

``````Math::PI / 180
``````

`Math::PI` comes from the standard library and returns a `Float`.

``````(Math::PI / 180).class
# => Float

Math::PI / 180
# => 0.017453292519943295
``````

A `Float` is easy to manipulate. It also looks like the available precision is quite interesting with 18 digits after the zero.

But this is not enough to me. Angles are going to be one of the most important and used data for this whole library. They will be greatly multiplied, divided. By definition a `Float` won't be completely accurate when manipulating π.

There will probably be some moments we will be okay to lose a bit of precision, but I want it to be by choice, not by design.
Also, without even caring about π, `Float` suffers from imprecision and should not be used when caring about accuracy.

``````0.1 + 0.2
# => 0.30000000000000004

1.1 * 0.1
# => 0.11000000000000001
``````

Therefore we need to use `BigDecimal` numeric values, especially `BigMath.PI`.

``````require "bigdecimal/math"

BigMath.PI(10).class
# => BigDecimal

BigMath.PI(10)
# 0.3141592653589793238462643388813853786957412e1

BigMath.PI(10) / 180
# 0.17453292519943295769236907715632521038652289e-1
``````

## First classes

We are starting basic with 3 classes:

• `Angle` that will be the parent class of any angle, any unit
• `Degree` for manipulating angles initialized in degrees
• `Radian` for manipulating angles initialized in radians

### `Angle`

`Angle` is going to implement some basic stuff, but more importantly it is going to allow angles to be converted in different units. We want to have `#to_degrees` and `#to_radians` available on angles to convert themselves in a new instance of `Angle` with a different unit.

Let's write the associated spec:

``````RSpec.describe Astronoby::Angle do
describe "::as_degrees" do
subject { described_class.as_degrees(180) }

it "returns an Angle object" do
expect(subject).to be_a(described_class)
end

it "returns an Angle in Degree" do
expect(subject).to be_a(Astronoby::Degree)
end
end

it "returns an Angle object" do
expect(subject).to be_a(described_class)
end

it "returns an Angle in Radian" do
end
end
end
``````

`Angle` doesn't manage the unit conversion's calculation, which will be handled by the children classes.

We are going to store the actual angle's value into a `@value` attribute and a `unit` parameter will be used to specify the value's unit.

A first implementation of the parent `Angle` class could look like this:

``````module Astronoby
class Angle
UNITS = [
DEGREES = :degrees,
].freeze

def self.as_degrees(angle)
Astronoby::Degree.new(angle)
end

end

def initialize(angle, unit:)
@angle = BigDecimal(angle)
@unit = unit
end

def value
@angle
end

def to_degrees
raise NotImplementedError, "#{self.class} must implement #to_degrees method."
end

raise NotImplementedError, "#{self.class} must implement #to_radians method."
end
end
end
``````

Note that we are automatically converting the angle's value into `BigDecimal`, in order to keep precision when manipulating or converting the angle.

### `Degree`

The `Degree` class aims to handle specific logic around angles in degrees.

Right now, the only logic we need to implement is conversion from degrees to radians. This is where we are writing the rule "multiply by π/180".

First, let's start with the tests to define how this new class is supposed to behave. We want to ensure `#to_degrees` will keep the angle as it is, and `#to_radians` will return a new instance in the proper class and convert its value.

``````RSpec.describe Astronoby::Degree do
let(:instance) { described_class.new(180) }

describe "#value" do
subject { instance.value }

it "returns the angle's numeric value in the current unit" do
expect(subject).to eq(180)
end
end

describe "#to_degrees" do
subject { instance.to_degrees }

it "returns itself" do
expect(subject).to eq(instance)
end
end

it "returns a new Radian instance" do
end

it "converted the degrees value into radians" do
expect(subject.value).to eq(BigMath.PI(10))
end
end
end
``````

`Degree`'s implementation is therefore quite simple:

``````module Astronoby
class Degree < Angle
def initialize(angle)
super(angle, unit: DEGREES)
end

def to_degrees
self
end

end
end
end
``````

Finally, the conversion we have been talking about for a while.

## `Radian`

On the same structure, `Radian` will contain the logic to convert an angle in radians into degrees.

``````RSpec.describe Astronoby::Radian do
let(:instance) { described_class.new(BigMath.PI(10)) }

describe "#value" do
subject { instance.value }

it "returns the angle's numeric value in the current unit" do
expect(subject).to eq(BigMath.PI(10))
end
end

describe "#to_degrees" do
subject { instance.to_degrees }

it "returns a new Degree instance" do
expect(subject).to be_a(Astronoby::Degree)
end

it "converted the degrees value into degrees" do
expect(subject.value).to eq(180)
end
end

it "returns itself" do
expect(subject).to eq(instance)
end
end
end
``````
``````module Astronoby
def initialize(angle)
end

def to_degrees
self.class.as_degrees(@angle * 180 / PI)
end

self
end
end
end
``````

## Conclusion

We have our first step, a working gem called Astronoby that manipulates angles and can convert degrees into radians.

Here is an example of use of the gem:

``````require "astronoby"

Astronoby::Angle
.as_degrees(180)
.value == BigMath.PI(10)

# => true
``````

The source code is now available on Github, with a gem downloadable from RubyGems and a brand new version released.

As a next step, we will have a look at another primary topic, which is time and especially calendars and time zones.

Happy hacking