Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper way to compare decimal values in RSpec?

I have this spec that checks for the expected number:

  expect(animal_cost).to eq(0.7771118644067795e5)

I get an error that says:

  expected: 77711.18644067795
        got: 0.7771118644067795e5

As you can see, the number I got is the same as the one I expected so I'm confused why this is failing. How can I set the spec?

animal_cost.class is a BigDecimal.

like image 731
lost9123193 Avatar asked Jul 24 '20 08:07

lost9123193


People also ask

What is an eql matcher in RSpec?

expect (message).to eq "Hello World!" The keyword eql is an RSpec “matcher”. Here, we will introduce the other types of matchers in RSpec. Matchers to test for object or value equality.

What is the best alternative to the official RSpec documentation?

RSpec is a mature, feature-packed testing framework, but the documentation can be difficult to navigate. As an alternative to the official documentation, this cheat sheet contains short example code to demonstrate all the built-in expectation matchers. These examples were created with RSpec 3.5.

How do you compare decimals?

Decimal numbers are the standard form of representing integers and non-integer numbers. When you compare the decimals, you need to check the digits before the decimal point and check if they are smaller than or greater than the other number.

What are some examples of RSpec's built-in matchers?

For examples of RSpec.describe, it, let, etc., see the previous post: RSpec::Core Cheat Sheet. These are the most commonly used matchers. RSpec.describe 'Common, built-in expectation matchers' do example 'Equality' do expect('x'+'y').to eq('xy') # a == b expect('x'+'y').to eql('xy') # a.eql?


Video Answer


2 Answers

The best way to verify floats using rspec is to use the be_within matcher. Floats cannot be represented completely mathematically accurate in binary. There are bound to be some rounding errors.

In your case one would write:

expect(animal_cost).to be_within(0.001).of(0.7771118644067795e5)
like image 168
nathanvda Avatar answered Nov 14 '22 22:11

nathanvda


[animal_cost] equals 0.7771118644067795e5 [...] it's a BigDecimal

It's a floating point issue. Your BigDecimal with a value of 0.7771118644067795e5 has an exact value of:

77711.18644067795

The float 77711.18644067795 on the other hand has an actual value of: (like most languages, Ruby truncates floats)

77711.186440677949576638638973236083984375

Depending on the comparison, these values might or might not be treated as "equal":

d = BigDecimal('0.7771118644067795e5')
f = 0.7771118644067795e5

d == f      #=> true
f == d      #=> true

d.eql?(f)   #=> true
f.eql?(d)   #=> false

The latter returns false because that's how Float#eql? works:

eql?(obj) → true or false

Returns true only if obj is a Float with the same value as float.

In order to make the test pass with the exact value, you should use a BigDecimal instead of a float:

expect(animal_cost).to eq(BigDecimal('0.7771118644067795e5'))

Personally, I'd avoid such examples because you're (apparently) copying the result into the expectation. It's not at all obvious whether this value is correct or not. (a "cost" with 11 decimal places seems wrong to me) Try to change your example data to get a comprehensible result and maybe also a "whole" number like e.g. 70,000.

like image 45
Stefan Avatar answered Nov 14 '22 21:11

Stefan