challenge exp(pi * sqrt(n))が整数に近くなるnを探す

1以上200未満の整数nのうち、 exp(pi * sqrt(n))がほとんど整数であるようなnを求めるコードを書いてください。 なお、expは底がeである指数関数 - Wikipedia、 piは円周率、sqrtは平方根です。また「ほとんど整数である」とは 整数からプラスマイナス0.0001の範囲にあることとします。

Pythonで34行のスクリプトを書いて得られた出力の例が下のようになります。

37 199148647.999978
58 24591257752.000000
67 147197952743.999999
163 262537412640768744.000000 
この問題は光成さんに教えて頂いた e^{π*sqrt{163}}≒26253741640768744 が元になっています。ご協力ありがとうございました。

Posted feedbacks - Nested

Flatten Hidden
1から199まで(Range)の整数から、選ぶ(Select)、
基準は、
「x=Exp[Pi Sqrt[n]]として(With)、Abs[x-Round@x]が10^-4以下のもの」

「10^-4を0.0001と書いてはいけない」という問題かな
1
2
3
4
Select[Range@199,
  Function[{n}, 
    With[{x = Exp[Pi Sqrt@n]},
      Abs[x - Round@x] <= 10^-4]]]
>「10^-4を0.0001と書いてはいけない」

そうなんですか?
もともとかなり古いライブラリだけど。。。使い勝手は悪くないと思う。

CReal モジュールを使う

このモジュールではデフォルトで小数点以下40桁まで表示。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module Main (main) where

import CReal

default (CReal)

f :: CReal -> (Int, CReal)
f n = (ceiling n, exp $ pi * sqrt n)

g :: CReal -> Bool
g n = 0.0001 > if 0.5 > n' - n then n' - n else 1 - (n' - n)
  where n' = fromInteger $ ceiling n

main :: IO ()
main = mapM_  (putStrLn . show) $ filter (g . snd) $ map f $ [1..200]

{-
*Main> :main
(37,199148647.9999780465518567665009238753359004336659)
(58,24591257751.9999998222132414695761923552658122276102)
(67,147197952743.9999986624542245068292613125786285081833)
(163,262537412640768743.9999999999992500725971981856888793538563)
-}
あっ。200未満か。まぁいいか。
f の定義がちょっとアレげなので、リファクタリング

f :: Integer -> (Integer, CReal)
f n = (n, exp $ pi * sqrt (fromInteger n))
20000以下で調べてみたら,

(37,199148647.9999780465518567665009238753359004336659)
(58,24591257751.9999998222132414695761923552658122276102)
(67,147197952743.9999986624542245068292613125786285081833)
(163,262537412640768743.9999999999992500725971981856888793538563)
(232,604729957825300084759.9999921715268564302785946808125512858845)
(652,68925893036109279891085639286943768.0000000001637386442092346075723290625709)
(719,3842614373539548891490294277805829192.9999872495660121875632701836570684449713)
(1169,44555719382988281777368496770130045948309444044.9999608028638684615024311524958053653676)
(1467,18095625621654510801615355531263454706630064771074975.9999999901236936712413276522472419790897)
(2608,4750778730825177725463920948909726618214491718039471366318747406368792.0000003084643221299811801879962000157848)
(4075,1247257156019637304856107520018074552566824585862995272173368815794085495792299621093743.9999936541874689715769690801805608661462)
(5773,46309587632860353087565367053742331250287153098248715578209888177688338779879045292937243508078581367989.9999155688538141662485158867634827239495)
(5868,327451666639079200503292535866541250265248788274691526825971156747731856100971255480468836963064283775072.0000971752541625920841201776565931010652)
(14370,35853082260707987565058966569844138230073788113687426211111838655289387060751425253235617998546754095483027646820100689677619375065141669969772221012766669726610356.0000809961676073067730760452585504610564)
(19183,932865712335864748985892137487224407024879778525651187858506512365813448395651706659915858370562540302669863675912417090372354324023009941419688829317421179304232837551154837099435561184585.9999366484683843100572967742009198686654)

という結果でした.163が題意の意味で一番整数に近いようです.
所要時間は,time ではかって,

2128.54s user 8.40s system 96% cpu 36:49.42 total

でした.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
module Main (main) where

import Data.List
import System.Environment
import System.IO
import CReal

default (CReal)

f :: CReal -> (Int, CReal)
f n = (ceiling n, exp $ pi * sqrt n)

g :: CReal -> Bool
g n = 0.0001 > h n

h :: CReal -> CReal
h n = if 0.5 > n' - n then n' - n else 1 - (n' - n)
  where n' = fromInteger $ ceiling n

main :: IO ()
main = do { args <- getArgs
          ; case args of
              []  -> loop 100 0
              n:_ -> loop (read n) 0
          }
loop n m 
 | n == m = return ()
 | True   = mapM_ (k . f) [(m*100)+1 .. (m+1)*100] >> loop n (m+1)

k :: (Int,CReal) -> IO ()
k n@(i,r) = if g r then hPutStrLn stdout (show n) >> hFlush stdout
	    else putStr (show i ++ "\r") >> hFlush stdout
cpuスペックを書きわすれた。
Intel(R) Pentium(R) M processor 2.13GHz
です
ええと、long doubleは18桁くらいの精度ですよね。
67の計算には足りて、163の計算には足りないのでしょうね、きっと。

Apfloatを使いました。

それだけじゃ何も芸がない感じだったので、implicitでApfloatをあまり意識せずに書ける様にしました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import org.apfloat._
import scala.runtime._

object findAlmostInt {
  implicit def toApfloat[A <: AnyVal](i:A):Apfloat = {
    new Apfloat(i.toString, 32)
  }
  implicit def toDouble(i:Apfloat):RichDouble = i.doubleValue.toDouble
  val PI = ApfloatMath.pi(32)

  def apply(i:int, j:int) = {
    (i to j-1).filter{n =>
      val x = ApfloatMath.exp(PI.multiply(ApfloatMath.sqrt(n)))
      val y = x.ceil.subtract(x)
      (if(y<0.5){y}else{1.subtract(y)}).abs < 0.0001
    }.toList
  }
}

println(findAlmostInt(1,200))
 便乗。このライブラリは重宝しそう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function doukaku60(){ // 精度的に 58 が限界。
  with(Math) for(var r = [], f, n = 1; n < 200; n++)
    if(abs((f = exp(PI * sqrt(n))) - round(f)) < 1e-4) r.push([n, f]);
  return r;
}
// ↓ Apfloat を用いて書き換え //

function doukaku60_(){
  var F = Packages.org.apfloat.Apfloat;
  var M = Packages.org.apfloat.ApfloatMath;
  for(var r = [], p = 25, h = new F(0.5, p), f, n = 1; n < 200; n++){
    f = M.exp(M.pi(p).multiply(M.sqrt(new F(n, p))));
    if(M.abs(f.subtract(f.add(h).floor())).floatValue() < 1e-4) r.push([n, f.toString(true)]);
  }
  return r;
}
print(doukaku60_().join('\n'))
Decimalを使って多倍長計算。sqrtは組み込みのものを使い、expは安直にマクローリン展開で収束するまで回しています。psycoを使ってますが、それでもかなり遅いです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from decimal import *
import itertools

def decimal_exp(x):
    curr, bunsi, bunbo = Decimal("1.0"), Decimal("1.0"), 1
    for k in itertools.count(1):
        bunsi *= x
        bunbo *= k
        next = curr + bunsi / bunbo
        if curr == next:
            return curr
        curr = next

def main():
    pi = Decimal("3.141592653589793238462643383279")
    for n in xrange(1, 200):
        x = decimal_exp(pi * Decimal(n).sqrt()) # result
        i = x.quantize(Decimal("1.0"), rounding=ROUND_HALF_UP) # nearest integer
        if (x - i).quantize(Decimal("0.00001"), rounding=ROUND_DOWN) == 0:
            print n, x

if __name__ == '__main__':
    try:
        import psyco
    except ImportError:
        pass
    else:
        psyco.full()
    main()
あああ、すみません。組み終わった後に ".00..." という表記を自分好みの "0.00.." という表記に書き換えたときにエンバグしちゃったみたいで、37 が表示されなくなってました。プラス評価もらったのに申し訳ない。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
--- a.orig	Sat Sep 15 15:12:33 2007
+++ a.py	Sat Sep 15 15:12:50 2007
@@ -16,7 +16,7 @@
     for n in xrange(1, 200):
         x = decimal_exp(pi * Decimal(n).sqrt()) # result
         i = x.quantize(Decimal("1.0"), rounding=ROUND_HALF_UP) # nearest integer
-        if (x - i).quantize(Decimal("0.00001"), rounding=ROUND_DOWN) == 0:
+        if (x - i).quantize(Decimal("0.0001"), rounding=ROUND_DOWN) == 0:
             print n, x
 
 if __name__ == '__main__':
人のふんどしで相撲を取るシリーズ。

上記のコードを任意精度演算ライブラリとしてgmpを使いCに移植しました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gmp.h>

mpf_t curr, next, bunsi, bunbo;

void mpf_exp(mpf_t x)
{
    unsigned int k;

    mpf_set_ui(curr, 1.0);
    mpf_set_ui(bunsi, 1.0);
    mpf_set_ui(bunbo, 1.0);

    for (k = 1; ;k++) {
        mpf_mul(bunsi, bunsi, x);
        mpf_mul_ui(bunbo, bunbo, k);
        mpf_div(next, bunsi, bunbo);
        mpf_add(next, next, curr);
        if (mpf_cmp(curr, next) == 0) {
            mpf_set(x, curr);
            return;
        }
        mpf_set(curr, next);
    }
}

int main(int argc, char *argv[])
{
    char buf[128], tmp[8];
    double d;
    int i;
    mpf_t x, pi;

    mpf_set_default_prec(128);

    mpf_init(curr);
    mpf_init(next);
    mpf_init(bunsi);
    mpf_init(bunbo);
    mpf_init(x);
    mpf_init(pi);

    mpf_set_str(pi, "3.1415926535897932384626433832795029", 10);

    for (i = 1; i < 200; i++) {
        mpf_set_ui(x, i);
        mpf_sqrt(x, x);
        mpf_mul(x, x, pi);
        mpf_exp(x);

        gmp_sprintf(buf, "%.*Ff", 6, x);
        d = atof(strncpy(tmp, strchr(buf, '.'), 7));
        if ((d < 0.0001) || ((1 - d) < 0.0001))
            printf("%d %s\n", i, buf);
    }

    mpf_clear(curr);
    mpf_clear(next);
    mpf_clear(bunsi);
    mpf_clear(bunbo);
    mpf_clear(x);
    mpf_clear(pi);

    return 0;
}
上記のコードをctypesを使ってpythonで実装してみました。

Cのヘッダファイル内で#ifdefやら#defineが多用されているので、もしかしたら
環境依存になっているかもしれません。

呼び出し毎にインスタンスを生成するexp関数と、静的なインスタンスを持つ
exp2関数の速度を比較すると、exp2の方が20%ほどはやいようです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
from ctypes import *

gmp = cdll.LoadLibrary('libgmp.so')

for s in ['set_default_prec', 'init', 'clear', 'set', 'set_ui', 'set_str', 'mul'
, 'mul_ui', 'div', 'add', 'cmp', 'sqrt', 'printf', 'snprintf']:
  try:
    gmp.__setattr__(s, gmp.__getattr__('__gmpf_'+s))
  except:
    gmp.__setattr__(s, gmp.__getattr__('__gmp_'+s))

def mpf():
  class mpf_t(Structure):
    _fields_ = [("_mp_prec", c_int), ("_mp_size", c_int), ("_mp_exp", c_int), ("
_mp_d", c_void_p)]

    def __init__(self):
      Structure.__init__(self)
      gmp.init(byref(self))

    def __del__(self):
      try:
        gmp.clear(byref(self))
      except:
        pass

  return pointer(mpf_t())

def exp(rop, op):
  curr, bunsi, bunbo, next = mpf(), mpf(), mpf(), mpf()

  gmp.set_ui(curr, c_ulong(1))
  gmp.set_ui(bunsi, c_ulong(1))
  gmp.set_ui(bunbo, c_ulong(1))

  k = 1
  while True:
    gmp.mul(bunsi, bunsi, op)
    gmp.mul_ui(bunbo, bunbo, c_ulong(k))
    gmp.div(next, bunsi, bunbo)
    gmp.add(next, next, curr)
    if gmp.cmp(curr, next) == 0:
      gmp.set(rop, curr)
      return
    gmp.set(curr, next)
    k += 1

def exp2(rop, op):
  try:
    curr, bunsi, bunbo, next = exp2.curr, exp2.bunsi, exp2.bunbo, exp2.next
  except:
    exp2.curr, exp2.bunsi, exp2.bunbo, exp2.next = mpf(), mpf(), mpf(), mpf()
    curr, bunsi, bunbo, next = exp2.curr, exp2.bunsi, exp2.bunbo, exp2.next

  gmp.set_ui(curr, c_ulong(1))
  gmp.set_ui(bunsi, c_ulong(1))
  gmp.set_ui(bunbo, c_ulong(1))

  k = 1
  while True:
    gmp.mul(bunsi, bunsi, op)
    gmp.mul_ui(bunbo, bunbo, c_ulong(k))
    gmp.div(next, bunsi, bunbo)
    gmp.add(next, next, curr)
    if gmp.cmp(curr, next) == 0:
      gmp.set(rop, curr)
      return
    gmp.set(curr, next)
    k += 1

def frac_f(x):
  buf = c_buffer(256)
  gmp.snprintf(buf, len(buf)-1, "%.Ff", x)
  return float(buf.value[buf.value.index('.'):])

def main():
  from time import time
  t = time()

  gmp.set_default_prec(128)

  x, pi = mpf(), mpf()

  gmp.set_str(pi, "3.141592653589793238462643383279502884197", 10)

  for i in range(1, 200):
    gmp.set_ui(x, c_ulong(i))
    gmp.sqrt(x, x)
    gmp.mul(x, x, pi)
#    exp(x, x)
    exp2(x, x)
    f = frac_f(x)
    if f < 0.0001 or (1-f) < 0.0001:
      gmp.printf("%d %.6Ff\n", i, x)

  print time() - t

if __name__ == '__main__':
  main()
#3130の匿名さんのアイデアを拝借して組みなおしてみたところ、2.6倍早くなりました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from decimal import *
import itertools

def divmod(x, y): # workaround for python2.5 decimal mod bug.
    div = x // y
    mod = x - div * y
    return div, mod

def decimal_exp(x):
    div, mod = divmod(x, Decimal("2.3025850929940456840179914546844"))
    curr, bunsi, bunbo = Decimal("1.0"), Decimal("1.0"), 1
    for k in itertools.count(1):
        bunsi *= mod
        bunbo *= k
        next = curr + bunsi / bunbo
        if curr == next:
            return (10 ** div) * curr
        curr = next

def main():
    pi = Decimal("3.141592653589793238462643383279")
    for n in xrange(1, 200):
        x = decimal_exp(pi * Decimal(n).sqrt()) # result
        i = x.quantize(Decimal("1.0"), rounding=ROUND_HALF_UP) # nearest integer
        if (x - i).quantize(Decimal("0.0001"), rounding=ROUND_DOWN) == 0:
            print n, x

if __name__ == '__main__':
    try:
        import psyco
    except ImportError:
        pass
    else:
        psyco.full()
    main()

このバグはPython2.5.2で修正されました。というわけでパッチです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
--- a.py    Thu Feb 28 02:32:31 2008
+++ a.py    Thu Feb 28 02:32:46 2008
@@ -1,10 +1,11 @@
 from decimal import *
-import itertools
+import itertools, sys
 
-def divmod(x, y): # workaround for python2.5 decimal mod bug.
-    div = x // y
-    mod = x - div * y
-    return div, mod
+if sys.version_info[:3] <= (2, 5, 1):
+    def divmod(x, y): # workaround for python2.5 decimal mod bug.
+        div = x // y
+        mod = x - div * y
+        return div, mod
 
 def decimal_exp(x):
     div, mod = divmod(x, Decimal("2.3025850929940456840179914546844"))
素直にbigdecimalを使って。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
require "bigdecimal"
require "bigdecimal/math.rb"

include BigMath

def exp_pi_sqrt(x)
  tmp = exp(PI(30) * sqrt(BigDecimal(x.to_s), 30), 30)
  err = BigDecimal 0.0001.to_s
  if (tmp > (tmp.floor - err)) and (tmp < (tmp.floor + err))
    print "#{x} #{tmp.to_s("F")}\n"
  elsif (tmp > (tmp.ceil - err)) and (tmp < (tmp.ceil + err))
    print "#{x} #{tmp.to_s("F")}\n"
  end
end

=begin
for i in 1 .. 200
  exp_pi_sqrt i
end
=end
強引に解いてみました。BigDecimalを使用していますがライブラリがないので自前で計算しています。expの計算方法が非効率的なので非常に時間がかかります(苦笑)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.math.BigDecimal;
import java.math.MathContext;

public class Sample {
    private static final double EPS = 0.000001;
    private static final double EPS2 = 1e-25;
    private static final BigDecimal TWO = new BigDecimal(2);
    private static final BigDecimal PI = new BigDecimal
        ("3.1415926535897932384626433");
    public static BigDecimal exp(BigDecimal x) {
        int i = 1;
        BigDecimal b = new BigDecimal(i++);
        BigDecimal c = x;
        BigDecimal a = new BigDecimal(1.0);
        BigDecimal d;
        while ((d = c.divide(b, MathContext.DECIMAL128)).doubleValue() > EPS) {
            a = a.add(d);
            b = b.multiply(new BigDecimal(i++));
            c = c.multiply(x);
        }
        return a;
    }

    public static BigDecimal sqrt(BigDecimal a) {
        BigDecimal x = new BigDecimal(10);
        BigDecimal delta;
        do {
            delta = (x.multiply(x).subtract(a)).divide(TWO.multiply(x), 
                                                       MathContext.DECIMAL128);
            x = x.subtract(delta);
        } while (delta.abs().doubleValue() > EPS2);
        return x;
    }

    public static void main(String[] args) {
        for (int i = 1; i < 200; i++) {
            BigDecimal er = exp(PI.multiply(sqrt(new BigDecimal(i))));
            BigDecimal rer = new BigDecimal(er.toBigInteger());
            double a = er.subtract(rer).doubleValue();
            if (a < 0.0001 || a > 0.9999) {
                System.out.printf("%d: %f%n", i, er);
            }
        }
    }
}
expの計算の効率を上げる事で、なんとか実用(?)的な性能にする事ができました。

x = n log 10 + k の時
    exp(x) = 10^n * exp(k)
の性質を使っています。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.math.BigDecimal;
import java.math.MathContext;

public class Sample {
    private static final double EPS = 0.000001;
    private static final double EPS2 = 1e-25;
    private static final BigDecimal TWO = new BigDecimal(2);
    private static final BigDecimal PI = new BigDecimal
        ("3.141592653589793238462643383279");
    private static final BigDecimal LN10 = new BigDecimal
        ("2.3025850929940456840179914546844");
    public static BigDecimal exp(BigDecimal x) {
        BigDecimal[] dr = x.divideAndRemainder(LN10);
        int n = dr[0].intValue();
        x = dr[1];
        int i = 1;
        BigDecimal b = new BigDecimal(i++);
        BigDecimal c = x.scaleByPowerOfTen(n);;
        BigDecimal a = new BigDecimal(1.0).scaleByPowerOfTen(n);
        BigDecimal d;
        while ((d = c.divide(b, MathContext.DECIMAL128)).doubleValue() > EPS) {
            a = a.add(d);
            b = b.multiply(new BigDecimal(i++));
            c = c.multiply(x);
        }
        return a;
    }

    public static BigDecimal sqrt(BigDecimal a) {
        BigDecimal x = new BigDecimal(Math.sqrt(a.doubleValue()));
        BigDecimal delta;
        do {
            delta = (x.multiply(x).subtract(a)).divide(TWO.multiply(x), 
                                                       MathContext.DECIMAL128);
            x = x.subtract(delta);
        } while (delta.abs().doubleValue() > EPS2);
        return x;
    }

    public static void main(String[] args) {
        for (int i = 1; i < 200; i++) {
            BigDecimal er = exp(PI.multiply(sqrt(new BigDecimal(i))));
            BigDecimal rer = new BigDecimal(er.toBigInteger());
            double a = er.subtract(rer).doubleValue();
            if (a < 0.0001 || a > 0.9999) {
                System.out.printf("%d: %f%n", i, er);
            }
        }
    }
}