Rails rounding decimal to the nearest power of ten - ruby-on-rails

I'm looking for a rails function that could return the number to the nearest power of ten(10,100,1000), and also need to support number between 0 and 1 (0.1, 0.01, 0.001):
round(9) = 10
round(19) = 10
round(79) = 100
round(812.12) = 1000
round(0.0321) = 0.01
round(0.0921) = 0.1
I've looking on : Round number down to nearest power of ten
the accepted answer using the length of the string, that can't applied to number between 0 and 1.
updated
Round up to nearest power of 10 this one seems great. But I still can't make it work in rails.

I'm not sure about any function which automatically rounds the number to the nearest power of ten. You can achieve it by running the following code:
def rounded_to_nearest_power_of_ten(value)
abs_value = value.abs
power_of_ten = Math.log10(abs_value)
upper_limit = power_of_ten.ceil
lower_limit = power_of_ten.floor
nearest_value = (10**upper_limit - abs_value).abs > (10**lower_limit - abs_value).abs ? 10**lower_limit : 10**upper_limit
value > 0 ? nearest_value : -1*nearest_value
end
Hope this helps.

Let's simplify your problem to the following form - let the input numbers be in the range [0.1, 1), how would rounding of such numbers look like then?
The answer would be simple - for numbers smaller than 0.5 we would return the number 0.1, for larger numbers it would be 1.0.
All we have to do is to make sure that our number will be in that range. We will "move" decimal separator and remember how many moves we made in second variable. This operation is called normalization.
def normalize(fraction)
exponent = 0
while fraction < (1.0/10.0)
fraction *= 10.0
exponent -= 1
end
while fraction >= 1.0
fraction /= 10.0
exponent += 1
end
[fraction, exponent]
end
Using above code you can represent any floating number as a pair of normalized fraction and exponent in base 10. To recreate original number we will move decimal point in opposite direction using formula
original = normalized * base^{exponent}
With data property normalized we can use it in our simple rounding method like that:
def round(number)
fraction, exponent = normalize(number)
if fraction < 0.5
0.1 * 10 ** exponent
else
1.0 * 10 ** exponent
end
end

if the number is >= 1.0, this should work.
10 ** (num.floor.to_s.size - ( num.floor.to_s[0].to_i > 4 ? 0 : 1))

Try this:
def round_tenth(a)
if a.to_f >= 1
return 10 ** (a.floor.to_s.size - ( a.floor.to_s[0].to_i > 4 ? 0 : 1))
end
#a = 0.0392
c = a.to_s[2..a.to_s.length]
b = 0
c.split('').each_with_index do |s, i|
if s.to_i != 0
b = i + 1
break
end
end
arr = Array.new(100, 0)
if c[b-1].to_i > 4
b -= 1
if b == 0
return 1
end
end
arr[b-1] = 1
return ("0." + arr.join()).to_f
end

class Numeric
def name
def helper x, y, z
num = self.abs
r = 1
while true
result = nil
if num.between?(x, y)
if num >= y/2.0
result = y.round(r+1)
else
result = x.round(r)
end
return self.negative? ? -result : result
end
x *= z; y *= z; r += 1
end
end
if self.abs < 1
helper 0.1, 1, 0.1
else
helper 1, 10, 10
end
end
end
Example
-0.049.name # => -0.01
12.name # => 10
and so on, you are welcome!

Related

How to round Decimals to the First Significant Figure in Ruby

I am attempting to solve an edge case to a task related to a personal project.
It is to determine the unit price of a service and is made up of the total_amount and cost.
Examples include:
# 1
unit_price = 300 / 1000 # = 0.3
# 2
unit_price = 600 / 800 # = 0.75
# 3
unit_price = 500 / 1600 # = 0.3125
For 1 and 2, the unit_prices can stay as they are. For 3, rounding to 2 decimal places will be sufficient, e.g. (500 / 1600).round(2)
The issue arises when the float becomes long:
# 4
unit_price = 400 / 56000 # = 0.007142857142857143
What's apparent is that the float is rather long. Rounding to the first significant figure is the aim in such instances.
I've thought about using a regular expression to match the first non-zero decimal, or to find the length of the second part and apply some logic:
unit_price.match ~= /[^.0]/
unit_price.to_s.split('.').last.size
Any assistance would be most welcome
One should use BigDecimal for this kind of computation.
require 'bigdecimal'
bd = BigDecimal((400.0 / 56000).to_s)
#⇒ 0.7142857142857143e-2
bd.exponent
#⇒ -2
Example:
[10_000.0 / 1_000, 300.0 / 1_000, 600.0 / 800,
500.0 / 1_600, 400.0 / 56_000].
map { |bd| BigDecimal(bd.to_s) }.
map do |bd|
additional = bd.exponent >= 0 ? 0 : bd.exponent + 1
bd.round(2 - additional) # THIS
end.
map(&:to_f)
#⇒ [10.0, 0.3, 0.75, 0.31, 0.007]
You can detect the length of the zeros string with regex. It's a bit ugly, but it works:
def significant_round(number, places)
match = number.to_s.match(/\.(0+)/)
return number unless match
zeros = number.to_s.match(/\.(0+)/)[1].size
number.round(zeros+places)
end
pry(main)> significant_round(3.14, 1)
=> 3.14
pry(main)> significant_round(3.00014, 1)
=> 3.0001
def my_round(f)
int = f.to_i
f -= int
coeff, exp = ("%e" % f).split('e')
"#{coeff.to_f.round}e#{exp}".to_f + int
end
my_round(0.3125)
#=> 0.3
my_round(-0.3125)
#=> -0.3
my_round(0.0003625)
#=> 0.0004
my_round(-0.0003625)
#=> -0.0004
my_round(42.0031)
#=> 42.003
my_round(-42.0031)
#=> -42.003
The steps are as follows.
f = -42.0031
int = f.to_i
#=> -42
f -= int
#=> -0.0031000000000034333
s = "%e" % f
#=> "-3.100000e-03"
coeff, exp = s.split('e')
#=> ["-3.100000", "-03"]
c = coeff.to_f.round
#=> -3
d = "#{c}e#{exp}"
#=> "-3e-03"
e = d.to_f
#=> -0.003
e + int
#=> -42.003
To instead keep only the most significant digit after rounding, change the method to the following.
def my_round(f)
coeff, exp = ("%e" % f).split('e')
"#{coeff.to_f.round}e#{exp}".to_f
end
If f <= 0 this returns the same as the earlier method. Here is an example when f > 0:
my_round(-42.0031)
#=> -40.0

How can I write this conditional in fewer lines?

I wrote this code in my model:
percentage = 0
if self.date_of_birth.present?
percentage += 15
end
if self.gender.present?
percentage += 15
end
if self.relationship_status.present?
percentage += 10
end
if self.language.present?
percentage += 10
end
if self.qualification.present?
percentage += 10
end
if self.interests.present?
if self.interests.count >= 10
percentage += 10
else
percentage += self.interests.count * 5
end
end
But it does not look good. It is a lot of code for a small thing. I want to reduce the number of lines.
You can do it inline, like this:
percentage += 15 if self.date_of_birth.present?
Instead of this:
if self.interests.count >= 10
percentage += 10
else
percentage += self.interests.count*5
end
You can use a ternary operator:
percentage += self.interests.count >= 10 ? 10 : self.interests.count*5
percentage = [
(15 if date_of_birth.present?),
(15 if gender.present?),
(10 if relationship_status.present?),
(10 if language.present?),
(10 if qualification.present?),
((counts = interests.count.to_i) >= 10 ? 10 : (counts * 5)),
].compact.sum
You could use an instance method in your model:
#app/models/model.rb
class Model < ActiveRecord::Base
def percentage
value = 0
values = [[:date_of_birth, 15], [:gender, 15], [:relationship_status,10], [:language,10], [:qualification, 10]]
values.each do |attr,val|
value += val if self.send(attr).present?
end
value += self.interests.count >= 10 ? 10 : self.interests.count*5 if self.interests.present?
# Rails should return the value of the last line, which is the "value" var
end
end
This would allow you to use #user.percentage, where #user is your instance var for the model.
Personally, I don't think that "less lines" is a good idea, but if you want your code in less lines, you can write it like this:
percentage = 0; if date_of_birth.present? then percentage += 15 end; if gender.present? then percentage += 15 end; if relationship_status.present? then percentage += 10 end; if language.present? then percentage += 10 end; if qualification.present? then percentage += 10 end; if interests.present? then if interests.count >= 10 then percentage += 10 else percentage += interests.count*5 end end
In Ruby, you can (almost) always replace linebreaks with semicolons to make your code fit on less lines. In fact, every Ruby program can always be written on a single line.
inc_att = ["date_of_birth", "gender", "relationship_status" , "language", "qualification", "interests"]
inc_att.each do |s|
if self[s].present? && (s == "date_of_birth" || s == "gender")
percentage += 15
elsif self[s].present? && s == "interests" && self[s].count < 10
percentage += self[s].count * 5
else
percentage += 10 if self[s].present?
end
end
Have a look into it
inc_att = ["date_of_birth", "gender", "relationship_status" , "language", "qualification", "interests"]
inc_att.each do |s|
if self[s].present? && (s == "date_of_birth" || s == "gender")
percentage += 15
elsif self[s].present? && s == "interests" && self[s].count < 10
percentage += self[s].count * 5
else
percentage += 10 if self[s].present?
end
end
Inspired by #sawa's answer:
counts = interests.count.to_i
percentage = (counts >= 10 ? 10 : (counts * 5)) +
[
date_of_birth.present? && 15,
gender.present? && 15,
relationship_status.present? && 10,
language.present? && 10,
qualification.present? && 10,
].select(&:itself).sum

Attempt to get numbers instead of NaN values when printing

I've a simple program with a for loop where i calculate some value that I print to the screen, but only the first value is printed, the rest is just NaN values. Is there any way to fix this? I suppose the numbers might have a lot of decimals thus the NaN issue.
Output from program:
0.18410
NaN
NaN
NaN
NaN
etc.
This is the code, maybe it helps:
for i=1:30
t = (100*i)*1.1*0.5;
b = factorial(round(100*i)) / (factorial(round((100*i)-t)) * factorial(round(t)));
% binomial distribution
d = b * 0.5^(t) * 0.5^(100*i-(t));
% cumulative
p = binocdf(1.1 * (100*i) * 0.5,100*i,0.5);
% >= AT LEAST
result = 1-p + d;
disp(result);
end
You could do the calculation of the fraction yourself.
Therefore you need to calculate $d$ directly. Then you can get all values of the numerators and the denominators and multiply them by hand and make sure that the result will not get too big. The following code is poorly in terms of speed and memory, but it may be a good start:
for i=1:30
t = (55*i);
b = factorial(100*i) / (factorial(100*i-t) * factorial(t));
% binomial distribution
d = b * 0.5^(t) * 0.5^(100*i-(t));
numerators = 1:(100*i);
denominators = [1:(100*i-t),1:55*i,ones(1,100*i)*2];
value = 1;
while length(numerators) > 0 || length(denominators) > 0
if length(numerators) == 0
value = value/denominators(1);
denominators(1) = [];
elseif length(denominators) == 0
value = value* numerators(1);
numerators(1) = [];
elseif value > 10000
value = value/denominators(1);
denominators(1) = [];
else
value = value* numerators(1);
numerators(1) = [];
end
end
% cumulative
p = binocdf(1.1 * (100*i) * 0.5,100*i,0.5);
% >= AT LEAST
result = 1-p + value;
disp(result);
end
output:
0.1841
0.0895
0.0470
0.0255
0.0142
0.0080
0.0045
...
Take a look at the documentation of factorial:
Note that the factorial function grows large quite quickly, and
even with double precision values overflow will occur if N > 171.
For such cases consider 'gammaln'.
On your second iteration you are already doing factorial (200) which returns Inf and then Inf/Inf returns NaN.

Sum of primes in a number - Lua

I'm trying to calculate the sum of the prime numbers in a given number. For instance, for the number 123456, the result will be 10 because 2+3+5 = 10.
I tried to write a code that does that in Lua but I had some issues.
First, here is the code:
function isPrime(num)
if(num == 1 or (num ~= 2 and num%2 == 0)) then
return false
end
for i=3, math.sqrt(num), 2 do
if(num%i == 0) then
return false
end
end
return true
end
function sumOfPrimes(num)
local sum = 0
for digit in string.gmatch(num,"%d") do
local prime = isPrime(digit)
if(isPrime(digit)) then
sum = sum + digit
end
print(digit)
end
return sum
end
function main()
print(sumOfPrimes(123456))
end
main()
It returnes 9 instead of 10. Another thing I've noticed is it adds 1 also to sum, but 1 isn't a prime. What's the problem here?
string.gmatch returns a string, you need to convert it to number before doing calculations
Btw, you are doing the prime validation twice on your loop.
This is a fixed version (returns 10 as expected):
...
function sumOfPrimes(num)
local sum = 0
for digit in string.gmatch(num, "%d") do
digit = tonumber(digit) --needed conversion
local prime_digit = isPrime(digit)
if prime_digit then
sum = sum + digit
end
end
return sum
end

Ruby/Rails while loop not breaking correctly?

I am working on a client's site, and I'm writing an amortization schedule calculator in in ruby on rails. For longer loan term calculations, it doesn't seem to be breaking when the balance reaches 0
Here is my code:
def calculate_amortization_results
p = params[:price].to_i
i = params[:rate].to_d
l = params[:term].to_i
j = i/(12*100)
n = l * 12
m = p * (j / (1 - (1 + j) ** (-1 * n)))
#loanAmount = p
#rateAmount = i
#monthlyAmount = m
#amort = []
#interestAmount = 0
while p > 0
line = Hash.new
h = p*j
c = m-h
p = p-c
line["interest"] = h
line["principal"] = c
if p <= 0
line["balance"] = 0
else
line["balance"] = p
end
line["payment"] = h+c
#amort.push(line)
#interestAmount += h
end
end
And here is the view:
- #amort.each_with_index do |a, i|
%li
.m
= i+1
.i
= number_to_currency(a["interest"], :unit => "$")
.p
= number_to_currency(a["principal"], :unit => "$")
.pp
= number_to_currency(a["payment"], :unit => "$")
.b
= number_to_currency(a["balance"], :unit => "$")
What I am seeing is, in place of $0.00 in the final payment balance, it shows "-$-inf", iterates one more loop, then displays $0.00, but shows "-$-inf" for interest. It should loop until p gets to 0, then stop and set the balance as 0, but it isn't. Any idea what I've done wrong?
The calculator is here. It seems to work fine for shorter terms, like 5 years, but longer terms cause the above error.
Edit:
Changing the while loop to n.times do
and then changing the balance view to
= number_to_currency(a["balance"], :unit => "$", :negative_format => "$0.00")
Is a workaround, but i'd like to know why the while loop wouldn't work correctly
in Ruby the default for numerical values is Fixnum ... e.g.:
> 15 / 4
=> 3
You will see weird rounding errors if you try to use Fixnum values and divide them.
To make sure that you use Floats, at least one of the numbers in the calculation needs to be a Float
> 15.0 / 4
=> 3.75
> 15 / 4.0
=> 3.75
You do two comparisons against 0 , which should be OK if you make sure that p is a Float.
As the other answer suggests, you should use "decimal" type in your database to represent currency.
Please try if this will work:
def calculate_amortization_results
p = params[:price].to_f # instead of to_i
i = params[:rate].to_f # <-- what is to_d ? use to_f
l = params[:term].to_i
j = i/(12*100.0) # instead of 100
n = l * 12
m = p * (j / (1 - (1 + j) ** (-1 * n))) # division by zero if i==0 ==> j==0
#loanAmount = p
#rateAmount = i
#monthlyAmount = m
#amort = []
#interestAmount = 0.0 # instead of 0
while p > 0
line = Hash.new
h = p*j
c = m-h
p = p-c
line["interest"] = h
line["principal"] = c
if p <= 0
line["balance"] = 0
else
line["balance"] = p
end
line["payment"] = h+c
#amort.push(line)
#interestAmount += h
end
end
If you see "inf" in your output, you are doing a division by zero somewhere.. better check the logic of your calculation, and guard against division by zero.
according to Wikipedia the formula is:
http://en.wikipedia.org/wiki/Amortization_calculator
to improve rounding errors, it's probably better to re-structure the formula like this:
m = (p * j) / (1 - (1 + j) ** (-1 * n) # these are two divisions! x**-1 == 1/x
which is equal to:
m = (p * j) + (p * j) / ((1 + j) ** n) - 1.0)
which is equal to: (use this one)
q = p * j # this is much larger than 1 , so fewer rounding errors when dividing it by something
m = q + q / ((1 + j) ** n) - 1.0) # only one division
I think it has something to do with the floating point operations precision. It has already been discussed here: Ruby number precision with simple arithmetic and it would be better to use decimal format for financial purposes.
The answer could be computing the numbers in the loop, but with precomputed number of iterations and from the scratch.

Resources