How to calculate PI in Ruby? - ruby

I have to get the exact amount of decimal digits of pi, so I tried using this method:
require "bigdecimal/math"
include BigMath
input = 9
output = BigMath.PI(input).to_s
puts output
but instead of getting this as a result:
output: 3.141592653 # Which are the first 9 decimal digits of PI
I get this:
output: 0.3141592653589793238462643313830947690870926e1
What am I doing wrong?

Since π is an irrational number, its decimal representation never ends and never settles into a permanently repeating pattern.
Therefore, algorithms for calculation of π can find only results with specified precision.
BigMath.PI - is not an exception.
Parameter prec which we pass as a first argument, means precision (that is the number of accurate digits after the decimal point).
When we execute BigMath.PI(9), we obtain a BigDecimal instance, which string representation is 0.3141592653589793238462643313830947690870926e1.
If we look more closely at this string we can see e1 in the end.
Such notation is common in math and it means:
Hence, in our case
0.3141592653589793238462643313830947690870926e1
is the same as
0.3141592653589793238462643313830947690870926 * 10 = 3.141592653589793238462643313830947690870926
Since we passed 9 to BigMath.PI, we have at least 9 accurate digits after the decimal dot
3.141592653589793238462643313830947690870926
3.141592653_________________________________
(Actually, when we pass 9 to BigMath.PI, it returns more than 9 accurate digits after the decimal dot, but we should not rely on this fact. You can try to compare if you like).
And probably the last thing to mention: we can't just convert BigDecimal to Float, if we care about accuracy, because Float in general stores only 15 digits after the decimal dot.
So, if your goal is to be able to show π with an arbitrary number of digits after the decimal dot, you can use the following method:
require 'bigdecimal/math'
##
# Returns PI as a string with number_of_digits_after_decimal_dot.
#
def pi(number_of_digits_after_decimal_dot: 2)
# Let's assume presicion equals to 2 as an example
precision = number_of_digits_after_decimal_dot
result = BigMath.PI(precision)
# => ВigDecimal 0.31415926535897932384671233672993238432e1
result = result.truncate(precision).to_s
# => String 0.314e1
# Remove '0.'
result = result[2..-1]
# => String 3141e1
# Remove 'e1'
result = result.split('e').first
# => String 3141
result = result.insert(1, '.')
# => String 3.141
result
end
If not, then, please, update your question to make it less ambiguous.

Related

Finding the first decimal in a string

I have strings that consist of two lots of numbers. I am trying to split them into two substrings so that there are two decimal places after each decimal.
I have this code:
if homeodds.length == 10 then
homeoddsp = homeodds[0,5].to_f
bookieh = homeodds[5,5].to_f
else
homeoddsp = homeodds[0,4].to_f
bookieh = homeodds[4,4].to_f
end
It handles an eight figure string fine:
"1.211.90" translates to "1.21" and "1.90".
and a ten figure string also works:
"12.2113.00" translates to "12.21" and "13.00".
When I have a nine figure string like "9.1110.00" or "10.119.55", I need to find out where the first decimal is, and take two digits after that so that the remainder becomes the second substring. These cases, I can't consistently do.
You could use String#scan for this particular problem
def decimal_splits(string)
string.scan(/\d+\.\d{2}/)
end
To break down the regular expression:
\d+ 1 or more number of digits
\. the decimal point
\d{2} two digits
The result will be an array of matches for the expression.
decimal_splits("9.1110.00")
#=> ["9.11", "10.00"]
decimal_splits("12.2113.00")
#=> ["12.21", "13.00"]
decimal_splits("1.211.90")
#=> ["1.21", "1.90"]
decimal_splits("10.119.55")
#=> ["10.11", "9.55"]

How to convert bytes in number into a string of characters? (character representation of a number)

How do I easily convert a number, e.g. 0x616263, equivalently 6382179 in base 10, into a string by dividing the number up into sequential bytes? So the example above should convert into 'abc'.
I've experimented with Array.pack but cant figure out how to get it to convert more than one byte in the number, e.g. [0x616263].pack("C*") returns 'c'.
I've also tried 0x616263.to_s(256), but that throws an ArgumentError: invalid radix. I guess it needs some sort of encoding information?
(Note: Other datatypes in pack like N work with the example I've given above, but only because it fits within 4 bytes, so e.g. [0x616263646566].pack("N") gives cdef, not abcdef)
This question is vaguely similar to this one, but not really. Also, I sort of figured out how to get the hex representation string from a character string using "abcde".unpack("c*").map{|c| c.to_s(16)}.join(""), which gives '6162636465'. I basically want to go backwards.
I don't think this is an X-Y problem, but in case it is - I'm trying to convert a number I've decoded with RSA into a character string.
Thanks for any help. I'm not too experienced with Ruby. I'd also be interested in a Python solution (for fun), but I don't know if its right to add tags for two separate programming languages to this question.
To convert a single number 0x00616263 into 3 characters, what you really need to do first is separate them into three numbers: 0x00000061, 0x00000062, and 0x00000063.
For the last number, the hex digits you want are already in the correct place. But for the other two, you have to do a bitshift using >> 16 and >> 8 respectively.
Afterwards, use a bitwise and to get rid of the other digits:
num1 = (0x616263 >> 16) & 0xFF
num2 = (0x616263 >> 8) & 0xFF
num3 = 0x616263 & 0xFF
For the characters, you could then do:
char1 = ((0x616263 >> 16) & 0xFF).chr
char2 = ((0x616263 >> 8) & 0xFF).chr
char3 = (0x616263 & 0xFF).chr
Of course, bitwise operations aren't very Ruby-esque. There are probably more Ruby-like answers that someone else might provide.
64 bit integers
If your number is smaller than 2**64 (8 bytes), you can :
convert the "big-endian unsigned long long" to 8 bytes
remove the leading zero bytes
Ruby
[0x616263].pack('Q>').sub(/\x00+/,'')
# "abc"
[0x616263646566].pack('Q>').sub(/\x00+/,'')
# "abcdef"
Python 2 & 3
In Python, pack returns bytes, not a string. You can use decode() to convert bytes to a String :
import struct
import re
print(re.sub('\x00', '', struct.pack(">Q", 0x616263646566).decode()))
# abcdef
print(re.sub('\x00', '', struct.pack(">Q", 0x616263).decode()))
# abc
Large numbers
With gsub
If your number doesn't fit in 8 bytes, you could use a modified version of your code. This is shorter and outputs the string correctly if the first byte is smaller than 10 (e.g. for "\t") :
def decode(int)
if int < 2**64
[int].pack('Q>').sub(/\x00+/, '')
else
nhex = int.to_s(16)
nhex = '0' + nhex if nhex.size.odd?
nhex.gsub(/../) { |hh| hh.to_i(16).chr }
end
end
puts decode(0x616263) == 'abc'
# true
puts decode(0x616263646566) == 'abcdef'
# true
puts decode(0x0961) == "\ta"
# true
puts decode(0x546869732073656e74656e63652069732077617920746f6f206c6f6e6720666f7220616e20496e743634)
# This sentence is way too long for an Int64
By the way, here's the reverse method :
def encode(str)
str.reverse.each_byte.with_index.map { |b, i| b * 256**i }.inject(:+)
end
You should still check if your RSA code really outputs arbitrary large numbers or just an array of integers.
With shifts
Here's another way to get the result. It's similar to #Nathan's answer, but it works for any integer size :
def decode(int)
a = []
while int>0
a << (int & 0xFF)
int >>= 8
end
a.reverse.pack('C*')
end
According to fruity, it's twice as fast as the gsub solution.
I'm currently rolling with this:
n = 0x616263
nhex = n.to_s(16)
nhexarr = nhex.scan(/.{1,2}/)
nhexarr = nhexarr.map {|e| e.to_i(16)}
out = nhexarr.pack("C*")
But was hoping for a concise/built-in way to do this, so I'll leave this answer unaccepted for now.

Ruby convert 2 decimal string into a number and keep the two decimals

I want to convert "5565.80" to 5565.80 and "5565.00" to 5565.00. The issue with to_f is that it removes the last 0 when the 2 decimals are .00. Is there a single way to do both?
Float
You can convert "5565.80" to a float :
value = "5565.80".to_f
# 5565.8
And then display the value with two decimals :
'%.2f' % value
# "5565.80"
A float has double-precision in Ruby, so your value will actually be :
5565.800000000000181898940354...
As a float, you cannot save exactly 5565.80.
Exact values
Integer
If you want exact values (e.g. for currency), you could use integers for cents :
"5565.80".delete('.').to_i
# 556580
When you need the corresponding float, you could divide it by 100.0.
Decimal
If you're working with a database, you could use decimal(20,2) or something equivalent.
BigDecimal
You could also use BigDecimal :
require 'bigdecimal'
BigDecimal.new("5565.80")
It will save the exact value but will be much slower than int or float.
You can use #round.
The argument to round is how many decimals you want to round to. Some examples:
5565.80.round(2) # => 5565.8 # omits trailing 0's
5565.00.round(2) # => 5565.0 # only keeps the decimal to show that this is a float
5565.79.round(2) # => 5565.79 # rounds to two digits
5565.123.round(3) # => 5565.123 # rounds to three decimal places, so nothing is lost
5565.123.round(2) # => 5565.12 # drops the 3
5565.129.round(2) # => 5565.13 # drops the 9 and rounds up

Keep leading zeroes when converting string to integer

For no particular reason, I am trying to add a #reverse method to the Integer class:
class Integer
def reverse
self.to_s.reverse.to_i
end
end
puts 1337.reverse # => 7331
puts 1000.reverse # => 1
This works fine except for numbers ending in a 0, as shown when 1000.reverse returns 1 rather than 0001. Is there any way to keep leading zeroes when converting a string into an integer?
Short answer: no, you cant.
2.1.5 :001 > 0001
=> 1
0001 doesn't make sense at all as Integer. In the Integer world, 0001 is exactly as 1.
Moreover, the number of leading integer is generally irrelevant, unless you need to pad some integer for displaying, but in this case you are probably converting it into another kind of object (e.g a String).
If you want to keep the integer as Fixnum you will not be able to add leading zeros.
The real question is: why do you want/need leading zeros? You didn't provide such information in the question. There are probably better ways to achieve your result (such as wrapping the value into a decorator object if the goal is to properly format a result for display).
Does rjust work for you?
1000.to_s.reverse.to_i.to_s.rjust(1000.to_s.size,'0') #=> "0001"
self.to_s.to_i does convert the integer to a string and this string "0001" to an integer value. Since leading zeros are not required for regular numbers they are dropped. In other words: Keeping leading zeros does not make sense for calculations, so they are dropped. Just ask yourself how the integer 1 would look like if leading zeros would be preserved, since it represents a 32 bit number. If you need the leading zeros, there is no way around a string.
BUT 10 + "0001".to_i returns 11, so you probably need to override the + method of the String class.

How can I increase the number of decimal digits when converting BigDecimal to String?

I am facing a problem with BigDecimal.
This code:
x = BigDecimal.new('1.0') / 7
puts x.to_s
outputs:
0.142857142857142857E0
I want to increase the number of digits.
In JAVA, I could do:
BigDecimal n = new BigDecimal("1");
BigDecimal d = new BigDecimal("7");
n = n.divide(d,200, RoundingMode.HALF_UP);
System.out.println(n);
The output is:
0.1428571428571428571428571428571428571428571428571428571428... (200 digits)
I looked at BigDecimal documentation, and tried to set the digits when instantiating the number, then tried to set the limit with the BigDecimal.limit, but I couldn't print more than 18 digits.
What am I missing?
I am running ruby 1.9.3p0 (2011-10-30) [i386-mingw32] on Windows 7 64bits
The div method allows you to specify the digits:
x = BigDecimal.new('1.0').div( 7, 50 )
puts x
With a result of:
0.14285714285714285714285714285714285714285714285714E0
Despite the internal representation of a big decimal, the to_s method is responsible for converting it to a string. I see to_s supports a format string:
Converts the value to a string.
The default format looks like 0.xxxxEnn.
The optional parameter s consists of either an integer; or an optional ‘+’ or ‘ ’, followed by an optional number, followed by an optional ‘E’ or ‘F’.
If there is a ‘+’ at the start of s, positive values are returned with a leading ‘+’.
A space at the start of s returns positive values with a leading space.
If s contains a number, a space is inserted after each group of that many fractional digits.
If s ends with an ‘E’, engineering notation (0.xxxxEnn) is used.
If s ends with an ‘F’, conventional floating point notation is used.
Examples:
BigDecimal.new('-123.45678901234567890').to_s('5F') -> '-123.45678 90123 45678 9'
BigDecimal.new('123.45678901234567890').to_s('+8F') -> '+123.45678901 23456789'
BigDecimal.new('123.45678901234567890').to_s(' F') -> ' 123.4567890123456789'

Resources