rspec big decimal matcher - ruby-on-rails

-:total_cost_with_tax => #<BigDecimal:7fda9d17aaf0,'0.105225E4',18(45)>,
-:total_cost_without_tax => #<BigDecimal:7fda9d17b450,'0.972E3',9(36)>,
-:total_last_installment_amount => #<BigDecimal:7fda9d17b978,'0.8011E2',18(45)>,
-:total_monthly_installment_amount => #<BigDecimal:7fda9d17abb8,'0.8011E2',18(45)>,
-:total_tax => #<BigDecimal:7fda9d17b068,'0.8025E2',18(45)>,
+:total_cost_with_tax => #<BigDecimal:7fda9d0184c8,'0.105225E4',18(36)>,
+:total_cost_without_tax => #<BigDecimal:7fda91ff2b48,'0.972E3',9(27)>,
+:total_last_installment_amount => #<BigDecimal:7fda91fee548,'0.8011E2',18(36)>,
+:total_monthly_installment_amount => #<BigDecimal:7fda91fe72c0,'0.8011E2',18(36)>,
+:total_tax => #<BigDecimal:7fda9d00a2b0,'0.8025E2',18(36)>,
So, these are littered throughout some of my tests... rspec 2, rails 3. I'm comparing hashes using .should eq() to compare. I can't seem to get the incantation right. Seems like it's a precision thing, which seems silly.

RSpec 3 has BigDecimal eq:
x.should eq(y)
expect(x).to eq(y)
If you're comparing a BigDecimal to a Float, be aware the precision can affect the comparison.
You can use this:
x.should be_within(delta).of(y)
expect x.to be_within(delta).of(y)
If you're comparing two BigDecimal numbers that have different precisions, be aware that these numbers can show different results from inspect and also from hash depending on your platform and what version of Ruby BigDecimal you're running.
For example, this can happen:
BigDecimal.new("2").hash == BigDecimal.new("2.0").hash
=> false
Your output shows that your BigDecimal string representations are slightly different.
Here's what your strings mean:
Part 1 is the object address.
Part 2 is the number value represented as a string.
Part 3 is the number of significant digits then the maximum number of significant digits.
Your output shows that your strings have different object addresses, identical number values (i.e. part 2), identical significant digits (the first number in part 3), but different numbers of maximum significant digits.
For your question, using RSpec 2 and comparing BigDecimal hashes, you'll solve your issue by using the rspec be_within matcher.
Note that Ruby BigDecimal numbers and Float numbers are both floating point numbers:
A BigDecimal is a arbitrary-precision decimal floating point number.
A Float is the native architecture's double-precision binary floating point number.
You can see the BigDecimal decimal floating-point representation by doing this:
require 'bigdecimal'
x=BigDecimal.new(100)
=> #<BigDecimal:7f8e62038570,'0.1E3',9(27)>
The significand part is '0.1' and the exponent part is 'E3'.
Note that this is for the typical Ruby MRI/KRI VM. Implementations may be different on other Ruby VMs, such as JRuby, because Java has its own bignum code.
When you compare two different types of floating-point numbers, such as BigDecimal and Float, you can get results that may seem counter-intuitive because the types use different bases (decimal vs. binary), different precisions, and different Ruby classes.
Example:
BigDecimal.new("1.111111111111111") === 1.111111111111111
=> true
BigDecimal.new("1.1111111111111111") === 1.1111111111111111
=> false

With Rspec 3.x you can use for BigDecimal the eq matcher like you can use it for float.
RSpec.describe "an integer" do
it "is equal to a float of the same value" do
expect(5).to eq(5.0)
end
end
For more information see the RSpec documentation:
https://www.relishapp.com/rspec/rspec-expectations/v/3-4/docs/built-in-matchers/equality-matchers

Related

How can I denote decimal floating point in swagger specs?

I would like to denote decimal with 2 places and decimal with 1 place in my api documentation. I'm using swagger 2.0, Is there inbuilt defined type or any other 'round' parameter in the specs, or my only option is to use 'x-' extension?
OpenAPI (fka Swagger) Specification uses a subset of JSON Schema to describe the data types.
If the parameter is passed as a number, you can try using multipleOf as suggested in this Q&A:
type: number
multipleOf: 0.1 # up to 1 decimal place, e.g. 4.2
# multipleOf: 0.01 # up to 2 decimal places, e.g. 4.25
Hovewer, multipleOf validation against floating-point numbers can be unreliable due to floating-point math specifics.
If your number if passed as a string, you can specify a regex pattern for the desired number format:
type: string
pattern: your_regex
In any case, you can also document any restrictions verbally in the description.

Lua significant figures

I'm trying to make a function that rounds a number up to a certain number of significant figures given by the user, for example if the user gives me the number
234.235534 with 5 significant numbers, the function should return 234.24
I think you're looking for the [fs]?printf's %g modifier.
converts floating-point number to decimal or decimal exponent notation
depending on the value and the precision.
where, the precision is defined by:
. followed by integer number or *, or neither that specifies
precision of the conversion. In the case when * is used, the
precision is specified by an additional argument of type int. If the
value of this argument is negative, it is ignored. If neither a number
nor * is used, the precision is taken as zero.
So, you want:
> return ("%.5g"):format(234.235534)
234.24
> return ("%.6g"):format(x)
234.236
I'm not much of a programmer, but I came up with this for my own use after I was disappointed by other rounding functions people recommended in lua. This should do what you asked.
function sigFig(num,figures)
local x=figures - math.ceil(math.log10(math.abs(num)))
return(math.floor(num*10^x+0.5)/10^x)
end
now in terms of significant digits, it won't add additional zeros to a number to signify precision. For example:
sigFig(234.235534,5) will yield 234.24
sigFig(234.0000001,6) will yield 234.0, not 234.000

Storing Prices with Cents in Rails

Ok I have a Stripe charge to which I am applying taxes. Stripe takes in a number as cents, so it leaves you with a number like 10015 instead of 100.15.
In my controller, I am sending the number to ActiveRecord as 10015/100.0
When I retrieve it, it gives me #<BigDecimal:7fca81f71130,'0.1243E3',18(27)>>
Whats going on ?
I tried
rails g migration add_expense_to_user expense:integer
and
rails g migration add_expense_to_user expense:decimal
to whose migration I added
add_column :user, :expense, :decimal, precision: 6, scale: 2
which is the current setup.
How do I store / retrieve the value if it is stored as 10015/100
The BigDecimal is just the type that Rails uses for decimal types in DBs. 0.1243E3 is scientific notation, short for 0.1243 x 10³ - ie. 124.3
You can get a regular float from it by just using the .to_f method in Ruby, or you can pass a BigDecimal into other Rails helpers, like number_to_currency(the_big_decimal) will produce "$124.30"
So, in other words, with the BigDecimal you probably already have what you're asking for in this question.
When you access the data, you need to call .to_f.
In irb:
a = BigDecimal.new(5, 2)
a
=> #<BigDecimal:1ad7c98,'0.5E1',9(27)>
a.to_f
=> 5.0
The value is stored inside the BigDecimal object with arbitrary precision. The "common" representation of non-integer values (i.e. floats) however doesn't provide this precision. Thus, BigDecimal provides various options to convert its value to other types.
You can e.g use expense.to_f to get a floating point representation of the BigDecimal value (and thus loosing the precision along the way). Alternatively, if you just want to print the value, you could use one of the to_s method to format the value as a string:
expense.to_s
# => "124.3"
See the BigDecimal documentation for details.

Rails is messing up BigDecimals by throwing in Floating Point Logic

I'm playing around with rails a bit and I have found something strange. For storing a money value I use the typical decimal data type which active record converts to BigDecimal. I considered this to be precise and I thought to avoid the odd behavior of floating point math. But when I store 99.99 to the db everything works fine, but when the records gets loaded by active record it loses precision and converts to something like 99.9899999999. This looks like a floating point issue.
I made some tests and found out that creating a BigDecimal like this b = BigDecimal.new("99.99") leads to a "clean" variable but building it this way b = BigDecimal.new(99.99) leads to the "unclean" version that I want to avoid.
I guess, that ActiveRecord reconstructs the BigDecimal with an intermediate float when loading the record from the database. This is not what I want and I would like to know if it can be avoided.
Ruby Version 1.9.3p0
Rails 3.2.9
Sqlite 3.7.9
Your problem is that you're using SQLite and SQLite doesn't have native support for numeric(m,n) data types. From the fine manual:
1.0 Storage Classes and Datatypes
Each value stored in an SQLite database (or manipulated by the database engine) has one of the following storage classes:
NULL. The value is a NULL value.
INTEGER. The value is a signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes depending on the magnitude of the value.
REAL. The value is a floating point value, stored as an 8-byte IEEE floating point number.
TEXT. The value is a text string, stored using the database encoding (UTF-8, UTF-16BE or UTF-16LE).
BLOB. The value is a blob of data, stored exactly as it was input.
Read further down that page to see how SQLite's type system works.
Your 99.99 may be BigDecimal.new('99.99') in your Ruby code but it is almost certainly the REAL value 99.99 (i.e. an eight byte IEEE floating point value) inside SQLite and there goes the neighborhood.
So switch to a better database in your development environment; in particular, develop on top of whatever database you're going to be deploying on.
Don't use floating point for monetary values
Yes, exactly, SQLite is messing up your BigDecimal values.
The fundamental problem is that the FP format cannot store most decimal fractions correctly.
I believe you have about four choices:
Round everything to, say, two decimal places so that you never notice the slightly-off values.
Store your BigDecimal values in SQLite with the TEXT or BLOB storage class.
Use a different db that has some sort of decimal string support.
Scale everything to integral values and use the INTEGER storage class.
The problem is that FP fractions are rational numbers of the form x/2n. But the decimal monetary amounts have fractions that are x/(2n * 5m). The representations just aren't compatible. For example, in 0.01 ... 0.99, only 0.25, 0.50, and 0.75 have exact binary representations.

How safe is comparing numbers in lua with equality operator?

In my engine I have a Lua VM for scripting. In the scripts, I write things like:
stage = stage + 1
if (stage == 5) then ... end
and
objnum = tonumber("5")
if (stage == objnum)
According to the Lua sources, Lua uses a simple equality operator when comparing doubles, the internal number type it uses.
I am aware of precision problems when dealing with floating point values, so I want to know if the comparison is safe, that is, will there be any problems with simply comparing these numbers using Lua's default '==' operation? If so, are there any countermeasures I can employ to make sure 1+2 always compares as equal to 3? Will converting the values to strings work?
You may be better off by converting to string and then comparing the results if you only care about equality in some cases. For example:
> print(21, 0.07*300, 21 == 0.07*300, tostring(21) == tostring(0.07*300))
21 21 false true
I learned this hard way when I gave my students an assignment with these numbers (0.07 and 300) and asked them to implement a unit test that then miserably failed complaining that 21 is not equal 21 (it was comparing actual numbers, but displaying stringified values). It was a good reason for us to have a discussion about comparing floating point values.
I can employ to make sure 1+2 always compares as equal to 3?
You needn't worry. The number type in Lua is double, which can hold many more integers exactly than a long int.
Comparison and basic operations on doubles is safe in certain situations. In particular if the numbers and their result can be expressed exactly - including all low value integers.
So 2+1 == 3 will be fine for doubles.
NOTE: I believe there's even some guarantees for certain math functions ( like pow and sqrt ) and if your compiler/library respects those then sqrt(4.0)==2.0 or 4.0 == pow(2.0,2.0) will reliably be true.
By default, Lua is compiled with c++ floats, and behind the scenes number comparisons boils down to float comparisons in c/c++, which are indeed problematic and discussed in several threads, e.g. most-effective-way-for-float-and-double-comparison.
Lua makes the situation only slightly worse by converting all numbers, including c++ integers, into floats. So you need to keep it in mind.

Resources