Fix Non-terminating decimal expansion bug + optimization of multiplier calculation
fixes #2581
Reason for issue was missing scale and rounding mode when dividing with stock-multiplier for calculation of new quote.

I changed the calculation the following way:
For both calculations (new stock and new quote) an individual multiplier is used. These multipliers (stockMultiplier and quoteMultipler) were calculated when new shares or old shares are set. In this way the multipliers were only calculated twice and not in every iteration when all new stocks or new quotes were calculated.
name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/wizards/splits/StockSplitModel.java
package name.abuchen.portfolio.ui.wizards.splits;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.List;

import name.abuchen.portfolio.model.Client;
import name.abuchen.portfolio.model.Security;
import name.abuchen.portfolio.model.SecurityEvent;
import name.abuchen.portfolio.model.SecurityPrice;
import name.abuchen.portfolio.model.Transaction;
import name.abuchen.portfolio.model.TransactionPair;
import name.abuchen.portfolio.money.Values;
import name.abuchen.portfolio.ui.util.BindingHelper;

public class StockSplitModel extends BindingHelper.Model
{
    private Security security;
    private LocalDate exDate = LocalDate.now();
    private BigDecimal newShares = BigDecimal.ONE;
    private BigDecimal oldShares = BigDecimal.ONE;

    private boolean changeTransactions = true;
    private boolean changeHistoricalQuotes = true;

    public StockSplitModel(Client client, Security security)
    {
        super(client);

        this.security = security;
    }

    public Security getSecurity()
    {
        return security;
    }

    public void setSecurity(Security security)
    {
        firePropertyChange("security", this.security, this.security = security); //$NON-NLS-1$
    }

    public LocalDate getExDate()
    {
        return exDate;
    }

    public void setExDate(LocalDate exDate)
    {
        firePropertyChange("exDate", this.exDate, this.exDate = exDate); //$NON-NLS-1$
    }

    public BigDecimal getNewShares()
    {
        return newShares;
    }

    public void setNewShares(BigDecimal newShares)
    {
        firePropertyChange("newShares", this.newShares, this.newShares = newShares); //$NON-NLS-1$
    }

    public BigDecimal getOldShares()
    {
        return oldShares;
    }

    public void setOldShares(BigDecimal oldShares)
    {
        firePropertyChange("oldShares", this.oldShares, this.oldShares = oldShares); //$NON-NLS-1$
    }

    public boolean isChangeTransactions()
    {
        return changeTransactions;
    }

    public void setChangeTransactions(boolean changeTransactions)
    {
        firePropertyChange("changeTransactions", this.changeTransactions, //$NON-NLS-1$
                        this.changeTransactions = changeTransactions);
    }

    public boolean isChangeHistoricalQuotes()
    {
        return changeHistoricalQuotes;
    }

    public void setChangeHistoricalQuotes(boolean changeHistoricalQuotes)
    {
        firePropertyChange("changeHistoricalQuotes", this.changeHistoricalQuotes, //$NON-NLS-1$
                        this.changeHistoricalQuotes = changeHistoricalQuotes);
    }

    // stock multiplier = quote divider
    private BigDecimal getStockMultiplier()
    {
        return newShares.divide(oldShares, Values.MC);
    }
    
    public long calculateNewStock(long oldStock)
    {
        return BigDecimal.valueOf(oldStock).multiply(getStockMultiplier())
                        .setScale(0, RoundingMode.HALF_EVEN).longValue();
    }
    
    public long calculateNewQuote(long oldQuote)
    {
        return BigDecimal.valueOf(oldQuote).divide(getStockMultiplier()) // when stock is multiplied, quote must be divided
                        .setScale(0, RoundingMode.HALF_EVEN).longValue();        
    }
    
    @Override
    public void applyChanges()
    {
        // save stock split ratio as technical values (and hence do not format
        // in the local of user) in order to restore/retrieve ratio later
        SecurityEvent event = new SecurityEvent(exDate, SecurityEvent.Type.STOCK_SPLIT, newShares + ":" + oldShares); //$NON-NLS-1$
        security.addEvent(event);

        if (isChangeTransactions())
        {
            List<TransactionPair<?>> transactions = security.getTransactions(getClient());
            for (TransactionPair<?> pair : transactions)
            {
                Transaction t = pair.getTransaction();
                if (t.getDateTime().toLocalDate().isBefore(exDate))
                    t.setShares(calculateNewStock(t.getShares()));
            }
        }

        if (isChangeHistoricalQuotes())
        {
            List<SecurityPrice> quotes = security.getPrices();
            for (SecurityPrice p : quotes)
            {
                if (p.getDate().isBefore(exDate))
                    p.setValue(calculateNewQuote(p.getValue()));
            }
        }

    }
}

package name.abuchen.portfolio.ui.wizards.splits;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.List;

import name.abuchen.portfolio.model.Client;
import name.abuchen.portfolio.model.Security;
import name.abuchen.portfolio.model.SecurityEvent;
import name.abuchen.portfolio.model.SecurityPrice;
import name.abuchen.portfolio.model.Transaction;
import name.abuchen.portfolio.model.TransactionPair;
import name.abuchen.portfolio.money.Values;
import name.abuchen.portfolio.ui.util.BindingHelper;

public class StockSplitModel extends BindingHelper.Model
{
    private Security security;
    private LocalDate exDate = LocalDate.now();
    private BigDecimal newShares = BigDecimal.ONE;
    private BigDecimal oldShares = BigDecimal.ONE;
    private BigDecimal stockMultiplier = BigDecimal.ONE;

    private boolean changeTransactions = true;
    private boolean changeHistoricalQuotes = true;

    public StockSplitModel(Client client, Security security)
    {
        super(client);

        this.security = security;
    }

    public Security getSecurity()
    {
        return security;
    }

    public void setSecurity(Security security)
    {
        firePropertyChange("security", this.security, this.security = security); //$NON-NLS-1$
    }

    public LocalDate getExDate()
    {
        return exDate;
    }

    public void setExDate(LocalDate exDate)
    {
        firePropertyChange("exDate", this.exDate, this.exDate = exDate); //$NON-NLS-1$
    }

    public BigDecimal getNewShares()
    {
        return newShares;
    }

    public void setNewShares(BigDecimal newShares)
    {
        firePropertyChange("newShares", this.newShares, this.newShares = newShares); //$NON-NLS-1$
        calculateStockMultiplier();
    }

    public BigDecimal getOldShares()
    {
        return oldShares;
    }

    public void setOldShares(BigDecimal oldShares)
    {
        firePropertyChange("oldShares", this.oldShares, this.oldShares = oldShares); //$NON-NLS-1$
        calculateStockMultiplier();
    }

    public boolean isChangeTransactions()
    {
        return changeTransactions;
    }

    public void setChangeTransactions(boolean changeTransactions)
    {
        firePropertyChange("changeTransactions", this.changeTransactions, //$NON-NLS-1$
                        this.changeTransactions = changeTransactions);
    }

    public boolean isChangeHistoricalQuotes()
    {
        return changeHistoricalQuotes;
    }

    public void setChangeHistoricalQuotes(boolean changeHistoricalQuotes)
    {
        firePropertyChange("changeHistoricalQuotes", this.changeHistoricalQuotes, //$NON-NLS-1$
                        this.changeHistoricalQuotes = changeHistoricalQuotes);
    }

    private void calculateStockMultiplier()
    {
        stockMultiplier =  newShares.divide(oldShares, Values.MC);
    }
    
    public long calculateNewStock(long oldStock)
    {
        return BigDecimal.valueOf(oldStock).multiply(stockMultiplier)
                        .setScale(0, RoundingMode.HALF_EVEN).longValue();
    }
    
    public long calculateNewQuote(long oldQuote)
    {
        return BigDecimal.valueOf(oldQuote).divide(stockMultiplier, Values.MC) // when stock is multiplied, quote must be divided
                        .setScale(0, RoundingMode.HALF_EVEN).longValue();
    }
    
    @Override
    public void applyChanges()
    {
        // save stock split ratio as technical values (and hence do not format
        // in the local of user) in order to restore/retrieve ratio later
        SecurityEvent event = new SecurityEvent(exDate, SecurityEvent.Type.STOCK_SPLIT, newShares + ":" + oldShares); //$NON-NLS-1$
        security.addEvent(event);

        if (isChangeTransactions())
        {
            List<TransactionPair<?>> transactions = security.getTransactions(getClient());
            for (TransactionPair<?> pair : transactions)
            {
                Transaction t = pair.getTransaction();
                if (t.getDateTime().toLocalDate().isBefore(exDate))
                    t.setShares(calculateNewStock(t.getShares()));
            }
        }

        if (isChangeHistoricalQuotes())
        {
            List<SecurityPrice> quotes = security.getPrices();
            for (SecurityPrice p : quotes)
            {
                if (p.getDate().isBefore(exDate))
                    p.setValue(calculateNewQuote(p.getValue()));
            }
        }

    }
}

@buchen: Of course, using single field also works. I tried BigDecimal#divide(stockMultiplier, Values.MC) locally and the unittest succeeds.
Should I switch to single field?

**Edit: With 9ee23fc I switched to single field solution (divide and passing math context).**