Handle rounding edge case

A lower-rate base currency like IDR previously produced less precise quotes.

Fixes #14
This commit is contained in:
Hakan Ensari 2020-05-02 15:21:58 +01:00
parent 2e83b9d50d
commit 2d56ce2e77
3 changed files with 30 additions and 9 deletions

View File

@ -4,6 +4,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased] ## [Unreleased]
### Changed ### Changed
- Handle rounding edge case where a lower-rate base currency like IDR produces less precise quotes
- Sample weekly when querying over a year - Sample weekly when querying over a year
- Bump PostgreSQL to 12 - Bump PostgreSQL to 12

View File

@ -7,6 +7,8 @@ module Roundable
# greater than around 20 are usually quoted to three decimal places and # greater than around 20 are usually quoted to three decimal places and
# exchange rates greater than 80 are quoted to two decimal places. # exchange rates greater than 80 are quoted to two decimal places.
# Currencies over 5000 are usually quoted with no decimal places. # Currencies over 5000 are usually quoted with no decimal places.
#
# https://en.wikipedia.org/wiki/Exchange_rate#Quotations
def round(value) def round(value)
if value > 5000 if value > 5000
value.round value.round
@ -16,8 +18,13 @@ module Roundable
Float(format('%<value>.3f', value: value)) Float(format('%<value>.3f', value: value))
elsif value > 1 elsif value > 1
Float(format('%<value>.4f', value: value)) Float(format('%<value>.4f', value: value))
else # I had originally opted to round smaller numbers simply to five decimal
# places but introduced this refinement to handle an edge case where a
# lower-rate base currency like IDR produces less precise quotes.
elsif value > 0.0001
Float(format('%<value>.5f', value: value)) Float(format('%<value>.5f', value: value))
else
Float(format('%<value>.6f', value: value))
end end
end end
end end

View File

@ -7,25 +7,38 @@ describe Roundable do
include Roundable include Roundable
it 'rounds values over 5,000 to zero decimal places' do it 'rounds values over 5,000 to zero decimal places' do
round(5000.123456).must_equal 5000.0 _(round(5000.123456)).must_equal 5000
end end
it 'rounds values over 80 and below 5,000 to two decimal places' do it 'rounds values over 80 and below 5,000 to two decimal places' do
round(80.123456).must_equal 80.12 _(round(80.123456)).must_equal 80.12
round(4999.123456).must_equal 4999.12 _(round(4999.123456)).must_equal 4999.12
end end
it 'rounds values over 20 and below 80 to three decimal places' do it 'rounds values over 20 and below 80 to three decimal places' do
round(79.123456).must_equal 79.123 _(round(79.123456)).must_equal 79.123
round(20.123456).must_equal 20.123 _(round(20.123456)).must_equal 20.123
end end
it 'rounds values over 1 and below 20 to four decimal places' do it 'rounds values over 1 and below 20 to four decimal places' do
round(19.123456).must_equal 19.1235 _(round(19.123456)).must_equal 19.1235
round(1.123456).must_equal 1.1235 _(round(1.123456)).must_equal 1.1235
end end
it 'rounds values below 1 to five decimal places' do it 'rounds values below 1 to five decimal places' do
round(0.123456).must_equal 0.12346 _(round(0.123456)).must_equal 0.12346
end
it 'rounds values below 0.0001 to six decimal places' do
_(round(0.0000655)).must_equal 0.000066
end
it 'conforms to ECB conventions' do
skip "We don't conform ¯\_(ツ)_/¯"
require 'day'
rates = Day.all.sample.rates.to_a
rates.shuffle.each do |_currency, rate|
_(round(rate)).must_equal rate
end
end end
end end