KiCad PCB EDA Suite
Loading...
Searching...
No Matches
spice_value.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2016 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Maciej Suminski <[email protected]>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 3
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22#include "spice_value.h"
23#include <math/util.h>
24#include <core/kicad_algo.h>
25
26#include <stdexcept>
27#include <cmath>
28
29#include <wx/textentry.h>
30#include <wx/numformatter.h>
31#include <confirm.h>
32#include <common.h>
33#include <locale_io.h>
34#include <geometry/eda_angle.h>
35
36
37void SPICE_VALUE_FORMAT::FromString( const wxString& aString )
38{
39 if( aString.IsEmpty() )
40 {
41 Precision = 3;
42 Range = wxS( "~V" );
43 }
44 else
45 {
46 long val;
47 aString.Left( 1 ).ToLong( &val );
48 Precision = static_cast<int>( val );
49 Range = aString.Right( aString.Length() - 1 );
50 }
51}
52
53
55{
56 return wxString::Format( wxS( "%d%s" ), std::clamp( Precision, 0, 9 ), Range );
57}
58
59
60void SPICE_VALUE_FORMAT::UpdateUnits( const wxString& aUnits )
61{
62 if( Range.GetChar( 0 ) == '~' )
63 Range = Range.Left( 1 ) + aUnits;
64 else if( SPICE_VALUE::ParseSIPrefix( Range.GetChar( 0 ) ) != SPICE_VALUE::PFX_NONE )
65 Range = Range.Left( 1 ) + aUnits;
66 else
67 Range = aUnits;
68}
69
70
71SPICE_VALUE::SPICE_VALUE( const wxString& aString ) :
72 m_base( 0.0 ),
74 m_spiceStr( false )
75{
76 if( aString.IsEmpty() )
77 return;
78
79 char units[8] = { 0, };
80 LOCALE_IO dummy; // Numeric values must be in "C" locale ('.' decimal separator)
81
82 sscanf( (const char*) aString.c_str(), "%lf%7s", &m_base, units );
83
84 if( *units == 0 )
85 {
86 Normalize();
87 return;
88 }
89
90 m_spiceStr = true;
91
92 for( char* bufPtr = units; *bufPtr; ++bufPtr )
93 *bufPtr = tolower( *bufPtr );
94
95 if( strcmp( units, "meg" ) == 0 )
96 {
98 }
99 else
100 {
101 switch( units[0] )
102 {
103 case 'f': m_prefix = PFX_FEMTO; break;
104 case 'p': m_prefix = PFX_PICO; break;
105 case 'n': m_prefix = PFX_NANO; break;
106 case 'u': m_prefix = PFX_MICRO; break;
107 case 'm': m_prefix = PFX_MILI; break;
108 case 'k': m_prefix = PFX_KILO; break;
109 case 'g': m_prefix = PFX_GIGA; break;
110 case 't': m_prefix = PFX_TERA; break;
111 default: m_prefix = PFX_NONE; break;
112 }
113 }
114
115 Normalize();
116}
117
118
120{
121 while( std::fabs( m_base ) >= 1000.0 )
122 {
123 if( m_prefix == PFX_TERA ) // this is the biggest unit available
124 break;
125
126 m_base *= 0.001;
127 m_prefix = (UNIT_PREFIX)( m_prefix + 3 );
128 }
129
130 while( m_base != 0.0 && std::fabs( m_base ) < 1.000 )
131 {
132 if( m_prefix == PFX_FEMTO ) // this is the smallest unit available
133 break;
134
135 m_base *= 1000.0;
136 m_prefix = (UNIT_PREFIX)( m_prefix - 3 );
137 }
138}
139
140
142{
143 switch( aPrefix )
144 {
145 case SPICE_VALUE::PFX_FEMTO: return wxT( "f" );
146 case SPICE_VALUE::PFX_PICO: return wxT( "p" );
147 case SPICE_VALUE::PFX_NANO: return wxT( "n" );
148 case SPICE_VALUE::PFX_MICRO: return wxT( "u" );
149 case SPICE_VALUE::PFX_MILI: return wxT( "m" );
150 case SPICE_VALUE::PFX_NONE: return wxEmptyString;
151 case SPICE_VALUE::PFX_KILO: return wxT( "k" );
152 case SPICE_VALUE::PFX_MEGA: return wxT( "Meg" );
153 case SPICE_VALUE::PFX_GIGA: return wxT( "G" );
154 case SPICE_VALUE::PFX_TERA: return wxT( "T" );
155 }
156
157 return wxEmptyString;
158}
159
160
162{
163 switch( aPrefix )
164 {
165 case SPICE_VALUE::PFX_FEMTO: return wxT( "f" );
166 case SPICE_VALUE::PFX_PICO: return wxT( "p" );
167 case SPICE_VALUE::PFX_NANO: return wxT( "n" );
168 case SPICE_VALUE::PFX_MICRO: return wxT( "u" );
169 case SPICE_VALUE::PFX_MILI: return wxT( "m" );
170 case SPICE_VALUE::PFX_NONE: return wxEmptyString;
171 case SPICE_VALUE::PFX_KILO: return wxT( "K" );
172 case SPICE_VALUE::PFX_MEGA: return wxT( "M" );
173 case SPICE_VALUE::PFX_GIGA: return wxT( "G" );
174 case SPICE_VALUE::PFX_TERA: return wxT( "T" );
175 }
176
177 return wxEmptyString;
178}
179
180
182{
183 switch( c )
184 {
185 case 'f': return SPICE_VALUE::PFX_FEMTO;
186 case 'p': return SPICE_VALUE::PFX_PICO;
187 case 'n': return SPICE_VALUE::PFX_NANO;
188 case 'u': return SPICE_VALUE::PFX_MICRO;
189 case 'm': return SPICE_VALUE::PFX_MILI;
190 case 'K': return SPICE_VALUE::PFX_KILO;
191 case 'M': return SPICE_VALUE::PFX_MEGA;
192 case 'G': return SPICE_VALUE::PFX_GIGA;
193 case 'T': return SPICE_VALUE::PFX_TERA;
194 default: return SPICE_VALUE::PFX_NONE;
195 }
196}
197
198
199double SPICE_VALUE::ToNormalizedDouble( wxString* aPrefix )
200{
201 Normalize();
202
203 *aPrefix = spice_prefix( m_prefix );
204 return m_base;
205}
206
207
209{
210 double res = m_base;
211
212 if( m_prefix != PFX_NONE )
213 res *= std::pow( 10, (int) m_prefix );
214
215 return res;
216}
217
218wxString SPICE_VALUE::ToString() const
219{
220 wxString res( wxString::Format( "%.3f", ToDouble() ) );
221 StripZeros( res );
222
223 return res;
224}
225
226
228{
229 wxString range( aFormat.Range );
230
231 if( range.EndsWith( wxS( "°" ) ) )
232 {
233 EDA_ANGLE angle( m_base * std::pow( 10, (int) m_prefix ), DEGREES_T );
234 angle.Normalize180();
235 return wxString::FromCDouble( angle.AsDegrees(), aFormat.Precision ) + wxS( "°" );
236 }
237
238 if( range.StartsWith( wxS( "~" ) ) )
239 {
240 Normalize();
241 range = si_prefix( m_prefix ) + range.Right( range.Length() - 1 );
242 }
243 else
244 {
245 SPICE_VALUE::UNIT_PREFIX rangePrefix = ParseSIPrefix( range[0] );
246 m_base = m_base * std::pow( 10, m_prefix - rangePrefix );
247 m_prefix = rangePrefix;
248 }
249
250 double mantissa = m_base;
251 int scale = 0;
252
253 while( std::fabs( mantissa ) >= 10.0 )
254 {
255 mantissa *= 0.1;
256 scale += 1;
257 }
258
259 while( mantissa != 0.0 && std::fabs( mantissa ) < 1.0 )
260 {
261 mantissa *= 10;
262 scale -= 1;
263 }
264
265 mantissa = KiROUND( mantissa * std::pow( 10, aFormat.Precision - 1 ) );
266 mantissa *= std::pow( 10, scale - aFormat.Precision + 1 );
267
268 wxString res = wxString::FromCDouble( mantissa, std::max( 0, aFormat.Precision - scale - 1 ) );
269
270 // If we have an excessively long number, switch to scientific notation
271 if( ssize_t( res.length() ) > aFormat.Precision + static_cast<long long>( scale ) + 1 )
272 res = wxString::FromCDouble( mantissa );
273
274 return res + range;
275}
276
277
279{
280 wxString res = wxString::FromCDouble( m_base );
281 StripZeros( res );
283
284 return res;
285}
286
287
289{
290 int prefixDiff = m_prefix - aOther.m_prefix;
292 res.m_spiceStr = m_spiceStr || aOther.m_spiceStr;
293
294 // Convert both numbers to a common prefix
295 if( prefixDiff > 0 )
296 {
297 // Switch to the aOther prefix
298 res.m_base = ( m_base * std::pow( 10, prefixDiff ) ) + aOther.m_base;
299 res.m_prefix = aOther.m_prefix;
300 }
301 else if( prefixDiff < 0 )
302 {
303 // Use the current prefix
304 res.m_base = m_base + ( aOther.m_base * std::pow( 10, -prefixDiff ) );
305 res.m_prefix = m_prefix;
306 }
307 else
308 {
309 res.m_base = m_base + aOther.m_base;
310 res.m_prefix = m_prefix; // == aOther.m_prefix
311 }
312
313 res.Normalize();
314
315 return res;
316}
317
318
320{
321 int prefixDiff = m_prefix - aOther.m_prefix;
323 res.m_spiceStr = m_spiceStr || aOther.m_spiceStr;
324
325 // Convert both numbers to a common prefix
326 if( prefixDiff > 0 )
327 {
328 // Switch to the aOther prefix
329 res.m_base = m_base * std::pow( 10, prefixDiff ) - aOther.m_base;
330 res.m_prefix = aOther.m_prefix;
331 }
332 else if( prefixDiff < 0 )
333 {
334 // Use the current prefix
335 res.m_base = m_base - aOther.m_base * std::pow( 10, -prefixDiff );
336 res.m_prefix = m_prefix;
337 }
338 else
339 {
340 res.m_base = m_base - aOther.m_base;
341 res.m_prefix = m_prefix; // == aOther.m_prefix
342 }
343
344 res.Normalize();
345
346 return res;
347}
348
349
351{
352 SPICE_VALUE res( m_base * aOther.m_base, (UNIT_PREFIX)( m_prefix + aOther.m_prefix ) );
353 res.m_spiceStr = m_spiceStr || aOther.m_spiceStr;
354 res.Normalize();
355
356 return res;
357}
358
359
361{
362 SPICE_VALUE res( m_base / aOther.m_base, (UNIT_PREFIX)( m_prefix - aOther.m_prefix ) );
363 res.m_spiceStr = m_spiceStr || aOther.m_spiceStr;
364 res.Normalize();
365
366 return res;
367}
368
369
370void SPICE_VALUE::StripZeros( wxString& aString )
371{
372 if ( aString.Find( ',' ) >= 0 || aString.Find( '.' ) >= 0 )
373 {
374 while( aString.EndsWith( '0' ) )
375 aString.RemoveLast();
376
377 if( aString.EndsWith( '.' ) || aString.EndsWith( ',' ) )
378 aString.RemoveLast();
379 }
380}
381
382
383bool SPICE_VALIDATOR::Validate( wxWindow* aParent )
384{
385 wxTextEntry* const text = GetTextEntry();
386
387 if( !text )
388 return false;
389
390 if( text->IsEmpty() )
391 {
392 if( m_emptyAllowed )
393 return true;
394
395 DisplayError( aParent, wxString::Format( _( "Please, fill required fields" ) ) );
396 return false;
397 }
398
399 wxString svalue = text->GetValue();
400
401 // In countries where the decimal separator is not a point, if the user
402 // has not used a point, replace the decimal separator by the point, as needed
403 // by spice simulator which uses the "C" decimal separator
404 svalue.Replace(",", "." );
405
406 try
407 {
408 // If SPICE_VALUE can be constructed, then it is a valid Spice value
409 SPICE_VALUE val( svalue );
410 }
411 catch( ... )
412 {
413 DisplayError( aParent, wxString::Format( _( "'%s' is not a valid SPICE value." ),
414 text->GetValue() ) );
415
416 return false;
417 }
418
419 if( svalue != text->GetValue() )
420 text->SetValue( svalue );
421
422 return true;
423}
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
double AsDegrees() const
Definition eda_angle.h:116
EDA_ANGLE Normalize180()
Definition eda_angle.h:268
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:37
bool m_emptyAllowed
< Is it valid to get an empty value?
bool Validate(wxWindow *aParent) override
Helper class to recognize Spice formatted values.
Definition spice_value.h:52
SPICE_VALUE operator+(const SPICE_VALUE &aOther) const
double m_base
SPICE_VALUE operator-(const SPICE_VALUE &aOther) const
UNIT_PREFIX m_prefix
Was the value defined using the Spice notation?
static UNIT_PREFIX ParseSIPrefix(wxChar c)
void Normalize()
Normalize the value.
double ToNormalizedDouble(wxString *aPrefix)
wxString ToString() const
Return string value as when converting double to string (e.g.
SPICE_VALUE operator/(const SPICE_VALUE &aOther) const
Remove redundant zeros from the end of a string.
wxString ToSpiceString() const
Return string value in Spice format (e.g.
SPICE_VALUE operator*(const SPICE_VALUE &aOther) const
SPICE_VALUE()
Parses the string to create a Spice value (e.g. 100n)
Definition spice_value.h:68
double ToDouble() const
static void StripZeros(wxString &aString)
The common library.
void DisplayError(wxWindow *aParent, const wxString &aText)
Display an error or warning message box with aMessage.
Definition confirm.cpp:192
This file is part of the common library.
#define _(s)
@ DEGREES_T
Definition eda_angle.h:31
wxString si_prefix(SPICE_VALUE::UNIT_PREFIX aPrefix)
wxString spice_prefix(SPICE_VALUE::UNIT_PREFIX aPrefix)
const int scale
std::vector< FAB_LAYER_COLOR > dummy
A SPICE_VALUE_FORMAT holds precision and range info for formatting values.Helper class to handle Spic...
Definition spice_value.h:39
wxString ToString() const
void UpdateUnits(const wxString &aUnits)
void FromString(const wxString &aString)
VECTOR3I res