Skip to content

Currency Service: Zero conversion results due to floating point precision issues #108

@osw282

Description

@osw282

Problem Diagnosis

The currencyservice is experiencing frequent errors where currency conversions result in zero amounts. Based on analysis of the pod logs and source code in server.js, the issue stems from floating-point precision problems in the currency conversion algorithm.

Error Pattern Observed

The following error appears repeatedly in the logs:

"Currency conversion resulted in zero amount, switching back to original currency"

Root Cause Analysis

In the convert function (lines 179-226 in server.js), the service performs a two-step conversion:

  1. Convert from source currency to EUR: units / data[from.currency_code]
  2. Convert from EUR to target currency: euros.units * data[request.to_code]

The problem occurs when:

  1. Small amounts are being converted between currencies with significantly different exchange rates
  2. JavaScript's floating-point arithmetic introduces precision errors
  3. The Math.floor() operations on lines 206-207 truncate small positive values to zero

Recommended Fix

Replace the current floating-point arithmetic with a decimal library to maintain precision:

// Add to dependencies
const Decimal = require('decimal.js');

// Replace the convert function logic:
function convert (call, callback) {
  try {
    _getCurrencyData((data) => {
      const request = call.request;

      // Use Decimal for precise arithmetic
      const fromRate = new Decimal(data[request.from.currency_code]);
      const toRate = new Decimal(data[request.to_code]);
      
      // Convert units and nanos separately with high precision
      const unitsDecimal = new Decimal(request.from.units).div(fromRate).mul(toRate);
      const nanosDecimal = new Decimal(request.from.nanos).div(fromRate).mul(toRate);
      
      const result = {
        units: Math.floor(unitsDecimal.toNumber()),
        nanos: Math.floor(nanosDecimal.toNumber()),
        currency_code: request.to_code
      };

      // Apply the existing zero-check logic
      if (request.from.currency_code !== request.to_code) {
        if (result.units === 0 && result.nanos === 0) {
          // Handle zero conversion case as before
          // ...
        }
      }

      callback(null, result);
    });
  } catch (err) {
    // Error handling
  }
}

Alternative Fix (Simpler)

If adding dependencies is not preferred, implement a minimum threshold check:

// Add minimum conversion threshold (e.g., 0.01 in the smallest unit)
const MIN_CONVERSION_THRESHOLD = 1; // 1 nano

if (result.units === 0 && result.nanos < MIN_CONVERSION_THRESHOLD) {
  // Set minimum conversion amount instead of zero
  result.nanos = MIN_CONVERSION_THRESHOLD;
}

Impact

  • Severity: High - affects all currency conversions
  • Frequency: Regular occurrence as observed in logs
  • User Experience: Customers see incorrect prices when switching currencies

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions