@@ -448,12 +448,14 @@ extension Decimal: DecimalFormattable {
448448
449449 @inlinable public func format(
450450 power: Int ,
451+ stride: Int ? = nil ,
451452 places: Int ? = nil ,
452453 signed: Bool = false ,
453454 suffix: String = " "
454455 ) -> String {
455456 let shifted : Self = . init( units: self . units, power: self . power + Int64. init ( power) )
456457 return shifted. format (
458+ stride: stride,
457459 places: places ?? - Int. init ( min ( shifted. power, 0 ) ) ,
458460 signed: signed,
459461 suffix: suffix
@@ -465,6 +467,7 @@ extension Decimal: CustomStringConvertible {
465467 /// using ASCII characters only. The string contains no leading plus sign.
466468 public var description : String {
467469 self . format (
470+ stride: nil ,
468471 places: - Int. init ( min ( self . power, 0 ) ) ,
469472 signed: false ,
470473 suffix: " " ,
@@ -494,11 +497,11 @@ extension Decimal: LosslessStringConvertible {
494497 }
495498}
496499extension Decimal {
497- public func format( places: Int , signed: Bool = false , suffix: String = " " ) -> String {
498- self . format ( places: places, signed: signed, suffix: suffix, ascii: false )
500+ public func format( stride : Int ? = nil , places: Int , signed: Bool = false , suffix: String = " " ) -> String {
501+ self . format ( stride : stride , places: places, signed: signed, suffix: suffix, ascii: false )
499502 }
500503
501- private func format( places: Int , signed: Bool , suffix: String , ascii: Bool ) -> String {
504+ private func format( stride : Int ? , places: Int , signed: Bool , suffix: String , ascii: Bool ) -> String {
502505 /// We test this before we perform any rounding, to preserve the sign.
503506 let negative : Bool = self . units < 0
504507 let positive : Bool = self . units > 0
@@ -537,6 +540,25 @@ extension Decimal {
537540 zeroes. after = self . units == 0 ? places : places + Int. init ( self . power)
538541 }
539542
543+ let digitsInFirstGroup : Int
544+ let digitsToGroup : Int
545+ let commas : Int
546+
547+ if zeroes. before > 0 {
548+ digitsToGroup = 1 // The leading '0' in "0.xxxxx"
549+ } else {
550+ digitsToGroup = digits + zeroes. after - places
551+ }
552+
553+ if let stride: Int = stride, 1 ..< digitsToGroup ~= stride {
554+ let r : Int
555+ ( commas, r) = ( digitsToGroup - 1 ) . quotientAndRemainder ( dividingBy: stride)
556+ digitsInFirstGroup = r + 1
557+ } else {
558+ commas = 0
559+ digitsInFirstGroup = digitsToGroup
560+ }
561+
540562 /// Add 1 for the decimal point. The unicode minus sign (U+2212) takes three bytes
541563 /// to encode in UTF-8.
542564 let punctuation : Int
@@ -553,7 +575,7 @@ extension Decimal {
553575 punctuation = places > 0 ? 1 : 0
554576 }
555577
556- let characters : Int = punctuation + zeroes. before + digits + zeroes. after
578+ let characters : Int = punctuation + zeroes. before + digits + zeroes. after + commas
557579 return . init( unsafeUninitializedCapacity: characters + suffix. utf8. count) {
558580 var i : Int
559581 if negative {
@@ -572,6 +594,10 @@ extension Decimal {
572594 } else {
573595 i = 0
574596 }
597+
598+ var digitsGrouped : Int = 0
599+ var digitsInCurrentGroup : Int = digitsInFirstGroup
600+
575601 // We would only insert zeroes before, if if the decimal starts with `0.`
576602 if zeroes. before > 0 {
577603 $0 [ i] = 0x30 ; i += 1 // '0'
@@ -586,24 +612,47 @@ extension Decimal {
586612 $0 [ i] = 0x30 ; i += 1
587613 }
588614 } else if places > zeroes. after {
589- // We know that the decimal point appears within the digits.
590- let period : Int = characters - places - 1
591615 for utf8 : UInt8 in string. utf8 {
592- if period == i {
616+ $0 [ i] = utf8 ; i += 1
617+ digitsGrouped += 1
618+
619+ if digitsGrouped < digitsToGroup {
620+ digitsInCurrentGroup -= 1
621+ if let stride: Int , digitsInCurrentGroup == 0 {
622+ $0 [ i] = 0x2C ; i += 1 // ','
623+ digitsInCurrentGroup = stride
624+ }
625+ } else if places > 0 ,
626+ digitsGrouped == digitsToGroup {
593627 $0 [ i] = 0x2E ; i += 1 // '.'
594628 }
595-
596- $0 [ i] = utf8 ; i += 1
597629 }
598630 for _ : Int in 0 ..< zeroes. after {
599631 $0 [ i] = 0x30 ; i += 1
600632 }
601633 } else {
634+ // Decimal point appears at the end or beyond the digits.
602635 for utf8 : UInt8 in string. utf8 {
603636 $0 [ i] = utf8 ; i += 1
637+ digitsGrouped += 1
638+ if digitsGrouped < digitsToGroup {
639+ digitsInCurrentGroup -= 1
640+ if let stride: Int , digitsInCurrentGroup == 0 {
641+ $0 [ i] = 0x2C ; i += 1 // ','
642+ digitsInCurrentGroup = stride
643+ }
644+ }
604645 }
605646 for _ : Int in 0 ..< zeroes. after - places {
606647 $0 [ i] = 0x30 ; i += 1
648+ digitsGrouped += 1
649+ if digitsGrouped < digitsToGroup {
650+ digitsInCurrentGroup -= 1
651+ if let stride: Int , digitsInCurrentGroup == 0 {
652+ $0 [ i] = 0x2C ; i += 1 // ','
653+ digitsInCurrentGroup = stride
654+ }
655+ }
607656 }
608657 if places > 0 {
609658 $0 [ i] = 0x2E ; i += 1 // '.'
0 commit comments