# Allow users to format values in a Matrix struct

**URL:** <https://forums.swift.org/t/allow-users-to-format-values-in-a-matrix-struct/78261>\
**Category:** Using Swift\
**Tags:** string, numbers, formatstyle\
**Created:** [March 4, 2025, 12:42am UTC](https://forums.swift.org/t/allow-users-to-format-values-in-a-matrix-struct/78261 "2025-03-04T00:42:15Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![wigging](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wigging/32/34513_2.png) [@wigging](https://forums.swift.org/u/wigging)\
**Post date:** [March 4, 2025, 12:42am UTC](https://forums.swift.org/t/allow-users-to-format-values-in-a-matrix-struct/78261/1 "2025-03-04T00:42:15Z")

</div>

I have a `Matrix` structure as shown below for working with two-dimensional numerical data. The underlying numeric values of the matrix are stored as a flat array.

```swift
struct Matrix<T> {

    let rows: Int
    let columns: Int
    var values: [T]

    subscript(row: Int, column: Int) -> T {
        get { return self.values[(row * self.columns) + column] }
        set { self.values[(row * self.columns) + column] = newValue }
    }
}

```

I extend the `Matrix` as shown here to print the values as a grid of numbers. If the matrix is only one row, then it is printed as a row vector; otherwise, the the max integer and fraction width (length) for each column is determined based on the values in the column. The integer and fraction lengths are determined by the `NumberString` protocol for Int, Float, and Double values. The integer and fraction lengths are used to pad the numbers so they are properly aligned in each column. Parentheses are added to each row for a prettier mathmatical display of the matrix.

```swift
extension Matrix: CustomStringConvertible where T: NumberString {

    var description: String {
        var desc = ""

        // Matrix is only one row so print as a row vector
        if self.rows == 1 {
            desc += "( "
            desc += self.values.map { "\($0)" }.joined(separator: " ")
            desc += " )"
            return desc
        }

        // Max integer width and fraction width for each matrix column
        var maxIntWidth = Array(repeating: 0, count: self.columns)
        var maxFracWidth = Array(repeating: 0, count: self.columns)

        for i in 0..<self.rows {
            for j in 0..<self.columns {
                maxIntWidth[j] = max(maxIntWidth[j], self[i, j].integerLength)
                maxFracWidth[j] = max(maxFracWidth[j], self[i, j].fractionLength)
            }
        }

        // Matrix values as strings with padding
        for i in 0..<self.rows {
            switch i {
            case 0:
                desc += "⎛ "
            case self.rows - 1:
                desc += "⎝ "
            default:
                desc += "⎜ "
            }

            for j in 0..<self.columns {
                let leftPad = String(repeating: " ", count: maxIntWidth[j] - self[i, j].integerLength)
                let rightPad = String(repeating: " ", count: maxFracWidth[j] - self[i, j].fractionLength)
                let valDesc = leftPad + self[i, j].numberDescription + rightPad

                if j != self.columns - 1 {
                    desc += valDesc + " "
                } else {
                    desc += valDesc
                }
            }

            switch i {
            case 0:
                desc += " ⎞\n"
            case self.rows - 1:
                desc += " ⎠"
            default:
                desc += " ⎟\n"
            }
        }

        return desc
    }
}

```

```swift
import Foundation

protocol NumberString {
    var numberDescription: String { get }
    var integerLength: Int { get }
    var fractionLength: Int { get }
}

extension Int: NumberString {
    var numberDescription: String {
        String(self)
    }

    var integerLength: Int {
        String(self).count
    }

    var fractionLength: Int {
        0
    }
}

extension Float: NumberString {
    var numberDescription: String {
        self.formatted(.number.precision(.fractionLength(1...4)))
    }

    var integerLength: Int {
        self.numberDescription.split(separator: ".")[0].count
    }

    var fractionLength: Int {
        self.numberDescription.split(separator: ".")[1].count
    }
}

extension Double: NumberString {
    var numberDescription: String {
        self.formatted(.number.precision(.fractionLength(1...6)))
    }

    var integerLength: Int {
        self.numberDescription.split(separator: ".")[0].count
    }

    var fractionLength: Int {
        self.numberDescription.split(separator: ".")[1].count
    }
}

```

Here is an example of printing a matrix of integers:

```swift
func runExample3() {
    let array2D = [2, 1, 892,
                   4, 5, 3]

    let matrix = Matrix(rows: 2, columns: 3, values: array2D)
    print("\nExample 3")
    print(matrix)
}

runExample3()

```

```plaintext
Example 3
⎛ 2 1 892 ⎞
⎝ 4 5 3 ⎠

```

Here is an example of printing a matrix of doubles:

```swift
func runExample5() {
    let array2D: [Float] = [2.5, 1, 8.235,
                            0.409, 23.5, 3,
                            19, 0.02, 1,
                            201, 9, 1902.3]

    let matrix = Matrix(rows: 4, columns: 3, values: array2D)
    print("\nExample 5")
    print(matrix)
}

runExample5()

```

```plaintext
Example 5
⎛ 2.5 1.0 8.235 ⎞
⎜ 0.409 23.5 3.0 ⎟
⎜ 19.0 0.02 1.0 ⎟
⎝ 201.0 9.0 1,902.3 ⎠

```

This works fine but the current method does not provide a way for the user to define the number formatting. The formatting is coded into the `NumberString` protocol for each value type. Ideally, I would like to provide a format style to the matrix which is then used for printing. Something like this where the values are printed using scientific notation:

```swift
let matrix = Matrix(rows: 4, columns: 3, values: array2D)
matrix.formatted(.number.notation(.scientific))

print(matrix)

```

Any suggestions on how this could be accomplished?

---

<div class="post-metadata">

**Author:** ![wigging](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wigging/32/34513_2.png) [@wigging](https://forums.swift.org/u/wigging)\
**Post date:** [March 4, 2025, 4:03am UTC](https://forums.swift.org/t/allow-users-to-format-values-in-a-matrix-struct/78261/2 "2025-03-04T04:03:04Z")

</div>

Here is a possible solution that uses the [FormatStyle](https://developer.apple.com/documentation/foundation/formatstyle) protocol to format the values in the matrix. This is applied with the `formatted(_:)` method shown below.

```swift
import Foundation

extension Matrix {

    func formatted<F: FormatStyle>(_ format: F) -> String where F.FormatInput == T, F.FormatOutput == String {
        var desc = ""

        // Matrix is only one row so print as a row vector
        if self.rows == 1 {
            desc += "( "
            desc += values.map { format.format($0) }.joined(separator: " ")
            desc += " )"
            return desc
        }

        // Max integer width and fraction width for each matrix column
        var maxIntWidth = Array(repeating: 0, count: self.columns)
        var maxFracWidth = Array(repeating: 0, count: self.columns)

        for i in 0..<rows {
            for j in 0..<columns {
                let value = format.format(self[i, j])
                let intLength = value.split(separator: ".")[0].count
                var fracLength = 0
                if value.contains(".") { fracLength = value.split(separator: ".")[1].count }

                maxIntWidth[j] = max(maxIntWidth[j], intLength)
                maxFracWidth[j] = max(maxFracWidth[j], fracLength)
            }
        }

        for i in 0..<rows {
            switch i {
            case 0:
                desc += "⎛ "
            case rows - 1:
                desc += "⎝ "
            default:
                desc += "⎜ "
            }

            for j in 0..<columns {
                let value = format.format(self[i, j])
                let intLength = value.split(separator: ".")[0].count
                var fracLength = 0
                if value.contains(".") { fracLength = value.split(separator: ".")[1].count }

                let leftPad = String(repeating: " ", count: maxIntWidth[j] - intLength)
                let rightPad = String(repeating: " ", count: maxFracWidth[j] - fracLength)
                let valueDesc = leftPad + value + rightPad

                if j != columns - 1 {
                    desc += valueDesc + " "
                } else {
                    desc += valueDesc
                }
            }

            switch i {
            case 0:
                desc += " ⎞\n"
            case rows - 1:
                desc += " ⎠"
            default:
                desc += " ⎟\n"
            }
        }

        return desc
    }
}

```

This allows the user to specify the number format using various numeric styles. Here the matrix is formatted using scientific notation and another is formatted with a fraction length from 1 to 4.

```swift
let array2D: [Float] = [2.5, 1, 8.235,
                        0.409, 23.5, 3,
                        19, 0.02, 1,
                        201, 9, 1902.3]

let matrix = Matrix(rows: 4, columns: 3, values: array2D)

let mat = matrix.formatted(.number.precision(.fractionLength(2)).notation(.scientific))
print(mat)

let matt = matrix.formatted(.number.precision(.fractionLength(1...4)))
print(matt)

```

```plaintext
⎛ 2.50E0 1.00E0 8.23E0 ⎞
⎜ 4.09E-1 2.35E1 3.00E0 ⎟
⎜ 1.90E1 2.00E-2 1.00E0 ⎟
⎝ 2.01E2 9.00E0 1.90E3 ⎠

⎛ 2.5 1.0 8.235 ⎞
⎜ 0.409 23.5 3.0 ⎟
⎜ 19.0 0.02 1.0 ⎟
⎝ 201.0 9.0 1,902.3 ⎠

```

The only thing I don't like about this approach is that an extra step is needed before printing the matrix. You have to create the formatted string representation then print that string. It would be cleaner if I could do something like `print(matrix, precision: 2)` where I pass in the format style as part of the print statement.

---

<div class="post-metadata">

**Author:** ![Pippin](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/pippin/32/24826_2.png) [@Pippin](https://forums.swift.org/u/Pippin)\
**Post date:** [March 4, 2025, 4:10am UTC](https://forums.swift.org/t/allow-users-to-format-values-in-a-matrix-struct/78261/3 "2025-03-04T04:10:39Z")

</div>

Just extend `String.StringInterpolation`:

```swift
extension String.StringInterpolation {
    
    mutating func appendInterpolation(_ matrix: Matrix, precision: Int) {
        appendInterpolation(/* Matrix string value */)
    }
}

let matrix = Matrix(...)
print("\(matrix, precision: 2)")

```

Or, I don't necessarily see anything against introducing a global `func print(_ matrix: Matrix, precision: Int, ...)` since it doesn't conflict with stdlib `print` /shrug

---

<div class="post-metadata">

**Author:** ![wigging](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wigging/32/34513_2.png) [@wigging](https://forums.swift.org/u/wigging)\
**Post date:** [March 4, 2025, 2:11pm UTC](https://forums.swift.org/t/allow-users-to-format-values-in-a-matrix-struct/78261/4 "2025-03-04T14:11:47Z")

</div>

NumPy has a [numpy.set\_printoptions](https://numpy.org/doc/stable/reference/generated/numpy.set_printoptions.html) function that determines how floating point numbers are displayed in arrays. This applies globally to all NumPy objects so you define the format once and all arrays are printed with that format. How would I accomplish this in Swift for my matrix example? Would I define a global `PrintOptions` struct as a singleton which would be used for all formatting operations?

---

<div class="post-metadata">

**Author:** ![j-f1](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/j-f1/32/12287_2.png) [@j-f1](https://forums.swift.org/u/j-f1)\
**Post date:** [March 4, 2025, 6:57pm UTC](https://forums.swift.org/t/allow-users-to-format-values-in-a-matrix-struct/78261/5 "2025-03-04T18:57:14Z")

</div>

You could define a `MatrixFormatStyle` struct that is generic over another `FormatStyle` for formatting individual values. With such an API, the call site could look like `matrix.formatted(.matrix(.number.precision(.fractionLength(2))))`

```swift
struct MatrixFormatStyle<Base: FormatStyle> where ValueFormat.FormatOutput == String {
    let style: Base
    init(_ base: Base) { self.style = style }

    func format(_ value: Matrix<Base.FormatInput>) -> String { ... }
}

extension FormatStyle {
    static func matrix<Base: FormatStyle>(_ base: Base) -> Self where Base.FormatOutput == String, Self == MatrixFormatStyle<Base> {
        MatrixFormatStyle(base)
    }
}

```

---

<div class="post-metadata">

**Author:** ![wigging](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wigging/32/34513_2.png) [@wigging](https://forums.swift.org/u/wigging)\
**Post date:** [March 5, 2025, 3:08am UTC](https://forums.swift.org/t/allow-users-to-format-values-in-a-matrix-struct/78261/6 "2025-03-05T03:08:19Z")

</div>

The code I shared above allows me to do:

```swift
matrix.formatted(.number.precision(.fractionLength(2)))

```

So I don't understand why I would want to do the more verbose approach that you suggest:

```swift
matrix.formatted(.matrix(.number.precision(.fractionLength(2))))

```

---

<div class="post-metadata">

**Author:** ![scanon](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/scanon/32/2445_2.png) [@scanon](https://forums.swift.org/u/scanon)\
**Post date:** [March 5, 2025, 3:17am UTC](https://forums.swift.org/t/allow-users-to-format-values-in-a-matrix-struct/78261/7 "2025-03-05T03:17:36Z")

</div>

> [@wigging](#):
>
> NumPy has a [numpy.set\_printoptions](https://numpy.org/doc/stable/reference/generated/numpy.set_printoptions.html) function that determines how floating point numbers are displayed in arrays. This applies globally to all NumPy objects so you define the format once and all arrays are printed with that format. How would I accomplish this in Swift for my matrix example? Would I define a global `PrintOptions` struct as a singleton which would be used for all formatting operations?

Stateful I/O formatting is one of those patterns that's great for toy examples, but almost never what you want in reality, because it makes building composable software pieces a giant pain (you have to keep specifying the stateful I/O anyway, because otherwise some other component will stomp on it and ruin your output, but you're using an API that assumes you don't have to do that, and so is not optimized to make it nice).

Generally what you'd do with a formatter pattern is create the formatter once, and then use it repeatedly, so your example actually becomes `matrix.formatted(myFormat)` at the point of use with either spelling.

---

<div class="post-metadata">

**Author:** ![wigging](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wigging/32/34513_2.png) [@wigging](https://forums.swift.org/u/wigging)\
**Post date:** [March 5, 2025, 3:52am UTC](https://forums.swift.org/t/allow-users-to-format-values-in-a-matrix-struct/78261/8 "2025-03-05T03:52:18Z")

</div>

Agreed, defining the format style and then using it at multiple places to print matrices (see below) is basically the same approach as having a global print option as in NumPy. Either way I am defining a format in one location then using it at other locations.

```swift
let arrayA = [2.5, 1, 8.235097,
              0.45, 23.58, 3]

let arrayB = [2.5, 1, 8.235,
              0.409, 23.5, 3,
              19, 0.02, 1,
              201, 9, 1902.3]

let matrixA = Matrix(rows: 2, columns: 3, values: arrayA)
let matrixB = Matrix(rows: 4, columns: 3, values: arrayB)

let style = FloatingPointFormatStyle<Double>().precision(.fractionLength(2))

print(matrixA.formatted(style))
print(matrixB.formatted(style))

```

---

<div class="post-metadata">

**Author:** ![j-f1](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/j-f1/32/12287_2.png) [@j-f1](https://forums.swift.org/u/j-f1)\
**Post date:** [March 5, 2025, 1:44pm UTC](https://forums.swift.org/t/allow-users-to-format-values-in-a-matrix-struct/78261/10 "2025-03-05T13:44:55Z")

</div>

The advantage to creating a full implementation of `FormatStyle` is that you can then use it in other places that accept a format style, like SwiftUI’s localized string literals. It also keeps the convention that `formatted(_:)` takes a format style as a parameter that uses `self` as its input and not some other type.

---

<div class="post-metadata">

**Author:** ![wigging](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wigging/32/34513_2.png) [@wigging](https://forums.swift.org/u/wigging)\
**Post date:** [March 6, 2025, 3:03pm UTC](https://forums.swift.org/t/allow-users-to-format-values-in-a-matrix-struct/78261/11 "2025-03-06T15:03:21Z")

</div>

Here is what I currently have to format the underlying values in the matrix.

```swift
import Foundation

struct Matrix<T> {

    let rows: Int
    let columns: Int
    var values: [T]

    subscript(row: Int, column: Int) -> T {
        get { values[row * columns + column] }
        set { values[row * columns + column] = newValue }
    }
}

```

```swift
extension Matrix {

    func formatted<F: FormatStyle>(_ format: F) -> String where F.FormatInput == T, F.FormatOutput == String {
        var desc = ""

        // Matrix is only one row so print as a row vector
        if self.rows == 1 {
            desc += "( "
            desc += values.map { format.format($0) }.joined(separator: " ")
            desc += " )"
            return desc
        }

        // Max integer width and fraction width for each matrix column
        var maxIntWidth = Array(repeating: 0, count: self.columns)
        var maxFracWidth = Array(repeating: 0, count: self.columns)

        for i in 0..<rows {
            for j in 0..<columns {
                let value = format.format(self[i, j])
                var intLength = value.count
                var fracLength = 0

                if value.contains(".") {
                    intLength = value.split(separator: ".")[0].count
                    fracLength = value.split(separator: ".")[1].count
                }

                maxIntWidth[j] = max(maxIntWidth[j], intLength)
                maxFracWidth[j] = max(maxFracWidth[j], fracLength)
            }
        }

        // Matrix values as strings with padding
        for i in 0..<rows {
            switch i {
            case 0:
                desc += "⎛ "
            case rows - 1:
                desc += "⎝ "
            default:
                desc += "⎜ "
            }

            for j in 0..<columns {
                let value = format.format(self[i, j])
                var intLength = value.count
                var fracLength = 0

                if value.contains(".") {
                    intLength = value.split(separator: ".")[0].count
                    fracLength = value.split(separator: ".")[1].count
                }

                let leftPad = String(repeating: " ", count: maxIntWidth[j] - intLength)
                let rightPad = String(repeating: " ", count: maxFracWidth[j] - fracLength)
                let valueDesc = leftPad + value + rightPad

                if j != columns - 1 {
                    desc += valueDesc + " "
                } else {
                    desc += valueDesc
                }
            }

            switch i {
            case 0:
                desc += " ⎞\n"
            case rows - 1:
                desc += " ⎠"
            default:
                desc += " ⎟\n"
            }
        }

        return desc
    }
}

```

This mostly works except the formatter is ignoring the matrix type and appears to be using the literal type for the underlying values. For example, if I print the matrix shown below, which has a type of `Matrix<Double>`, using the string description of each value I get the results shown in the output text below. Notice the -2, 1, 18 are correctly printed as -2.0, 1.0, and 18.0 because the matrix itself has a type of double.

```swift
let arrayB = [-2, 1, 18,
              4.5, 0, -300,
              18, 901, 7.01]

let matrixB = Matrix(rows: 3, columns: 3, values: arrayB)

print(matrixB)

```

```plaintext
⎛ -2.0 1.0 18.0 ⎞
⎜ 4.5 0.0 -300.0 ⎟
⎝ 18.0 901.0 7.01 ⎠ 

```

But if I format the values using `.number` and print the matrix, then I get what is shown below. You can see that the -2, 1, 18 are printed as integers instead of doubles even though the matrix itself is `Matrix<Double>`. How can I tell the format style to infer the type based on the matrix type and not the underlying literal value type?

```swift
print(matrixB.formatted(.number))

```

```plaintext
⎛ -2 1 18 ⎞
⎜ 4.5 0 -300 ⎟
⎝ 18 901 7.01 ⎠ 

```

---

<div class="post-metadata">

**Author:** ![sveinhal](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/sveinhal/32/3992_2.png) [@sveinhal](https://forums.swift.org/u/sveinhal)\
**Post date:** [March 11, 2025, 9:57am UTC](https://forums.swift.org/t/allow-users-to-format-values-in-a-matrix-struct/78261/12 "2025-03-11T09:57:12Z")

</div>

> [@wigging](#):
>
> So I don't understand why I would want to do the more verbose approach that you suggest

I think you can mostly get the best of two worlds by doing this:

```swift
extension Matrix {
  struct MatrixFormatStyle<F: FormatStyle> {
    let elementFormat: F

    func format(_ matrix: Matrix<T>) -> String {
      // move your implementation here
    }

    func locale(_ locale: Locale) -> Matrix<T>.MatrixFormatStyle<F> {
      .init(elementFormat: elementFormat.locale(locale))
    }
  }
}

extension Matrix {
    func formatted<F: FormatStyle>(_ format: F) -> String where F.FormatInput == T, F.FormatOutput == String {
        MatrixFormatStyle(elementFormatter: format).format(self)
    }
}

```

Then you get the nice short syntax, but still expresses your formatting code as its own `FormatStyle` so other API that's generic over elements and formatters, can work with your matrices as well.

You should probably also try to make your code agnostic on decimal separator. I don't think there is a generic way of extracting the `locale` (and thus `locale.decimalSeparator` from any given `FormatStyle`, but it should be possible to try this to cover most cases:

```swift
let decimalSeparator = switch format {
  case let f as FloatingPointFormatStyle<T>: 
    f.locale.decimalSeparator
  case let f as Decimal.FormatStyle: 
    f.locale.decimalSeparator
  case let f as Measurement.FormatStyle: 
    f.locale.decimalSeparator
  default:
    "."
}

```
