17
\$\begingroup\$

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!

\$\endgroup\$
4
  • 1
    \$\begingroup\$ As much as I love the history, fibonacci is reserved specifically for the fibonacci sequence, unless you'd like to change the tag wiki... \$\endgroup\$ Feb 6, 2016 at 0:00
  • \$\begingroup\$ 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. \$\endgroup\$
    – user15259
    Feb 6, 2016 at 3:31
  • \$\begingroup\$ I think this challenge should have a bit of info on how roman numerals work and the process involved, just to be self-contained. \$\endgroup\$
    – Liam
    Feb 6, 2016 at 17:50
  • 1
    \$\begingroup\$ That's not the intended use. I have edited the tag wiki excerpt to reflect this. \$\endgroup\$
    – Dennis
    Feb 8, 2016 at 2:29

5 Answers 5

3
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ int main doesn't have to return 0. \$\endgroup\$
    – Adalynn
    Aug 1, 2017 at 13:49
2
+200
\$\begingroup\$

APL (Dyalog Unicode), 90 bytes (using classic ⎕AV)

⎕CY'dfns'
'(·E)?(·N)*·$'⎕R''∊'·',⍨¨¯5(↓,'E',↑)'N'@(0=≢¨)⊢roman'I'⍴⍨¨d↑⍨-6⌈≢d←60⊥⍣¯1⌊⎕×60*5

Try it online!

A full program that takes a number (actually any expression that evaluates to a single number is fine) from stdin and prints the result to stdout. Uses uppercase letters and middle dot as digit separator. The middle dot allows to shorten the regex at the end (since it doesn't need to be escaped, unlike *), and the middle dot is in the classic character set.

How it works

⎕CY'dfns'  ⍝ Load the dfns library to access `roman`

⌊⎕×60*5  ⍝ Multiply input by 60^5 and floor (to do things over integers)
60⊥⍣¯1   ⍝ Convert to base 60 using as many base-60 digits as needed
d←       ⍝ Assign to d
d↑⍨-6⌈≢  ⍝ Pad with zeros to get 6 digits if there were fewer
'I'⍴⍨¨   ⍝ Convert each number to that many copies of 'I'
⊢roman   ⍝ Normalize the Roman numeral

'N'@(0=≢¨)   ⍝ Insert 'N' at zero digits (normalized result is empty)
¯5(↓,'E',↑)  ⍝ Insert 'E' at 5 places from the end
∊'·',⍨¨      ⍝ Append a center dot to each numeral and flatten

'(·E)?(·N)*·$'⎕R''  ⍝ Regex replace to remove trailing E/Ns
\$\endgroup\$
2
  • \$\begingroup\$ I'm curious why this syserrors in Classic. \$\endgroup\$
    – Adám
    Jul 13, 2020 at 15:21
  • \$\begingroup\$ @Adám syserror is raised at enableSALT. Works fine without that line, except that the middle dot is mangled. \$\endgroup\$
    – Bubbler
    Jul 14, 2020 at 0:03
1
\$\begingroup\$

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)
\$\endgroup\$
1
\$\begingroup\$

JavaScript (Node.js), 537 531 430 bytes

-101 bytes (big) thanks to Bubbler!

Usage of some math (logarythms in this case, to calculate starting value for the "for" loop) and a code converting decimal numbers to roman numerals from here (w3resource). Actually, the task took me more time than I expected it to take and gave a lot of additional intuition while working with numeral systems other than the decimal one. Great one :)

Function itself is an anonymous one declared with arrow function declaration.

n=>{if(n==0||n==1)return"NI"[n];k="",j=-`${n-(n|0)}.`.split`.`[1].length||0;j<-5?j=-5:"";with(Math){l=m=ceil(log(n)/log(60))}for(;l>j;l--){x=n%60**l/60**(l-1)|0;x==0&&l==m?(k+="I*"):"";l==0&&j<0?(k+="E*"):"";d=`${+x}`.split``,o=" X XX XXX XL L LX LXX LXXX XC  I II III IV V VI VII VIII IX".split` `,h="",i=2;while(i--)h=(o[+d.pop()+(i*10)]||"")+h;k+=x==0?"N*":`${Array(+d.join("")+1).join("M")+h}*`;}return k.substr(0,k.length-1)}

Try it online!

\$\endgroup\$
2
  • 1
    \$\begingroup\$ You can use header and footer like this to make the byte count clear. Also some quick tips: if(n==0){return"N"}else if(n==1){return"I"} -> if(n==0||n==1)return"NI"[n], split(".") -> split`.`, ((n%(60**l))/60**(l-1))|0 -> n%60**l/60**(l-1)|0. \$\endgroup\$
    – Bubbler
    Jul 16, 2020 at 1:11
  • 1
    \$\begingroup\$ o=["","a","b","..."] -> o=" a b ...".split` `. Also you can remove any Roman numeral fragments that are higher than 60. \$\endgroup\$
    – Bubbler
    Jul 16, 2020 at 1:14
0
\$\begingroup\$

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.

\$\endgroup\$
2
  • \$\begingroup\$ ["l","xl","x","ix","v","iv","i"] can be words"l xl x ix v iv i" \$\endgroup\$
    – H.PWiz
    Sep 11, 2019 at 18:17
  • \$\begingroup\$ @H.PWiz thanks, incorporated! \$\endgroup\$
    – CR Drost
    Sep 11, 2019 at 19:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy