From 2d56ce2e77022e5450a0572df06aae934d5a299d Mon Sep 17 00:00:00 2001 From: Hakan Ensari Date: Sat, 2 May 2020 15:21:58 +0100 Subject: [PATCH] Handle rounding edge case A lower-rate base currency like IDR previously produced less precise quotes. Fixes #14 --- CHANGELOG.md | 1 + lib/roundable.rb | 9 ++++++++- spec/roundable_spec.rb | 29 +++++++++++++++++++++-------- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a543859..2970b11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Changed +- Handle rounding edge case where a lower-rate base currency like IDR produces less precise quotes - Sample weekly when querying over a year - Bump PostgreSQL to 12 diff --git a/lib/roundable.rb b/lib/roundable.rb index bcaa0b2..dacc813 100644 --- a/lib/roundable.rb +++ b/lib/roundable.rb @@ -7,6 +7,8 @@ module Roundable # greater than around 20 are usually quoted to three decimal places and # exchange rates greater than 80 are quoted to two decimal places. # Currencies over 5000 are usually quoted with no decimal places. + # + # https://en.wikipedia.org/wiki/Exchange_rate#Quotations def round(value) if value > 5000 value.round @@ -16,8 +18,13 @@ module Roundable Float(format('%.3f', value: value)) elsif value > 1 Float(format('%.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('%.5f', value: value)) + else + Float(format('%.6f', value: value)) end end end diff --git a/spec/roundable_spec.rb b/spec/roundable_spec.rb index 260d44f..8102fa2 100644 --- a/spec/roundable_spec.rb +++ b/spec/roundable_spec.rb @@ -7,25 +7,38 @@ describe Roundable do include Roundable 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 it 'rounds values over 80 and below 5,000 to two decimal places' do - round(80.123456).must_equal 80.12 - round(4999.123456).must_equal 4999.12 + _(round(80.123456)).must_equal 80.12 + _(round(4999.123456)).must_equal 4999.12 end it 'rounds values over 20 and below 80 to three decimal places' do - round(79.123456).must_equal 79.123 - round(20.123456).must_equal 20.123 + _(round(79.123456)).must_equal 79.123 + _(round(20.123456)).must_equal 20.123 end it 'rounds values over 1 and below 20 to four decimal places' do - round(19.123456).must_equal 19.1235 - round(1.123456).must_equal 1.1235 + _(round(19.123456)).must_equal 19.1235 + _(round(1.123456)).must_equal 1.1235 end 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