The Crystal Programming Language Forum

Compute/displaying arbitrary decimal digits

I’m trying to compute/display floating point values to at least 100 digits.
When I run this I only get the following output.

require "big"

def ramanujan_num(x, digits=32)
    Math.exp(Math::PI * Math.sqrt(x)).significant(digits)
end

digits = 100
#puts "Ramanujan's constant to #{digits} decimals:\n #{ramanujan_num(163, digits)}"
printf("Ramanujan's constant to #{digits} decimals:\n%s\n\n", ramanujan_num(163, digits))
# Produced output:
Ramanujan's constant to 100 decimals:
2.6253741264076826e+17

# Desired output:
Ramanujan's constant to 100 decimals:
262537412640768743.9999999999992500725971981856888793538563373369908627075374103782106479101186073129511813461860645042

How do I compute/display the desired output?

Two things:

  • you can use %.100f as a format specifier to format float with 100 decimal places
  • I don’t think floats have 100 decimal places
  • you require "big" but never use it… :thinking:

Edit: classic off-by-one error :man_shrugging:

2 Likes

I can display to 100 digits now with the %.100f but the computed value is still wrong.

printf("Ramanujan's constant to #{digits} decimals:\n%.100f\n\n", ramanujan_num(163, digits))

262537412640766432.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

So now I need to do the computation to display to the desired precision.

Do you know how to do this in another language? That way I could try to see how to do it in Crystal. Ruby gives me the same answer.

I’m trying to do this Rosetta Code example, mimicking the Sidef example.

# Sidef example and output

func ramanujan_const(x, decimals=32) {
    local Num!PREC = *"#{4*round((Num.pi*√x)/log(10) + decimals + 1)}"
    exp(Num.pi * √x) -> round(-decimals).to_s
}
 
var decimals = 100
printf("Ramanujan's constant to #{decimals} decimals:\n%s\n\n",
     ramanujan_const(163, decimals))
 
say "Heegner numbers yielding 'almost' integers:"
[19, 96, 43, 960, 67, 5280, 163, 640320].each_slice(2, {|h,x|
    var c = ramanujan_const(h, 32)
    var n = (x**3 + 744)
    printf("%3s: %51s ≈ %18s (diff: %s)\n", h, c, n, n-Num(c))
})

# Output
Ramanujan's constant to 100 decimals:
262537412640768743.9999999999992500725971981856888793538563373369908627075374103782106479101186073129511813461860645042

Heegner numbers yielding 'almost' integers:
 19:             885479.77768015431949753789348171962682 ≈             885480 (diff: 0.22231984568050246210651828037318)
 43:          884736743.99977746603490666193746207858538 ≈          884736744 (diff: 0.00022253396509333806253792141462)
 67:       147197952743.99999866245422450682926131257863 ≈       147197952744 (diff: 0.00000133754577549317073868742137)
163: 262537412640768743.99999999999925007259719818568888 ≈ 262537412640768744 (diff: 0.00000000000074992740280181431112)

Crystal code and output.

def ramanujan_num(x, digits=32)
  #(Math::E ** (Math::PI * Math.sqrt(x)) )
  (Math.exp(Math::PI * Math.sqrt(x))).round(digits)
end

digits = 100
#puts "Ramanujan's constant to #{digits} decimals:\n #{ramanujan_num(163, digits)}"
#printf("Ramanujan's constant to #{digits} decimals:\n%s\n\n", ramanujan_num(163, digits))
printf("Ramanujan's constant to #{digits} decimals:\n%s\n\n", ramanujan_num(163, digits))
 
puts "Heegner numbers yielding 'almost' integers:"
[19, 96, 43, 960, 67, 5280, 163, 640320].each_slice(2) { |(h, x)|
  c = ramanujan_num(h, 32)
  n = (x**3 + 744)
  printf("%3s: %51s ≈ %18s (diff: %s)\n", h, c, n, n-c)
}

Ramanujan's constant to 100 decimals:
2.6253741264076826e+17

Heegner numbers yielding 'almost' integers:
 19:                                   885479.7776801554 ≈             885480 (diff: 0.2223198446445167)
 43:                                   884736743.9997752 ≈          884736744 (diff: 0.00022482872009277344)
Unhandled exception: Arithmetic overflow (OverflowError)
  from /home/jzakiya/crystal/share/crystal/src/int.cr:280:14 in '**'
  from ramanujan.cr:16:8 in '__crystal_main'
  from /home/jzakiya/crystal/share/crystal/src/crystal/main.cr:105:5 in 'main_user_code'
  from /home/jzakiya/crystal/share/crystal/src/crystal/main.cr:91:7 in 'main'
  from /home/jzakiya/crystal/share/crystal/src/crystal/main.cr:114:3 in 'main'
  from __libc_start_main
  from ../sysdeps/x86_64/start.S:122:0 in '_start'
  from ???

}

I was looking at the docs to see if there’s a way to increase the computational precision.

https://trizen.gitbooks.io/sidef-lang/syntax_and_semantics/literals/float.html

In Sidef, all literal numbers are specially parsed and converted implicitly into a rational number, which are internally represented by Math::GMPz and Math::GMPq.

You will need to use those types in Crystal. I don’t know if we have those, though.

That explains why Sidef has more precision: they simply don’t use the machine Float64 representation, they use rationals.

Thanks for the research @asterite, I thought it was something like that because they explicitly do a to_s on exp(Num.pi * √x) -> round(-decimals).to_s.

So I guess Crystal can’t (currently) fulfill the requirements of this task because it can’t compute/display the necessary digits of precision.

Math.sqrt(x) returns a Float32 I think, maybe it’s not precise enough? https://crystal-lang.org/api/0.33.0/Math.html#sqrt(value:Float32)-instance-method

The required precision can only be provided by Big* numbers. However, it seems some of the necessary math operations are not yet implemented.

Right. We have BigRational in the standard library. We are just missing some functionality (like exp and PI)

2 Likes