Fibonacci’s sexagesimals

15

Leonardo da Pisano a.k.a Fibonacci was instrumental in bringing the Hindu-Arabic numeral system into Europe. Before that, mathematicians there worked in base sixty with Roman numerals.

For example, the square root of two might be approximated as: one and twenty-four parts of sixty and fifty-one parts of three-thousand-six-hundred, and written as: i xxiv li, with the scaling determined by context. At the time, “nothingness” was known (i.e. zero), but had no standard representation in this number system.

Had Fibonacci ignored these new-fangled decimal digits he encountered during his travels, he surely would have addressed the deficiencies in the current system. This improved system we'll call Fibonacci’s sexagesimals.

Your task is to write a program, function or code snippet which takes a floating point number in ASCII or binary format and outputs in base sixty Roman numerals. The input can be file, console, command line or function argument and output can be file or console, whichever is easiest.

The output can be upper or lower case, and must include these improvements:

  • use n or N to indicate null meaning a place has no value, i.e. “zero” (a problem with the system)
  • use e or E to indicate et corresponding to the sexagesimal point (another problem with the system)
  • use a middle dot · or an asterisk * to separate groups of Roman numerals (yet another problem with the system)

Assume the input will be floating point with mantissa not greater than lix·lix·lix·lix·lix. Fractions less than n·e·n·n·n·n·i can be ignored. So, provided the input has these restrictions, at most ten groups of Roman numerals with one e can be outputted.

Numbers less than i must have a leading n·e to ensure the context is clear.

Some examples: inputoutput

  • 0n
  • 1i
  • 60i·n
  • 0.1n·e·vi
  • 3600i·n·n
  • 10.5x·e·xxx
  • 16777215i·xvii·xl·xx·xv
  • 3.1415926536iii·e·viii·xxix·xliv·n·xlvii

The output must avoid unnecessary leading in the mantissa part, isolated e, or trailing ·n in the fractional part of the output. So for example, n·n·n·n·i, i·e, and i·e·n·n·n·n·n are incorrect outputs for an input of 1.

Differences of plus or minus n·e·n·n·n·n·i in the output are within tolerances and acceptable.

The input is any legal floating point in the language of your choice, so can include positive or negative exponents as long as the input doesn't fall outside the range specified above.

And finally, Roman numeral built-ins are allowed!

user15259

Posted 2016-02-05T22:36:15.233

Reputation:

1

As much as I love the history, [tag:fibonacci] is reserved specifically for the fibonacci sequence, unless you'd like to change the tag wiki...

– Addison Crump – 2016-02-06T00:00:52.847

Tag is for "Leonardo Fibonacci is mostly known for the fibonacci sequence (0, 1, 1, 2, 3, 5, 8, 13, ...).", so would say it's meant for the person. – None – 2016-02-06T03:31:03.723

I think this challenge should have a bit of info on how roman numerals work and the process involved, just to be self-contained. – Liam – 2016-02-06T17:50:02.713

1That's not the intended use. I have edited the tag wiki excerpt to reflect this. – Dennis – 2016-02-08T02:29:23.107

Answers

1

Python 3, 323 319 320 bytes

This answer implements Fibonacci's sexagesimals with the delimiter * and without regard for Kolmogorov complexity in the lists of Roman numerals (for now, at least). Attempts were made to join the while and for loop under which the Roman numerals are generated under one loop, but those attempts have not yet met with success. Any golfing tips and suggestions are welcome and appreciated.

Edit: Bug fixing and golfing.

Edit: More bug fixing.

def f(x):
 v=divmod;f=x%1;n=int(x);d=",I,II,III,IV,V,VI,VII,VIII,IX".split(",");t=",X,XX,XXX,XL,L".split(",");z=["N"];a=f>0;s=z*0**n+["E"]*a
 while n:n,m=v(n,60);j,k=v(m,10);s=[z,[t[j]+d[k]]][m>0]+s
 for i in range(5*a):m,f=v(f*60,1);j,k=v(int(m),10);s+=[z,[t[j]+d[k]]][m>0]
 while s[-1:]==z*a:s.pop()
 return"*".join(s)

Ungolfed:

def f(x):
    integ = int(x)
    frac = x % 1
    units=",I,II,III,IV,V,VI,VII,VIII,IX".split(",")
    tens=",X,XX,XXX,XL,L".split(",")
    zero = ["N"]
    output = []
    a = frac != 0
    if integ == 0:
        output += z
    if a:
        output += ["E"]
    while integ > 0:
        integ, digit = divmod(integ, 60)
        j, k = divmod(int(digit), 10)
        if digit:
            output += [tens[j], units[k]]
        else:
            output += zero
    for i in range(5*a):
        digit, frac = divmod(frac*60, 1)
        j, k = divmod(int(digit), 10)
        if digit:
            output += [tens[j], units[k]]
        else:
            output += zero
    while output[-1:] == zero * a:
        output.pop()
    return "*".join(output)

Sherlock9

Posted 2016-02-05T22:36:15.233

Reputation: 11 664

3

C – 584 bytes

Non-competing (obviously), but to serve as inspiration:

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
char*f(int z){static char r[8];char*l[]={"","I","II","III","IV","V","VI","VII","VIII","IX"},*h[]={"","X","XX","XXX","XL","L"};if(!z)return"N";sprintf(r,"%s%s",h[z/10],l[z%10]);return r;}int main(int c,char**v){char*s="";int i,j,z[10],k=60;long x;double d,y;y=modf(atof(v[1]),&d);x=d;for(i=4;i>=0;i--){z[i]=x%k;x/=k;}for(i=5;i<=9;i++){z[i]=(y*=k);y-=z[i];}for(i=0;!z[i]&&i<4;i++);for(;i<5;i++){printf("%s%s",s,f(z[i]));s="*";}for(j=9;!z[j]&&j>=i;j--);if(i<=j)printf("*E");for(;i<=j;i++)printf("*%s",f(z[i]));printf("\n");return 0;}

Save as fs.c, build with gcc -o fs fs.c -lm, and run as ./fs <arg>.

Test cases:

$ ./fs 0
N
$ ./fs 1
I
$ ./fs 60
I*N
$ ./fs 0.1
N*E*VI
$ ./fs 3600
I*N*N
$ ./fs 10.5
X*E*XXX
$ ./fs 16777215
I*XVII*XL*XX*XV
$ ./fs 3.1415926536
III*E*VIII*XXIX*XLIV*N*XLVII

Largest mantissa and fraction:

$ ./fs 777599999
LIX*LIX*LIX*LIX*LIX
$ ./fs 0.999999998713992
N*E*LIX*LIX*LIX*LIX*LIX

I'm using double as the working type, so the largest mantissa and fraction combined exceeds the native accuracy of that type. If I used long double instead it could handle it.

user15259

Posted 2016-02-05T22:36:15.233

Reputation:

int main doesn't have to return 0. – Zacharý – 2017-08-01T13:49:37.527

0

Haskell (333 322 315 bytes)

I'm not clear whether the last sexagesimal digit is supposed to be rounded when I do or whether truncation is allowed; this truncates, I think the Python3 one might too?

d n f 0=n;d n f x=f x
x!n=60*(x-fromInteger n)
f 0=[];f x=(\n->n:f(x!n))$floor x
l 0=[];l x=(\(d,m)->l d++[m])$divMod x 60
v=[50,40,10,9,5,4,1]
n&i|n==0=""|n>=v!!i=words"l xl x ix v iv i"!!i++(n-v!!i)&i|True=n&(i+1)
q=foldl1(\a x->a++'.':x).map(d"n"(&0))
p x=(\n->d"n"(q.l)n++d""((".e."++).q.take 5.f)(x!n))$floor x

(-9 bytes, thanks H.PWiz! -2 bytes by eliminating where for (\->)$, -5 more by inventing this d function and golfing a++"."++x to a++'.':x.)

Ungolfed:


-- this function gets called `d` for default
onZero :: (Eq n, Num n) => z -> (n -> z) -> n -> z
onZero def f x 
 | x == 0    = def
 | otherwise = f x 

-- this function gets called `f`
fracPart :: RealFrac a => a -> [Integer]
fracPart x
  | x == 0    = [] 
  | otherwise = n : fracPart (60 * (x - fromInteger n))
    where n = floor x

-- this function gets called `l`
leadPart :: Integral n => n -> [Integer]
leadPart x
  | x == 0    = [] 
  | otherwise = leadPart div ++ [ mod ]
    where (div, mod) = x `divMod` 60

-- these get called `v`
romanValues :: [Integer]
romanValues = [50, 40, 10, 9, 5, 4, 1]

-- these get inlined with `words`, and correspond to the values above
romanLetters :: [String]
romanLetters = ["l", "xl", "x", "ix", "v", "iv", "i"]

-- this becomes (&)
romanNumeralLoop :: Integer -> Int -> String
romanNumeralLoop n i
 | n == 0                  = "" 
 | n >= (romanValues !! i) = (romanLetters !! i) ++ romanNumeralLoop (n - (romanValues !! i)) i
 | otherwise               = romanNumeralLoop n (i + 1)

-- this becomes `q`
concatRomanWithDots :: [Integer] -> String
concatRomanWithDots numbers = concatWithDots (map toRoman numbers)
  where 
    toRoman = onZero "n" (\x -> romanNumeralLoop x 0)
    concatWithDots = foldl1 concatDot
    concatDot acc item = acc ++ "." ++ item

-- this becomes `p`
solve x = onZero "n" elseRomanizeLeadPart n ++ onZero "" elseRomanizeFracPart f
  where
    n = floor x
    f = 60 * (x - fromInteger n) 
    elseRomanizeLeadPart l = concatRomanWithDots (leadPart l)
    elseRomanizeFracPart f = ".e." ++ concatRomanWithDots (take 5 (fracPart f))

The method of converting integers to roman numerals was stolen shamelessly from Thomas Ahle on StackOverflow and just golfed a little.

CR Drost

Posted 2016-02-05T22:36:15.233

Reputation: 949

["l","xl","x","ix","v","iv","i"] can be words"l xl x ix v iv i" – H.PWiz – 2019-09-11T18:17:13.390

@H.PWiz thanks, incorporated! – CR Drost – 2019-09-11T19:16:31.887