Kotchasan Framework Documentation

Kotchasan Framework Documentation

Number Class - Number Manipulation Utilities

EN 03 Feb 2026 12:50

Number Class - Number Manipulation Utilities

The Number class provides utilities for number formatting, safe division, and document number generation with date placeholders.

Namespace

Kotchasan\Number

Overview

Number class provides methods for:

  • Formatting numbers with thousands separators
  • Safe division (prevents division by zero)
  • Generating document numbers with date patterns

API Reference

format()

Format a number with thousands separator (preserves decimal places without rounding)

public static function format($value, string $thousands_sep = ','): string

Parameters:

  • $value - Number to format
  • $thousands_sep - Thousands separator (default: ,)

Returns: Formatted number as string

Features:

  • Supports decimals (preserves original decimal places)
  • No rounding
  • Only formats the integer part

Example:

use Kotchasan\Number;

// Basic formatting
echo Number::format(1234567); // "1,234,567"
echo Number::format(1234.56); // "1,234.56"
echo Number::format(1000000.789); // "1,000,000.789"

// No rounding of decimals
echo Number::format(123.456789); // "123.456789"
echo Number::format(9999.99); // "9,999.99"

// Integer (no decimals)
echo Number::format(123); // "123"
echo Number::format(123.0); // "123"

// Custom separators
echo Number::format(1234567, ' '); // "1 234 567"
echo Number::format(1234567, '.'); // "1.234.567"
echo Number::format(1234567, '-'); // "1-234-567"

// With currency
$price = 2500.50;
echo "Price: " . Number::format($price) . " USD";
// "Price: 2,500.50 USD"

// With database data
$total = $db->first("SELECT SUM(amount) AS total FROM orders")->total;
echo "Total: $" . Number::format($total);

division()

Perform safe division, returns 0 if divisor is 0 (prevents division by zero error)

public static function division($dividend, $divisor)

Parameters:

  • $dividend - The dividend
  • $divisor - The divisor

Returns: Result of division, or 0 (if divisor is 0)

Example:

use Kotchasan\Number;

// Normal division
echo Number::division(10, 2); // 5
echo Number::division(15, 3); // 5
echo Number::division(7, 2); // 3.5
echo Number::division(100, 7); // 14.285714285714

// Prevents division by zero
echo Number::division(10, 0); // 0 (no error)
echo Number::division(100, 0); // 0

// Calculate percentage
$correct = 85;
$total = 100;
$percentage = Number::division($correct * 100, $total);
echo "Score: " . $percentage . "%"; // "Score: 85%"

// Calculate average (safe if no data)
$totalAmount = 1500;
$itemCount = 0; // could be 0
$average = Number::division($totalAmount, $itemCount);
echo "Average: " . Number::format($average); // "Average: 0"

// With actual data
$itemCount = 5;
$average = Number::division($totalAmount, $itemCount);
echo "Average: " . Number::format($average); // "Average: 300"

// Calculate rate
$success = $db->count('orders', ['status' => 'completed']);
$totalOrders = $db->count('orders');
$successRate = Number::division($success * 100, $totalOrders);
echo "Success Rate: " . Number::format($successRate) . "%";

printf()

Format a number with date placeholders for generating document numbers

public static function printf(string $format, $value, string $prefix = ''): string

Parameters:

  • $format - Format string with placeholders
  • $value - Number value to substitute (for %d or other format specifiers)
  • $prefix - Prefix (optional, used with %s)

Returns: Formatted string

Supported Placeholders:

  • %YY - Buddhist year (full) - 2567
  • %yy - Gregorian year (full) - 2024
  • %Y - Buddhist year (2 digits) - 67
  • %y - Gregorian year (2 digits) - 24
  • %M - Month with leading zero (01-12)
  • %m - Month without leading zero (1-12)
  • %D - Day with leading zero (01-31)
  • %d - Day without leading zero (1-31)
  • %s - Prefix

Example:

use Kotchasan\Number;

// Assuming current date is December 1, 2024

// Basic document number
echo Number::printf("DOC%YY%M%D-%04d", 123);
// "DOC25671201-0123" (Buddhist year 2567, month 12, day 01)

// Gregorian year
echo Number::printf("INV%yy%M%D-%05d", 456);
// "INV20241201-00456"

// 2-digit year
echo Number::printf("REC%Y%M-%04d", 789);
// "REC6712-0789"

// Month and day without leading zeros
echo Number::printf("ORD%yy%m%d-%04d", 101);
// "ORD2024121-0101" (if date is 1/12/2024)

// With prefix
echo Number::printf("%sBILL%yy%m%d-%03d", 999, "COMP");
// "COMPBILL2024121-999"

// Tax invoice number
echo Number::printf("TAX%YY%M-%06d", 1);
// "TAX256712-000001"

// Member ID
echo Number::printf("MEM%YY-%08d", 12345);
// "MEM2567-00012345"

// Standard format specifiers
echo Number::printf("ORDER-%05d", 42); // "ORDER-00042"
echo Number::printf("ID-%010d", 123); // "ID-0000000123"

Real-World Examples

1. Receipt System

use Kotchasan\Number;

class ReceiptSystem
{
    private $db;

    public function createReceipt($customerId, $items)
    {
        // Calculate total
        $total = 0;
        foreach ($items as $item) {
            $total += $item['price'] * $item['quantity'];
        }

        // Generate receipt number
        $lastReceipt = $this->db->first("SELECT MAX(id) as last_id FROM receipts");
        $nextId = ($lastReceipt->last_id ?? 0) + 1;
        $receiptNumber = Number::printf("REC%YY%M%D-%06d", $nextId);

        // Save receipt
        $receiptId = $this->db->insert('receipts', [
            'receipt_number' => $receiptNumber,
            'customer_id' => $customerId,
            'total_amount' => $total,
            'created_date' => date('Y-m-d H:i:s')
        ]);

        return [
            'receipt_id' => $receiptId,
            'receipt_number' => $receiptNumber,
            'total_formatted' => '$' . Number::format($total)
        ];
    }

    public function calculateDiscount($amount, $discountPercent)
    {
        $discountAmount = Number::division($amount * $discountPercent, 100);
        $finalAmount = $amount - $discountAmount;

        return [
            'original' => Number::format($amount),
            'discount' => $discountPercent . '%',
            'discount_amount' => Number::format($discountAmount),
            'final' => Number::format($finalAmount)
        ];
    }
}

// Usage
$receipt = new ReceiptSystem($db);
$result = $receipt->createReceipt(123, [
    ['price' => 100, 'quantity' => 2],
    ['price' => 50.50, 'quantity' => 3]
]);

echo $result['receipt_number']; // "REC25671201-000001"
echo $result['total_formatted']; // "$351.50"

2. Financial Report

class FinancialReport
{
    public static function formatCurrency($amount)
    {
        return '$' . Number::format($amount);
    }

    public static function calculatePercentage($part, $total)
    {
        $percentage = Number::division($part * 100, $total);
        return Number::format($percentage) . '%';
    }

    public static function generateSummary($sales, $target)
    {
        $achievementRate = Number::division($sales * 100, $target);
        $remaining = $target - $sales;

        return [
            'sales' => self::formatCurrency($sales),
            'target' => self::formatCurrency($target),
            'achievement' => Number::format($achievementRate) . '%',
            'remaining' => self::formatCurrency($remaining)
        ];
    }
}

// Generate report
$report = FinancialReport::generateSummary(250000, 300000);
print_r($report);
/*
Array (
    [sales] => $250,000
    [target] => $300,000
    [achievement] => 83.333333333333%
    [remaining] => $50,000
)
*/

3. Document Number Generator

class DocumentNumberGenerator
{
    private static $formats = [
        'invoice' => "INV%YY%M%D-%05d",
        'receipt' => "REC%YY%M%D-%05d",
        'quotation' => "QUO%YY%M%D-%05d",
        'purchase_order' => "PO%YY%M%D-%05d"
    ];

    public static function generate($type, $sequence, $branch = '')
    {
        if (!isset(self::$formats[$type])) {
            throw new \Exception("Invalid document type: $type");
        }

        return Number::printf(self::$formats[$type], $sequence, $branch);
    }

    public static function generateWithBranch($type, $sequence, $branchCode)
    {
        $format = "%s" . self::$formats[$type];
        return Number::printf($format, $sequence, $branchCode);
    }
}

// Usage
echo DocumentNumberGenerator::generate('invoice', 123);
// "INV25671201-00123"

echo DocumentNumberGenerator::generateWithBranch('receipt', 456, "NYC");
// "NYCREC25671201-00456"

4. Statistics Calculator

class Statistics
{
    public static function calculateAverage($numbers)
    {
        $sum = array_sum($numbers);
        $count = count($numbers);

        return Number::division($sum, $count);
    }

    public static function calculateGrowthRate($oldValue, $newValue)
    {
        $diff = $newValue - $oldValue;
        $growthRate = Number::division($diff * 100, $oldValue);

        return Number::format($growthRate) . '%';
    }

    public static function formatReport($data)
    {
        $total = array_sum($data);
        $average = self::calculateAverage($data);
        $count = count($data);

        return [
            'count' => Number::format($count),
            'total' => Number::format($total),
            'average' => Number::format($average),
            'max' => Number::format(max($data)),
            'min' => Number::format(min($data))
        ];
    }
}

// Usage
$salesData = [1500, 2300, 1800, 2100, 1900];
$stats = Statistics::formatReport($salesData);
print_r($stats);

echo Statistics::calculateGrowthRate(1000, 1500); // "50%"

Best Practices

1. Use format() for Display

// ✅ Good - display as readable string
echo "Total: $" . Number::format($total);

// ❌ Bad - display raw number
echo "Total: $$total"; // $1234567 (hard to read)

2. Use division() When Zero is Possible

// ✅ Good - prevents division by zero
$average = Number::division($total, $count);

// ❌ Dangerous - may cause error
$average = $count > 0 ? $total / $count : 0; // verbose and complex

3. Generate Document Numbers with printf()

// ✅ Good - consistent format with printf()
$docNo = Number::printf("INV%YY%M%D-%05d", $id);

// ❌ Bad - manual generation (complex and error-prone)
$y = date('Y') + 543;
$docNo = "INV{$y}" . date('md') . "-" . str_pad($id, 5, '0', STR_PAD_LEFT);

4. Format Before or After Storage?

// ✅ Good - store as number, format only for display
$db->insert('orders', ['amount' => 1234.56]);
echo Number::format($order->amount); // "1,234.56"

// ❌ Bad - store formatted string
$db->insert('orders', ['amount' => Number::format(1234.56)]); // "1,234.56"
// Problem: cannot calculate with string

Important Considerations

[!WARNING]
format() does NOT round: This method only adds separators, it doesn't round decimals. Use round() first if needed

[!NOTE]
division() returns 0: When dividing by zero, returns 0 (not null). Be careful when distinguishing "no data" from "result is zero"

[!IMPORTANT]
printf() uses current date: Date placeholders use date(), so they reflect the time when the method is called, not a custom date

[!TIP]
Performance: All methods are static and can be called directly without creating an instance

Summary

The Number class has 3 main methods:

  1. format() - Format numbers with thousands separators (preserves decimals)
  2. division() - Safe division (returns 0 if divisor is 0)
  3. printf() - Generate document numbers with date patterns (supports Buddhist/Gregorian years)

Perfect for:

  • Displaying numbers in readable format
  • Calculations that need to prevent division by zero
  • Generating automatic document numbers with date patterns