Type Decimal

RocouRocou Membre
octobre 2021 modifié dans Objective-C, Swift, C, C++ #1

Avez-vous une astuce pour manipuler facilement des variables de type decimal?
Il y a tout de même beaucoup de fonctions ou d'objets qui ne connaissent pas ce type, ce qui implique de nombreuses conversions. Du coup, je me demande quels sont les avantages à utiliser le type decimal.

Mots clés:

Réponses

  • CéroceCéroce Membre, Modérateur

    @Rocou a dit :
    Du coup, je me demande quels sont les avantages à utiliser le type decimal.

    Pas de perte de précision, ça calcule en décimal et pas en binaire.

    Je n'ai jamais travaillé avec Decimal, alors je connais mal.
    En Swift, on peut toujours utiliser NSDecimalNumber qui est un NSNumber et utilisable par exemple dans un NSNumberFormatter.

  • J'ajouterai que théoriquement tu ne vas pas utiliser Decimal pour autre choses que des manipulations de valeurs entrées par l'utilisateur ou provenant d'un web service.

    Ce qui fait que normalement tu ne vas pas passer des Decimal à n'importe quelle function. Comme l'a dit @Céroce Decimal est toll-free bridged vers NSDecimalNumber qui dérive de NSNumber. Donc pour tout ce qui est de la question d'affichage des valeurs tu n'as pas à t'inquiéter non plus. NSControl et NSNumberFormatter aiment bien NSNumber.

    Je serai tenté alors de suggérer que si tu es si ennuyé par des conversions intempestives c'est peut-être que tu utilise mal Decimal.

    Dis nous-en plus sur ces conversions.

  • C'est dans le cadre de ma petite application "Pourcentage calc":
    1. l'utilisateur saisit des nombres,
    2. ils sont récupérés sous forme de texte,
    3. puis convertis en décimal,
    4. les calculs se font
    5. les résultats (au type decimal) sont convertis en texte
    6. le texte est affiché

  • @Rocou a dit :
    C'est dans le cadre de ma petite application "Pourcentage calc":
    1. l'utilisateur saisit des nombres,
    2. ils sont récupérés sous forme de texte,
    3. puis convertis en décimal,
    4. les calculs se font
    5. les résultats (au type decimal) sont convertis en texte
    6. le texte est affiché

    NumberFormatter a une propriété generatesDecimalNumbers qui te fournira un NSNumber bridgeable vers Decimal. Comme suit :

    let nf = NumberFormatter()
    
    nf.generatesDecimalNumbers = true
    let decimal = nf.number(from: "1.234") as? Decimal // Decimal 1.234
    
    nf.generatesDecimalNumbers = false
    let perhapsNotDecimal = nf.number(from: "5.678") as? Decimal // nil
    

    Après comme je t'ai dit si tu utilise les NSBindings c'est encore plus simple et automatique.

  • let decimal = nf.number(from: "1.234") as? Decimal // Decimal 1.234
    

    Le NumberFormatter gère automatiquement la localisation du symbole décimal ? Ou est-ce que l'exemple ne fonctionne qu'en dialecte British ?

  • @Draken a dit :

    let decimal = nf.number(from: "1.234") as? Decimal // Decimal 1.234
    

    Le NumberFormatter gère automatiquement la localisation du symbole décimal ? Ou est-ce que l'exemple ne fonctionne qu'en dialecte British ?

    Effectivement NumberFormatter gère la localisation. Toujours Locale.current par défaut. Chez moi c'est en_CA@currency=EUR pour des raisons diverses (j'avoue être étonné mais si c'est le cas c'est qu'il y a une raison, que j'ai oublié bien entendu...).

    C'est vrai qu'avec une locale fr_FR il faut plutôt écrire

    let decimal = nf.number(from: "1,234") as? Decimal // Decimal 1.234
    
  • @Pyroh a dit :blush:

    let nf = NumberFormatter()
    
    nf.generatesDecimalNumbers = true
    let decimal = nf.number(from: "1.234") as? Decimal // Decimal 1.234
    
    nf.generatesDecimalNumbers = false
    let perhapsNotDecimal = nf.number(from: "5.678") as? Decimal // nil
    

    J'ai essayé un truc comme ça:
    let tauxInteret_dec = nf.number(from: tauxInteret_remb.stringValue) as? Decimal

    Mais dans tauxInteret_dec j'ai "nil" alors que dans tauxInteret_remb.stringValue j'ai bien la valeur attendue.

    Après comme je t'ai dit si tu utilise les NSBindings c'est encore plus simple et automatique.

    Pour l'instant, je n'ai rien compris au Bindings. Je creuserai mais pour le moment, je fais l'impasse.

  • Il faut appliquer le formatter sur le NSTextField. AppKit se charge du reste pour toi:

    Note que comme la locale est "fr_FR" (ce qui doit être le cas sur ta machine) le séparateur décimal est la virgule.

  • @Pyroh a dit :
    Il faut appliquer le formatter sur le NSTextField. AppKit se charge du reste pour toi:

    Note que comme la locale est "fr_FR" (ce qui doit être le cas sur ta machine) le séparateur décimal est la virgule.

    Bien vérifier que la locale est "fr_FR" comme l'a dit Pyroh
    Chez moi, j'ai "en_FR" je sais pas comment il est arrivé a faire ce mix ! :)

  • C'est bien compliqué tout ceci :/

  • @Rocou a dit :
    C'est bien compliqué tout ceci :/

    Ouais la conception d'applications c'est compliqué quand on creuse un peu. 😉

    J'ai mis à jour mon exemple sur les bindings sur GitHub. Et j'ai rajouté un petit truc en plus pour toi.

  • @Pyroh a dit :

    Ouais la conception d'applications c'est compliqué quand on creuse un peu. 😉

    Même pas vrai ! Il y a des cours sur Internet pour apprendre à créer des applications en 3 semaines ! 😓

  • @Draken a dit :
    Même pas vrai ! Il y a des cours sur Internet pour apprendre à créer des applications en 3 semaines ! 😓

    T'as déjà entendu parler des crèmes amaigrissantes 😁 ?

  • @Pyroh un grand merci pour le "petit truc en plus pour moi"! :)
    J'essaie de comprendre les liens entre le code et les objets du storyboard cependant je n'arrive pas à "connecter les "bindings". Le bouton "connect" est grisé, comme le montre la photo qui suit:

  • @Rocou a dit :
    @Pyroh un grand merci pour le "petit truc en plus pour moi"! :)

    Mais de rien !

    J'essaie de comprendre les liens entre le code et les objets du storyboard cependant je n'arrive pas à "connecter les "bindings". Le bouton "connect" est grisé, comme le montre la photo qui suit:

    Oublie ça pour les bindings. De mémoire ça a fonctionné bizarrement il y a de ça bien des lunes mais ça ne fonctionne plus. Ou si ça fonctionne c'est pas intuitif. Joue la sécurité :

    Note que l'architecture est inutilement compliquée on pourrait très bien fusionner CalculusEngine avec CalculusViewController mais utiliser un NSObjectController c'est plus propre et best-practice.

    Sinon pour rigoler j'ai répliqué cette partie en SwiftUI (release 3, macOS12 mini) :

    struct Calculator: View {
        struct Section: View {
            static var resultFormatter: NumberFormatter {
                let nf = NumberFormatter()
                nf.maximumFractionDigits = 3
                return nf
            }
    
            let label: LocalizedStringKey
            let sign: String
            let function: (Decimal, Decimal) -> Decimal
    
            @State private var left: Decimal = .zero
            @State private var right: Decimal = .zero
    
            private var result: Decimal { function(left, right) }
    
            var body: some View {
                GroupBox(label) {
                    HStack(alignment: .firstTextBaseline) {
                        TextField("left", value: $left, format: .number, prompt: Text(verbatim: "0")).frame(width: 90)
                        Text(sign).frame(width: 10)
                        TextField("right", value: $right, format: .number, prompt: Text(verbatim: "0")).frame(width: 90)
                        Text(verbatim: "=")
                        Text(verbatim: "\(Self.resultFormatter.string(for: result) ?? "0")").frame(width: 90, alignment: .trailing)
                    }.multilineTextAlignment(.trailing).padding(12)
                }
            }
        }
    
        var body: some View {
            VStack {
                Section(label: "Addition", sign: "+", function: +)
                Section(label: "Substraction", sign: "-", function: -)
                Section(label: "Multiplication", sign: "×", function: *)
                Section(label: "Division", sign: "÷", function: /)
            }
            .textFieldStyle(.roundedBorder)
            .frame(width: 500, height: 380)
        }
    }
    
  • Bon, je n'arrive pas à compiler.
    J'ai repris un bout de ton code (celui concernant la fonction add() ), j'ai créé une vue, installé trois NSTextField et scrupuleusement repris tous tes réglages mais chez moi cela donne une erreur à la compilation:

    libc++abi: terminating with uncaught exception of type NSException
    *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key totalBTC.'
    terminating with uncaught exception of type NSException

    "totalBTC", c'est un de mes TextField. Si tu as une idée de ce qui peut se passer, je suis preneur!

  • Ce n'est pas une erreur de runtime ça?
    Y'a un truc qui croit être un de tes TextField, mais qui est en réalité un NSObject, et donc qui ne connait pas totalBTC.

  • Je n'arrive pas à voir où est le souci. Pourtant, il y a trois fois rien dans le code.
    Il y a cependant des choses que je ne comprends pas que j'ai tout simplement recopiées. J'appréhende le truc peu à peu mais quand il y a un souci à la compilation, je n'avance plus du tout.

  • Alors ton code compile très bien rassure-toi. Si tu regarde le message d'erreur tu vois que c'est une exception qui est levée (il faut que le code compile et tourne pour ça).
    Exception qui pour le coup t'informe du soucis : this class is not key value coding-compliant for the key totalBTC.

    La réponse la plus simple à ton soucis est que tu as bound (bind au passé, j'aime pas "bindé") ton NSTextField à la propriété totalBTC d'un objet. Lequel ne comportant malheureusement pas la propriété sus-nommée.

    Le binding fonctionne de cette manière: tu bind un contrôle à la propriété d'un object. Tu peux le faire via un NSObjectController ou une de ses sous-classe mais c'est pas obligatoire (sauf en cas d'Array mais c'est le prochain cours 😉).
    Quand tu crée le binding depuis un contrôle tu "asservi" une des ses propriétés à la propriété d'un autre objet. Cocoa s'amuse ensuite à suivre les changements des deux objets afin que la valeur des propriétés bound respectives de ces derniers soient toujours égales (mais pas identiques si c'est des classes, merci NSCopying). Pour ça il utilise des méthodes genre value(forKey:) ou setValue(_:forKey:) ou plus généralement tout l'attirail KVO/KVC.

    On va prendre un cas qu'on connait tous les deux qui est celui du code que j'ai mis sur GitHub parce qu'on a tous les deux accès au code.

    Si tu prends le textfield de gauche dans la section Addition et regarde ses bindings on voit que:

    • la propriété bound est Value qui corresponde à la clé NSBindingName.value commune à tous les NSControls
    • l'autre objet du binding est *Object Controller`
    • comme c'est un object controller le champs Controller Key est égal à selection, c'est (presque) toujours le cas.
    • Model Key Path est égal au nom de la propriété qu'on veut bind sur l'objet modèle, dans ce cas c'est addLeft.
    • le reste des champs et options est anecdotique ici mais je pense que tu dois comprendre les 3 derniers.

    Là concrètement on a lié la valeur du textfield à la valeur de la propriété addLeft de l'instance de CalculusEngine qui est liée à l'object controller. De la même manière le text field du milieu est bound à la propriété addRight du même objet et celui tout à droite à addResult.

    Quand la valeur du textfield change elle est validé par le number formatter et transmise via binding à la propriété addLeft de l'instance CalculusEngine en passant pas l'object controller. L'instance CalculusEngine sait que si addLeft change alors addResult aussi (grâce à keyPathsForValuesAffectingValue(forKey key: String) -> Set<String>) alors, via binding, la valeur de addResult est envoyée à la propriété value et le textfield affiche la valeur transformée par le number formatter qui lui est attaché.

    À aucun moment il ne faut utiliser un @IBOutlet pour les bindings.

    PS: Oui je sais que Calculus c'est pas ça en anglais.

  • Meric @Pyroh ,
    Bon j'ai résolu le problème de "compilation", tout semble fonctionner normalement sauf un truc qui correspond à la fin de tes explications:
    la fonction de calcul retourne un résultat qui devrait se retrouver dans la zone de résultat (un label) mais cela ne fonctionne pas. J'ai vérifié le bon fonctionnement de la fonction, tout est ok. la frappe au clavier est bien "suivie", le calcul est correct mais le résultat ne s'affiche pas.
    Je vais creuser en relisant tes explications.

  • Bon ok, j'ai un peu honte car l'erreur venait du number formatter mal paramétré... merci @Pyroh .

    Maintenant je me pose une autre question: comment faire pour initialiser un des champs, par exemple le champ addLeft, avec une valeur?
    Encore une fois, ne vais-je pas avoir un problème de conversion vers le type décimal?

    Voici un truc que j'aimerais pouvoir faire:
    1- je récupère la valeur du cours de l'action Apple sur le net
    2- j'affiche cette valeur dans addLeft
    3- je saisis une valeur dans addRight pour le calcul...

Connectez-vous ou Inscrivez-vous pour répondre.