Blog-Archiv

Samstag, 4. Oktober 2025

Two-Factor Authentication on LINUX with KeePassXC

I had to set up a "2FA" (Two Factor Authentication) or "TOTP" (Timedbased One-Time Password) tool for my github account. I use github on my LINUX 6.8.0-85 / Ubuntu 24.04.3 desktop computer from my web-browser and from my Eclipse IDE. Github now forces their users to use 2FA, with a delay of 45 days. They do not recommend any tool, so I had to search for a LINUX app that provides 2FA (TOTP).

First I installed gnome-authenticator 3.32.2, but this did not work. It could not scan the github QR-code and it gave me no way to manually enter the "setup key" that github offers as alternative for scanning. Error message was "AccessDenied ... SelectArea is not allowed" (I love these messages without fix hint:-). I searched for solutions and found I had to install gstreamer1.0-gtk4, but this did not work, I found no PPA repository for it, moreover I had an installed version gstreamer1.0-gtk3. As it is useless, I removed gnome-authenticator from my system again.

Next I found an "askubuntu" page that recommeded KeePassXC, and this installation worked fine via commandline "sudo apt install keepassxc". The graphical user-interface can be launched afterwards via "keepassxc".

Here are some screenshots of what I did then in that tool. I must say that I am completely new to 2FA and do not know at all how this works and what you have to do for it. I just followed the instructions of the "askubuntu" page:

  • Create a new KeePassXC database - the GUI tool prompts you for parameters and does this for you, no problem
  • Create a new "Entry" with username and password, here I used my github username and password, it did not require any password standards, or maybe my password was good enough
  • Select the "Entry" and go to menu "Entries" - "TOTP" - "Set up TOTP", here I entered the "setup key" from github
  • In the context-menu of the selected "Entry" I chose "TOTP" - "Copy TOTP" and entered the result on the github page
  • github then labeled my 2FA authentication as "Configured"

Here are the screenshots of most things I have done:

Here I already created my user "Entry". The screenshot shows the context menu that leads to "New Entry".

Here is the UI where you create the "New Entry". You need to enter your username and password for that. I also entered the URL of my github project.

This is how the app looks when the "Entry" is created.

The menu to set up TOTP. Don't forget to select your "Entry" first.

Here I entered the "setup key" from github as "Secret Key".

Here is the context menu on the selected "Entry" to copy a generated TOTP.

This is how my github account "Password and authentication" page looked after I entered the generated TOTP in the github input field.

One more evening gone for LINUX! But I don't give up trusting in open-source software, it's simply the better concept.


UPDATE: Today I logged out from github to explore how to log in via 2FA with keepassxc.

  • After confirming the dialog that I really want to log out, I clicked "Log in" on the github main page
  • Until now I used a generated password sequence for accessing github; in the github login dialog, I entered my username and that generated long password, but this didn't work, the password was reported to be invalid
  • So I tried the password I entered yesterday on my keepassxc application, and this worked (I can't say if keepassxc submitted that password to github or if that was my initial old github password, because I used the same for both)
  • After receiving the correct password, github asked me to "Enter the code from your two-factor authentication app ... below"
  • I launched keepassxc, selected my "Entry", used the context menu on it und chose "TOTP" - "Copy TOTP" (see screenshot above)
  • I guess the app copied the code to the system-clipboard, so that I could paste it into the input field ("XXXXXX") on the github page in my browser
  • I didn't even have to press ENTER or click the green "Verify" button, immediately my github account page opened.

So for performing 2FA with github, you always need your 2FA app open to copy a generated TOTP from it. For starting the 2FA app, you always have to enter your password in it. Thus 2FA authentication takes more time than a simple log-in with username and password, and it requires the presence of always the same 2FA app on your device. If you lose your device, you lose access to your github account, which only can be restored using the recovery codes that github provided you while setting up 2FA for your account. If you missed that, or stored the recovery codes on your now damaged device, the github support will not be able to give you back your account! That is why they recommend to store recovery codes in some Internet cloud.

I now tried to push some source-code changes in my Eclipse, and it worked. I had configured Eclipse to also use the github-generated long password sequence, so I wonder if that will break in 45 days, because obviously that sequence became invalid.




Donnerstag, 25. September 2025

Java In-Memory URL

Ever needed an URL that reads its bytes from memory? This may be useful when you need to redirect a stream through some filter. Here is a Java class that does this stunningly simple. Mind that it is for a small amount of data only, all bytes (String data) must be in memory for this to work.

 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
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.Objects;

public final class InMemoryUrl
{
    public static URL build(String data) {
        Objects.requireNonNull(data);
        try {
            return URL.of(
                new URI("InMemory", "DummyPath", null), // protocol, path, reference
                new InMemoryUrlStreamHandler(data.getBytes()));
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static class InMemoryUrlStreamHandler extends URLStreamHandler
    {
        private final byte[] data;
        
        public InMemoryUrlStreamHandler(byte[] data) {
            this.data = data;
        }
        
        @Override
        protected URLConnection openConnection(URL url) throws IOException {
            return new URLConnection(url)
            {
                @Override
                public void connect() throws IOException {
                    connected = true;
                }
                @Override
                public long getContentLengthLong() {
                    return data.length;
                }
                @Override
                public InputStream getInputStream() throws IOException {
                    return new ByteArrayInputStream(data);
                }
            };
        }
    }
    
    private InMemoryUrl() {}
}

Here is a test main that you can use to try it out:

    public static void main(String[] args) throws Exception {
        final URL url = InMemoryUrl.build("This is a test with non-ASCII chars: ÄÜÖ äüö ß");
        final byte[] data = url.openConnection().getInputStream().readAllBytes();
        System.out.println(new String(data));
    }



Montag, 21. Juli 2025

How to Make Very Wide Textpages More Readable

This is about web-browser pages that display very wide text. To be more specific, a text line is so wide that the eye is not able to find the next line when it reached the right end of the current line and returns to the left. This makes reading tedious and slow. I mean something like the following (screenshot):

But you can modify the layout of web-pages at any time. Click into one of the text paragraphs that are too wide. Then press F12, or open the context menu by clicking right mouse button and select "Inspect", or open the top-right browser menu and select "More Tools" - "Developer Tools". A window or page-embedded panel like the following will open (screenshot):

The HTML element "p" (paragraph) should already be selected on the left in this debugger-window, when not, make sure the tab "Elements" is selected on top, and click onto some "p" element in the left side page view. Then you should see the CSS-styles of "p" on right side.

Click onto the end of some right-side style line (maybe also press ENTER?) to create a new style line. Write the following in new style lines:

  • columns: 4;
  • border: 2px solid gray;
  • and optionally to make it more beautiful:
    • padding: 8px;
    • border-radius: 8px;

Screenshot:

Your page should look like this now (screenshot):

You can close the F12 debugger window, your styles will persist until you reload the current page, or go to a new page. Unfortunately you need to do this on every new page again!

The text is much better readable now. You will be surprised how this speeds up your reading. This is called the "newspaper layout", and it is one of the things that web-designers do not take serious. Not everything that is old (and this centuries old!) is bad. I must mention that the CSS "columns" style is quite new, available only with browsers that support CSS version 3.




Montag, 16. Juni 2025

Should I Use abc Music Notation

I don't know how often I looked at the abc music notation and considered using it. I never did, and the first look was 27 years ago. Not that I did not need it, I have always been using and writing music on paper, and a computer would have offered some valuable features like transposing a score from one key to another in a second. But abc never convinced me. And with every look at it I found new disadvantages.

In 1997, the years when abc evolved, I decided to implement my own music notation language. The goal was to write notes for a single-voiced melody and output it as printable sheet music. I achieved it by using Java/AWT as graphical input user-interface, a self-designed notation language using simple text characters, and a GNU awk script that translated it into into MusixTeX, which is a very mature French TeX package for writing music scores. Together with the C-programming make utility and UNIX shell scripts I wrote and printed over 120 of my pieces on that application. This worked very well for 26 years, across several different LINUX distributions, although I had to manually configure the MusixTeX files and pathes sometimes when changing distros. I would not dare to write MusixTeX source directly, that's not readable. If you have unreadable source, it will be time-consuming and error-prone to maintain, correct and extend your pieces. My own notation language is simple, structured and readable, although more a commandline-language than a context-free grammar.

So how readable is the abc alternative?
By the way, alternative to abc is just musicXML, but this is not readable at all, too big.
The abc notation is free open source, and it was well accepted by both musicians and developers. There are online-translators on the web where you can try it out. It even has a grammar definition, but not final, and version-specific. Basically it is a commandline-language, that means every line of an .abc file is interpreted in the context of the header lines read before. The order of header lines matters. Another thing is the readability. Not good at all. Not too big like musicXML, too small instead.

  1. Spaces:
    If you want "beaming" (eighth notes with a common flag-line), you must do it by leaving out spaces between the notes. The result is really hard to read. What would you expect is this:
    X:1
    L:1/8
    K:C  % end of header lines
    c2G EFG
    Look at the last line. In IPN notation, this is a "C5" quarter note, then a "G4" eighth note, then "E4", "F4" and "G4" eighth notes. The EFG will be beamed together, not the c2G, because a quarter note has no flag that can be written as beam. (Fortunately, for all cases other than beaming, spaces can be used to separate notes.)

  2. Newlines:
    If you want the "staff" (note line) to end, you insert a newline. If you don't, the note line will get wider and wider. In other words, you must do the layout of the score manually through newlines. There is no formatter that sizes notes and bars to some formatting parameter, like musixflx provides in MusixTeX - abc source code hard-codes layout:
    X:1
    L:1/4
    K:C
    GBd2|GBd2| 
    a/g/f/e/d2|a/g/f/e/d2|
    This will be rendered in two "staffs" (note lines), each containing two bars. Effect is that whenever you maintain your score and add some fill notes, the overall layout will be affected. The idea behind this may have been to formulate conceptual units, but in fact it turns out to be a missing model-view separation.

  3. Empty Lines:
    Another newline-gotcha is that an empty line introduces a new song. So you must not have empty lines inside your tune!

  4. Numbers:
    There is misleading similarity between IPN notation and abc notation concerning numbers.
    The IPN "C2" means the C in 2nd octave (very low).
    The abc "C2" means an IPN "C4" that is 2 times longer than the standard-length of a note (which is given in header via e.g. "L:1/8", in that case it would be a quarter note).
    This is not a nice feature for reading musicians that know IPN.

  5. Separators:
    In abc, the comma is not a separator. It lowers the octave of the note before. The expression C, (C comma) is one octave lower than C. A single quote is used to put the note one octave higher, c' is one octave higher than c (which surprisingly is one octave higher than C).
    Yes, it is understandable, but a comma always was and will be a separator for normal human beings.

Many more cryptographies have been used that make abc scores hardly readable. Not only the author will have problems when coming back to his score years later, maybe also other people would like to be able to maintain some abc source that was written by someone that unfortunately liked to apply every trick the notation offers.

Finally the recommended EasyABC editor - can probably be installed on LINUX, but you should take yourself a day for it. I just threw away my nerves. So much Python documentation and Blogs to read. Everything so outdated (2017), written in so tiny fonts. The editor was implemented in Python, using the wxPython UI toolkit, which is platform-dependent. (Is Python really platform-independent?) The Python package installer (pip) seems to not support LINUX when a package contains platform-dependent binaries. How elegant Java is ....

Summary:
The informatic heresy "Write Less Do More" has done lots of harm, also here in abc. I can understand that shortness was important in 1990 when machines had just 64 KB memory, but since then there is no need for shortness, just the contrary would be needed, as so much unreadable source code blocks the human side of digitalization. Simplicity and accuracy will win in the long run.
Ask yourself:
Will I still be able to understand my abc code in a year?
Will my colleague be able to maintain my abc code when I go on vacation?

Yes, it would be nice to be part of the abc community with nearly a million tunes on its web-repository, but not for the price of having to layout tons of hard-to-read code after every maintenance. So I'll stay on my home-grown solution, will improve it and add an optional "to abc" converter to it. Maybe we all should use MuseScore.

Why is there no better solution for writing music notes? Because this is so complex. It has grown over centuries. It even contains programming structures like loops and conditions, and look how complicated programming languages are.

In case you already fell into abc, here is an example how to not write abc code:

....
L:1/4
K:G
GBd2|GBd2| 
a/g/f/e/d2|[Aa]/g/f/e/[Dd]2| 
G,/G/zg2|D/d/z[Aa]2|
z2[Dd]B|[G,G]4|]

(Can anyone read that?) You can achieve the same through more readable code:

....
L: 1/1
K: G
G/4 B/4 d/2 | G/4 B/4 d/2 | 
a/8` g/8` f/8` e/8 d/2 | [Aa]/8` g/8` f/8` e/8 [Dd]/2 | 
G,/8` G/8 z/4 g/2 | D/8` d/8 z/4 [Aa]/2 | 
z/2 [Dd]/4 B/4 | [G,G] |]
  • Set the standard note length to the whole bar (1/1), and put the length of the note to every note, else you will have to always calculate their lengths while reading (Write more do more:-)
    UPDATE: Dotted notes! Oh my God. There is no regular dot in abc, they do it via the "L: 1/8" header and according multiplication factors. You would have to use the 'broken rhythm' symbol ">", but this affects also the next note, so you would have to change e.g. the following eighth-note to a quarter-note - which again makes the notes hard to understand (wrong note length).

  • Add spaces wherever it is possible. If impossible due to beaming, you can use back quote ` (with optional subsequent space) to beam together the (then separated) notes.

How do you make your abc code more readable? Leave a comment!


UPDATE 1: didn't take me long to break it, look at following triplet clamps:

X: 1
T: Triplet Clamps Too Short
N: For all of eighth / quarter / half triplets the clamp is too short
M: 4/4
L: 1/1
(3 E/8`F/16`-G/16`A/8 B/4 c/2 |\
(3 D/4 E/8`-F/8 G/4 A/2 |\
(3 C/2 D/4`-E/4 F/2 |

This is not due to the '-' character (tie), you can also leave that out. I tried this abc code on several web translators, was the same everywhere. When you declare a triplet, it binds the next three notes, regardless of what durations they have.


UPDATE 2: maybe concerning only the abcjs web-UI. Try to play this tune on some online abc translator's player:

X: 1
T: Lengthened Notes Play Differently
M: 4/4
L: 1/1
(C | C) | F | C- | C | F |

You will notice that the "(C | C)" expression plays two tones, while the "C- | C" expression correctly just sounds once before the F. The printed notes are the same in both cases, with a slur into next bar.


UPDATE 3: detected a really bad bug. Voices influence each other. The "broken rhythm" marker (">") seems to have effect across bar and voice boundaries. Try to render this tune on some online abc translator:

X: 1
M: 3/4
L: 1/1
K: C
V: VoiceOne
[c/2 g/2]> |
V: VoiceTwo
G/4> A/4`G/8`F/8 |

This example has two voices. In VoiceOne there is a dotted half chord without a subsequent note. The VoiceTwo starts with a dotted quarter note ("G/4>"), but seems to be affected by the "broken rhythm" marker (">") of VoiceOne, because it appears as dotted eighth note:

See the dotted eighth note in second line, which should be a dotted quarter? Moreover, the dot sits too high.

The consequence is really hard. It forces you to use the "L:" header field for any note length. That means, whenever you have a tune that contains dotted notes, you need to set the "L:" field to a value that can match the shortest length of a dotted note. For example, if you have a dotted quarter note in your tune, you need to set "L: 1/8", because only then you can avoid the fatal "broken rhythm" marker by stating "[c6 g6]" and "G3":

X: 1
M: 3/4
L: 1/8
K: C
V: VoiceOne
[c6 g6] |
V: VoiceTwo
G3 A`G`F |



Donnerstag, 29. Mai 2025

Set Java Swing JSlider Position by Mouse Click

You can set the position of the Java/Swing JSlider knob by dragging it with the left mouse button. But you can not set its position by clicking into the slider's running track, neither by left nor by right click. The left click moves the knob just a little by its increment value, which may require lots of clicks to get to the desired position. But the right mouse click is not used by JSlider, so why not use it for moving the knob to the click location?

Another idea would be to show tooltips according to the current mouse position over the slider. That means if you move the mouse e.g. over the value "2" of the slider, you would like to show a tooltip that says "Mouse is over the value of 2" (or something not so silly:-).

Move Knob to Right Mouse Click Location

All of the following applies to both horizontal and vertical sliders.

    private JSlider createSlider() {
        final JSlider slider = new JSlider();
        
        slider.addMouseListener(new MouseAdapter() {
            /** Moves the slider knob to the given event location when it was a right-mouse click.*/
            @Override
            public void mouseClicked(MouseEvent e) {
                if (((JComponent) e.getSource()).isEnabled() && SwingUtilities.isRightMouseButton(e)) {
                    final int sliderValue = new SliderMouseEventLocator(e).sliderValue;
                    slider.setValue(sliderValue);
                }
            }
        });

        return slider;
    }

So this was the mouse event catching, but what is SliderMouseEventLocator?

public class SliderMouseEventLocator
{
    /** Result of construction. */
    public final Integer sliderValue;
    
    public SliderMouseEventLocator(MouseEvent e) {
        final JSlider slider = (JSlider) e.getSource();
        final BasicSliderUI ui = (BasicSliderUI) slider.getUI();
        final boolean horizontal = (slider.getOrientation() != SwingConstants.VERTICAL);
        this.sliderValue = horizontal ? ui.valueForXPosition(e.getX()) : ui.valueForYPosition(e.getY());
    }
}

This code converts the mouse click location to a slider value. The mouse-event coordinate is relative to the slider, not relative to the window. We can let BasicSliderUI do the work.

You need a new SliderMouseEventLocator for each arriving mouse event. If you want to reuse that object, you would have to implement a mouse-event converter-method instead of the immutable public final sliderValue result field (that must be evaluated at least on constructor execution).

Show Tooltip According to Mouse Location

Having the SliderMouseEventLocator class, it is easy to implement different tooltips according to mouse moves.

        slider.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseMoved(MouseEvent e) {
                final Integer sliderValue = new SliderMouseEventLocator(e).sliderValue;
                final String tooltip = getTextRepresentation(sliderValue);
                // TODO: implement getTextRepresentation
                slider.setToolTipText(tooltip);
            }
        });

Add this snippet to the createSlider() method and implement getTextRepresentation(sliderValue).

Hope this was helpful for the few remaining Swing programmers!




Freitag, 23. Mai 2025

Basic Ubuntu LINUX Firewall Security

LINUX has a built-in firewall called netfilter, configurable through iptables. There is a user friendly Ubuntu command-line tool called ufw to configure this firewall. All three should be already installed in your Ubuntu LINUX. (Probably you don't want to mess with snort!)

By default, the firewall is not turned on!
You must activate it manually. For the purpose of working with your firewall, it may be useful to additionally install its graphical user interface:

  • sudo apt install gufw

And run it:

  • sudo gufw

You will see this:

If you move the "Status" switch to the right, the firewall will be turned on. Now enter this command-line:

  • sudo ufw status

You should see "Status: active". This will survive a reboot. You can reach the gufw graphical user interface without command-line when you enter "firewall" in your application-finder ("Show Apps" button on Ubuntu taskbar). Of course you must enter the superuser password here, as you must do when running any sudo commandline. Remember to read the documentation, especially when you use P2P applications, because ports may be closed now!




Single Web Page Upload to the Internet

Just copied my (self-contained) information page about Austrian mountains to the Internet ("Berge Österreichs mit mindestens 100 m Schartenhöhe"):

This contains lots of information, and why shouldn't it be visible to all?
The Internet provides a free single web-page upload on

Only an accessible e-mail address is required. (Hopefully no ads coming now...)

This page is full of JavaScript (ES6) code that lets you filter and sort table rows, and even collapse and expand table columns. Click onto the column header to try out. It also displays the number of all and that of currently filtered rows on top left.

I published and explained the JavaScript (ES6) code for this type of HTML-table in a series of 4 Blog articles, starting with Sortable Filterable HTML Table with ES6, Part 1. You can use this code for free. Have fun!

Samstag, 10. Mai 2025

HTML Page Reload on Focus

Imagine you need a page that always reloads itself from the server when the user focuses it, that means the according browser tab or window was in background and gets clicked to foreground. For such a page the user would never have to press the "Reload" button when coming back to it, which may make sense e.g. for a wheather-report site (to always show the current temperature).

This is different from updating the page periodically, which can be done via the refresh attribute in a <head> <meta> element. Mind that such would take control away from the user, may consume valuable resources, and is not applicable in all environments.

Code

Following HTML would do a page reload on focus. Mind that this is not recommendable for big expensive pages that need several seconds to render!

<!DOCTYPE HTML>
<html>
  <body onfocus="window.location = window.location;">

    <!-- Here goes the page HTML -->

  </body>
</html>

The technique is to install an event handler for the focus event in the <body> element. The JavaScript code "window.location = window.location;" inside the event handler may look ridiculous, but it actually performs a page reload.

Test

Here is a test-page to verify that behavior. It displays the current time every time it gets loaded, and it has the above mechanism installed.

 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
<!DOCTYPE HTML>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <title>Show Load Time when Focused</title>
  </head>

  <body
    onpageshow="renderTime();"
    onfocus="window.location = window.location;"
  >
    <fieldset>
      <legend>Load Time:</legend>
      <div id="time"></div>
    </fieldset>
	
    <script>
      function renderTime() {
        const date = new Date();
        const timeField = document.getElementById("time");
        timeField.innerHTML = date.getHours()+":"+date.getMinutes()+":"+date.getSeconds();
      }
    </script>
  </body>
</html>

To verify,

  1. load this page in your web-browser,
  2. look at the displayed time, remember it,
  3. then open a new tab or window and focus it (or click onto some already opened),
  4. then change back to this page

→ The displayed time must have changed now.

Resume

I do not know if this is the simplest way to make a page reload itself automatically when it is looked at, but at least it needs very few code. If you need to update just single elements or fields on focus, you will have to use AJAX.




Sonntag, 16. März 2025

Convert Decimal to Fraction in Java

Ever needed to convert a decimal number into a fraction, like 0.5 into 1 / 2 ?
There is a simple method to do this, but it doesn't always work:

    public static long[] toFraction(double decimal) {
        String decimalAsString = String.valueOf(decimal);
        int decimalPlaces = decimalAsString.length() - decimalAsString.indexOf('.') - 1;
        
        long divisor = (long) Math.pow(10, decimalPlaces);
        long dividend = (long) (decimal * divisor);

        long gcd = greatestCommonDivisor(dividend, divisor);

        return new long[] { dividend / gcd, divisor / gcd };
    }

    public static long greatestCommonDivisor(long... numbers) {
        return greatestCommonDivisor(LongStream.of(numbers));
    }
    public static long greatestCommonDivisor(LongStream numbers) {
        return greatestCommonDivisorLong(numbers.boxed());
    }
    public static long greatestCommonDivisorLong(Stream<Long> numbers) {
        return numbers.reduce(0L, (x, y) -> gcd(x, y));
    }
    private static long gcd(long x, long y) {
        return (y == 0L) ? x : gcd(y, x % y);
    }

This works nice as long as you don't pass "periodic" numbers like 1.333333333333333 to that method. It would return 1333333333333333 / 1000000000000000, not 4 / 3 like expected.

Now there are libraries on the web that resolve this problem, the most popular being Apache Commons Math. The only problem for me was that there are so many other solutions in this library (of size 2.2 MB) that I would never need for my very small project. So I decided to go into that source and look if I could adapt some snippet. What I came across was not really understandable code, but trying it out showed that it worked well also for periodic numbers:

  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
100
101
102
103
104
105
106
107
108
109
110
111
112
/** Adopted and simplified from apache-commons-math BigFraction. */
public class Fraction
{
    private static final double DEFAULT_EPSILON = 1e-5; // 1.0e-20
    private static final int DEFAULT_ITERATIONS = 100; // 10000

    private final double doubleValue;
    private final BigInteger dividend; // numerator
    private final BigInteger divisor; // denominator

    /**
     * @param dividend numerator, the number above the fraction line.
     * @param divisor denominator, the number below the fraction line.
     */
    public Fraction(long dividend, long divisor) {
        this((double) dividend / (double) divisor);
    }
    
    /**
     * @param value the number to turn into a (reduced) fraction.
     */
    public Fraction(double value) {
        this(value, DEFAULT_EPSILON, Integer.MAX_VALUE, DEFAULT_ITERATIONS);
    }

    private Fraction(double value, double epsilon, int maxDenominator, int maxIterations) {
        this.doubleValue = value;
        
        final long overflow = Long.MAX_VALUE;
        double r0 = value;
        long a0 = (long) Math.floor(r0);

        if (Math.abs(a0) > overflow) {
            throw new RuntimeException("MAX_VALUE overflow!");
        }

        // check for (almost) integer arguments, which should not go to iterations.
        if (Math.abs(a0 - value) < epsilon) {
            dividend = BigInteger.valueOf(a0);
            divisor  = BigInteger.ONE;
            return;
        }

        long p0 = 1;
        long q0 = 0;
        long p1 = a0;
        long q1 = 1;

        long p2 = 0;
        long q2 = 1;

        int n = 0;
        boolean stop = false;
        do {
            ++n;
            final double r1 = 1.0 / (r0 - a0);
            final long a1 = (long) Math.floor(r1);
            p2 = (a1 * p1) + p0;
            q2 = (a1 * q1) + q0;
            if ((p2 > overflow) || (q2 > overflow)) {
                // in maxDenominator mode, if the last fraction was very close to the actual value
                // q2 may overflow in the next iteration; in this case return the last one.
                if (epsilon == 0.0 && Math.abs(q1) < maxDenominator) {
                    break;
                }
                throw new RuntimeException("maxDenominator overflow!");
            }

            final double convergent = (double) p2 / (double) q2;
            if ((n < maxIterations) &&
                (Math.abs(convergent - value) > epsilon) &&
                (q2 < maxDenominator)) {
                p0 = p1;
                p1 = p2;
                q0 = q1;
                q1 = q2;
                a0 = a1;
                r0 = r1;
            } else {
                stop = true;
            }
        } while (!stop);

        if (n >= maxIterations) {
            throw new RuntimeException("maxIterations overflow!");
        }

        if (q2 < maxDenominator) {
            dividend = BigInteger.valueOf(p2);
            divisor  = BigInteger.valueOf(q2);
        } else {
            dividend = BigInteger.valueOf(p1);
            divisor  = BigInteger.valueOf(q1);
        }
    }

    
    /** @return the calculated-by-division or constructor-given value. */
    public double doubleValue() {
        return doubleValue;
    }

    /** @return the reduced dividend (numerator). */
    public BigInteger getDividend() {
        return dividend;
    }
    
    /** @return the reduced divisor (denominator). */
    public BigInteger getDivisor() {
        return divisor;
    }
}

112 lines of code against a 2.2 MB library (that may also change its API in course of time) is not bad. I confess that I don't understand lines 26 to 95, but I also don't understand many other things that I use daily.

Here are two applications for class Fraction, which I have put into MathUtils.java:

    /** 1.5 -> 3/2 */
    public static long[] toFraction(double number) {
        final Fraction fraction = new Fraction(number);
        return new long[] { 
                fraction.getDividend().longValue(), 
                fraction.getDivisor().longValue() 
            };
    }

    /** 10/4 -> 5/2 */
    public static long[] reduceFraction(long dividend, long divisor) {
        final Fraction fraction = new Fraction(dividend, divisor);
        return new long[] { 
                fraction.getDividend().longValue(), 
                fraction.getDivisor().longValue() 
            };
    }

Following JUnit-5 test code in MathUtilsTest.java showed that it works:

    @Test
    void periodicNumberToFraction() {
        long[] fraction;
        
        fraction = MathUtils.toFraction1(1.333333333333333);
        assertEquals(4, fraction[0]);
        assertEquals(3, fraction[1]);
        
        fraction = MathUtils.toFraction(1.666666666666666);
        assertEquals(5, fraction[0]);
        assertEquals(3, fraction[1]);
        
        fraction = MathUtils.toFraction(1.777777777777777);
        assertEquals(16, fraction[0]);
        assertEquals(9, fraction[1]);
        
        fraction = MathUtils.toFraction(0.777777777777777);
        assertEquals(7, fraction[0]);
        assertEquals(9, fraction[1]);
        
        fraction = MathUtils.toFraction(1.599999999999999); // ~ 1.6
        assertEquals(8, fraction[0]);
        assertEquals(5, fraction[1]);
    }

If you can't put a truck onto your bicycle, you may want to use a photo of it:-)
Hope this is helpful to someone!