Bytes to megabytes in ruby - ruby-on-rails

In javascript (or coffeescript), I have the following function:
bytesToMegabytes = (bytes) ->
return Math.round((b/1024/1024) * 100) / 100
I'm trying to recreate it in ruby. I have:
def bytes_to_megabytes (bytes)
(((bytes.to_i/1024/1024) * 100) / 100).round
end
But this rounds differently? For example, 1153597 becomes 1 in the ruby code.

I don't want to be a smart-ass, but no one seems to notice the calculation confusion here. 1 megabyte is simply 1000000 byte (google it). The 1024 is an outdated confusion about 10^2 byte which is 1024 kilobyte. Since 1998 this has become known as a kibibyte (wiki) and is now the norm.
That means you should just divide your byte by 1000000 and you're done. I've added some rounding for extra usefulness:
def bytes_to_megabytes (bytes)
(bytes.to_f / 1000000).round(2)
end
puts bytes_to_megabytes(1153597) # outputs 1.15

Try:
def bytes_to_megabytes (bytes)
bytes / (1024.0 * 1024.0)
end
bytes_to_megabytes(1153597)
#=> 1.1001558303833008
You can make CONSTANT variable like
MEGABYTES = 1024.0 * 1024.0
def bytes_to_megabytes (bytes)
bytes / MEGABYTES
end
bytes_to_megabytes(1153597)
#=> 1.1001558303833008
Now make you clear about to_i and round,
But this rounds differently? For example, 1153597 becomes 1 in the
ruby code.
to_i method take only decimal part it does not roundup the number for that you have to convert to float then roundup
> "148.68".to_i
#=> 148
> "148.68".to_f.round
#=> 149
As Stefen's comment : In Rails You can do simply like this:
> 1153597 / 1.0.megabyte
#=> 1.1001558303833008
> (1153597 / 1.0.megabyte).round
#=> 1
For more details of megabyte method Numeric#megabyte

1153597/1024
=> 1126
1153597/1024/1024
=> 1
This makes sense, since with integer division you get an integer result and 1153597 Bytes is roughly equal to 1MB. If you convert your input to a float first, it may be what you expect:
1153597.0/1024/1024
=> 1.1001558303833008
Therefore, change the code to use to_f instead of to_i, and remove round
def bytes_to_megabytes (bytes)
(((bytes.to_f/1024/1024) * 100) / 100)
end

With Ruby on Rails:
def bytes_to_megabytes(bytes)
(bytes.to_f/1.megabyte).round(2)
end

The equivalent of your JavaScript function would be
def bytes_to_megabytes (bytes)
(bytes.to_f / 1024 / 1024 * 100).round / 100.0
end
Ruby does integer division whereas JavaScript does floating-point division. Thus you have to make sure that at least one operand is a floating-point number.

Related

Consistant parse float values on uneven data

I have a list of float numbers, representing currency, and I need to turn them into integers, for precision.
The task is to turn float numbers into integers, likes:
0.95 => 95
1 => 100
1,465.01 => 146501
The problem is:
I don't have access to change the input csv files
The numbers came in a variety of ways (1.00, 1.0, 1, .95, 0.95, etc)
How can, safely, I turn these numbers into integers?
Some examples of my problem:
('16.81'.to_f * 100).to_i => 1680
('16.81'.to_f * 100_00).to_i / 100 => 1681
('342.28'.to_f * 100).to_i => 34228
('342.28'.to_f * 100_00).to_i / 100 => 34227
__ EDIT __
By the way, I'm using ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin19]
Floating point numbers can't necessarily represent all decimal numbers. This is explained in Is floating point math broken?. As such, when dealing with floats, you are always a bit uncertain and usually need to use rounding to get a desired number.
From your examples, ('16.81'.to_f * 100) results in 1680.9999999999998. Getting the integer value from that cuts off the fractional part, resulting in 1680. By using round instead, you can get the desired integer (which also solves the issue of partial cents). When relying on this, please note the details of how Ruby rounds exactly, specifically the optional half argument).
Instead of relying on Floats however, a better idea is to use BigDecimal numbers instead which allow arbitrary precision floating point numbers.
require 'bigdecimal'
(BigDecimal('16.81') * 100).round
# => 1681
(BigDecimal('.95') * 100).round
# => 95
number = '1,465.01'
# remove any "additional" characters
normalized = number.gsub(/[^-+0-9.]/, '')
(BigDecimal(normalized) * 100).round
# => 146501
In the last example, I have shown how you might cleanup your "human-readable" numbers for consistent parsing. Depending on your source data, you might need to perform additional changes (e.g. if you might have values such as '1.465,01' as is common in e.g. some European countries).
Use Bigdecimal for float numbers and append .to_i to convert it in integer
require 'bigdecimal'
(BigDecimal('16.81') * 100).to_i # 1681
(BigDecimal('342.28') * 100).to_i # 34228
For more details you can refer https://corainchicago.github.io/blog/why-does-ruby-add-numbers-wrong/

Most elegant way to add 1 to an integer if a boolean is true in Ruby?

This will be an easy question for you Ruby experts: What is the most elegant way to add the value of 1 to an integer if a boolean is true?
For example, extra_unit is a boolean, and I want to add 1 to the total only if extra_unit is true.
total = unit_price * (units + (extra_unit ? 1 : 0 ))
Is that the most elegant solution, where, by "elegant", I mean in terms of compact but still readable code?
Given the caveat in my comment to the question above, and if I wanted it on one line and didn't care what it did to the mind of a coworker or my future self, I'd do something like this:
total = unit_price * (bonus ? units + 1 : units)
But, really, it could be written more verbosely without affecting the speed, which would increase the readability:
unit_price = 1.00
units = 1
bonus = true
unit_price * (units + (bonus ? 1 : 0 )) # => 2.0
unit_price * (bonus ? units + 1 : units) # => 2.0
multiplier = if bonus
units + 1
else
units
end
unit_price * multiplier # => 2.0
Those all return the same value so they're equivalent in result.
Running some benchmarks:
require 'fruity'
compare do
t1 {unit_price * (units + (bonus ? 1 : 0 ))}
t2 {unit_price * (bonus ? units + 1 : units)}
t3 {
multiplier = if bonus
units + 1
else
units
end
unit_price * multiplier
}
end
# >> Running each test 65536 times. Test will take about 2 seconds.
# >> t2 is similar to t1
# >> t1 is similar to t3
I ran the benchmarks multiple times, and the positions would swap, with t3 being marginally slower twice, but not enough that Fruity would consistently flag it as such.
So, as far as elegance goes, insisting that it being cryptic or as small as possible doesn't necessarily buy us anything useful, so instead go with readable and still fast. From experience, benchmarks are essential if you're going for elegant; Too often I've been surprised when I thought something would be faster because it was concise, when in comparison another more verbose expression ran circles around it.

How do I write a function to convert duration to time in milliseconds and account for invalid values?

I'm using Rails 5 with Ruby 2.4. I want to convert a string, which represents duration (time) to milliseconds, so I have this function
def duration_in_milliseconds(input)
if input && input.count("a-zA-Z ") == 0
if input.index(".") && !input.index(":")
input.gsub!(/\./, ':')
end
# replace bizarre apostraphes where they intended ":"'s
input.gsub!(/'/, ':')
# Strip out unnecessary white space
input.gsub!(/\A\p{Space}+|\p{Space}+\z/, '')
input.split(':').map(&:to_f).inject(0) { |a, b| a * 60 + b } * 1000
else
0
end
end
This works great for strings like "6:52" (6 mintues, 52 seconds), but also works for things like "52:6" (which is invalid, "52:06" would be correct). I wish to have the function return zero if someone enters in a single digit in any spot other than the first number before the ":". How do I do this? NOte that I still want a duration like "15:24.8" to parse normally (that is 15 minutes, 24 seconds, and eight tenths of a second).
I was trying to come up with a solution for this and ended up reworking the code.
I started splitting the string and removing all the white space. To remove the white space first I ensure that there's no leading nor trailing white space and then I incorporate any amount of extra space before or after the : characters.
Then I assign the values returned from the split to 3 variables I named hr, min and seg. If you're unfamiliar with this, the variables are filled out from left to right and, in case there aren't enough elements, the remaining vars get a nil.
Then, the condition. For this I say: "Return zero if either min or seg don't match two consecutive numeric characters while being present"
And finally, I sum their respective values in seconds, which returns the numer of seconds. And at the end of the line I convert it to milliseconds by multiplying it by 1000
def duration_in_milliseconds(input)
hr, min, seg = input.strip.split(/\s*:\s*/)
return 0 if [min, seg].any? {|value| value && !(value =~ /\d\d/)}
(hr.to_i * 60 * 60 + min.to_i * 60 + seg.to_i) * 1000
end
And I tried it out on a bunch of strings to make sure it worked.
duration_in_milliseconds("0:00:01") # => 1000
duration_in_milliseconds("0:01:01") # => 61000
duration_in_milliseconds("1:01:01") # => 3661000
duration_in_milliseconds("0:15") # => 900000
duration_in_milliseconds("1: 05") # => 3900000
duration_in_milliseconds("1:5") # => 0
duration_in_milliseconds("1:5 ") # => 0
duration_in_milliseconds(" \t 1 : 05 : 15 ") # => 3915000
duration_in_milliseconds("1:5:15") # => 0
duration_in_milliseconds("1") # => 3600000
duration_in_milliseconds("1:5D:15") # => 0
I hope it helped and answered the question (in some way), if not, please don't hessitate to ping me.
Cheers
Fede

Get Price in Cents for Stripe

I have to send the amount of some price in cents to Stripe in order to make charge against a card. In my app, the total_price value is a decimal, i.e in dollars and cents. Obviously, I can convert this to cents by multiplying by 100:
total_price * 100
But the result is still a decimal, and Stripe gives me an 'Invalid amount' error. I know there can be issues with rounding floats. I want to know the safest way to cast my total_price to an integer in Rails. I have seen some reference to a money gem but is this necessary in this case?
Ruby has several methods available to floats, depending on what you need:
(cart.total_price * 100).to_i (discards all decimals)
(cart.total_price * 100).round # .round(numofdecimals)
(cart.total_price * 100).floor # 1.3 => 1
(cart.total_price * 100).ceil # 1.3 => 2
(cart.total_price * 100).to_r #to rationals, e.g. 2.5 => 5/2
I hope this helps.
There is actually a very easy way to do this, directly from a decimal without converting to a float first. Given:
decimal price = BigDecimal('100.00')
You can use BigDecimal's fix() and frac() methods to take the first part, multiply by 100 and add the fraction:
price_in_cents = ((decimal_price.fix * 100) + decimal_price.frac).to_i
Another seemingly simple possibility is to use string manipulation. You can convert the decimal to a string, strip the period, and convert to integer like:
price_in_cents = decimal_price.to_s.sub('.', '').to_i
But the gotcha here is that decimal_price.to_s == '100.0' not '100.00'. If there are non-zero digits in the fraction, then they would be preserved in the conversion, but otherwise one is lost. So if decimal_price == 111.11 then the above string manipulation would return the expected result.

after_update callback issues

I'm trying to recalculate percentages in an after_update callback of my model.
def update_percentages
if self.likes_changed? or self.dislikes_changed?
total = self.likes + self.dislikes
self.likes_percent = (self.likes / total) * 100
self.dislikes_percent = (self.dislikes / total) * 100
self.save
end
end
This doesn't work. The percentage always comes out as a 100 or 0, which completely wrecks everything.
Where am I slipping up? I guarantee that self.likes and self.dislikes are being incremented correctly.
The Problem
When you divide an integer by an integer (aka integer division), most programming languages, including Ruby, assume you want your result to be an Integer. This is mostly due to History, because with lower level representations of numbers, an integer is very different than a number with a decimal point, and division with integers is much faster. So your percentage, a number between 0 and 1, has its decimal truncated, and so becomes either 0 or 1. When multiplied by 100, becomes either 0 or 100.
A General Solution
If any of the numbers in the division are not integers, then integer division will not be performed. The alternative is a number with a decimal point. There are several types of numbers like this, but typically they are referred to as floating point numbers, and in Ruby, the most typical floating point number is of the class Float.
1.0.class.ancestors
# => [Float, Precision, Numeric, Comparable, Object, Kernel]
1.class.ancestors
# => [Fixnum, Integer, Precision, Numeric, Comparable, Object, Kernel]
In Rails' models, floats are represented with the Ruby Float class, and decimal with the Ruby BigDecimal class. The difference is that BigDecimals are much more accurate (ie can be used for money).
Typically, you can "typecaste" your number to a float, which means that you will not be doing integer division any more. Then, you can convert it back to an integer after your calculations if necessary.
x = 20 # => 20
y = 30 # => 30
y.to_f # => 30.0
x.class # => Fixnum
y.class # => Fixnum
y.to_f.class # => Float
20 / 30 # => 0
20 / 30.0 # => 0.666666666666667
x / y # => 0
x / y.to_f # => 0.666666666666667
(x / y.to_f).round # => 1
A Solution For You
In your case, assuming you are wanting integer results (ie 42 for 42%) I think the easiest way to do this would be to multiply by 100 before you divide. That pushes your decimal point as far out to the right as it will ever go, before the division, which means that your number is as accurate as it will ever get.
before_save :update_percentages
def update_percentages
total = likes + dislikes
self.likes_percent = 100 * likes / total
self.dislikes_percent = 100 * dislikes / total
end
Notes:
I removed implicit self you only need them on assignment to disambiguate from creating a local variable, and when you have a local variable to disambiguate that you wish to invoke the method rather than reference the variable
As suggested by egarcia, I moved it to a callback that happens before the save (I selected before_save because I don't know why you would need to calculate this percentage on an update but not a create, and I feel like it should happen after you validate that the numbers are correct -- ie within range, and integers or decimal or whatever)
Because it is done before saving, we remove the call to save in the code, that is already going to happen
Because we are not explicitly saving in the callback, we do not risk an infinite loop, and thus do not need to check if the numbers have been updated. We just calculate the percentages every time we save.
Because likes/dislikes is an integer value and integer/integer = integer.
so you can do one of two things, convert to Float or change your order of operations.
self.likes_percent = (self.likes.to_f/total.to_f) * 100
Or, to keep everything integers
self.likes_percent = (self.likes * 100)/total
I'm not sure that this is the only problem that you have, but after_update gets called after the object is saved.
Try changing the update_percentages before - on a before_update or a before_validate instead. Also, remove the self.save line - it will be called automatically later on if you use one of those callbacks.

Resources